<?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[pauls dev blog]]></title><description><![CDATA[A blog featuring tutorials and learnings about Programming, Productivity, DevOps with Docker, Cloud-related technologies, and other challenges in IT.]]></description><link>https://hashnode.knulst.de</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1661417607197/pXxp7Ou5M.png</url><title>pauls dev blog</title><link>https://hashnode.knulst.de</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 19 Apr 2026 09:09:27 GMT</lastBuildDate><atom:link href="https://hashnode.knulst.de/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[4 Important Services Everyone Should Deploy In A Docker Swarm]]></title><description><![CDATA[In a previous article, I showed how a Docker Swarm is set up in ~15 minutes.
Remember: Whenever you read “Docker Swarm” we are talking about “Docker in Docker Swarm mode”
Within this article, I will show and explain four services everyone should use ...]]></description><link>https://hashnode.knulst.de/4-important-services-everyone-should-deploy-in-a-docker-swarm</link><guid isPermaLink="true">https://hashnode.knulst.de/4-important-services-everyone-should-deploy-in-a-docker-swarm</guid><category><![CDATA[Devops]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Sat, 29 Jul 2023 08:31:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1690619279183/0edf7517-8743-4585-8304-ebd3ba10c981.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In a previous article, I showed <a target="_blank" href="https://www.paulsblog.dev/docker-swarm-in-a-nutshell/">how a Docker Swarm is set up in ~15 minutes</a>.</p>
<p><strong>Remember:</strong> Whenever you read “Docker Swarm” we are talking about “Docker in Docker Swarm mode”</p>
<p>Within this article, I will show and explain four services everyone should use in their Docker Swarm: <strong>traefik</strong>, <strong>Portainer</strong>, <strong>Docker-Registry</strong>, <strong>FTP</strong>.</p>
<h2 id="heading-traefik">Traefik</h2>
<blockquote>
<p><em>Makes Networking Boring</em>
<em>Cloud-Native Networking Stack That Just Works.</em></p>
</blockquote>
<p>One important service in any Docker Swarm is <a target="_blank" href="https://www.traefik.io/?ref=paulsblog.dev">traefik</a>. I use this for assigning domains/subdomains to every docker service. A simple docker-compose.yml can be found on my FTP <a target="_blank" href="https://ftp.f1nalboss.de/data/docker-compose.traefik.yml?ref=paulsblog.dev">here</a>. To use this file it is necessary to update the labels for your docker swarm by executing:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> NODE_ID=$(docker info -f <span class="hljs-string">'{{.Swarm.NodeID}}'</span>)
docker node update --label-add traefik-public.traefik-public-certificates=<span class="hljs-literal">true</span> <span class="hljs-variable">$NODE_ID</span>
</code></pre>
<p>These two commands assume that you want to have traefik on your manager node! To understand what a manager node <a target="_blank" href="https://docs.docker.com/engine/swarm/how-swarm-mode-works/nodes/?ref=paulsblog.dev">have a look at the docker documentation</a>.</p>
<p>Another <strong>VERY</strong> important step is creating a file named <code>acme.json</code> within the folder where the docker-compose will be stored and do:</p>
<pre><code class="lang-bash">chmod 600 acme.json
</code></pre>
<p>Furthermore, you have to define a <code>TRAEFIK_USERNAME</code>, <code>TRAEFIK_HASHED_PASSWORD</code>, <code>TRAEFIK_DOMAIN</code> and <code>TRAEFIK_SSLEMAIL</code>:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> TRAEFIK_USERNAME=admin
<span class="hljs-built_in">export</span> TRAEFIK_HASHED_PASSWORD=$(openssl passwd -apr1 testpassword)
<span class="hljs-built_in">export</span> TRAEFIK_DOMAIN=dashboard.YOUR_DOMAIN.tld
<span class="hljs-built_in">export</span> TRAEFIK_SSLEMAIL=your_email@address.de
</code></pre>
<p>The last command which has to be executed before you can deploy the service is creating the <code>traefik-public</code> network which will be used across all containers:</p>
<pre><code>docker network create --driver=overlay traefik-public
</code></pre><p>After this is done it is possible to deploy traefik to your docker swarm:</p>
<pre><code>docker stack deploy -c docker-compose.traefik.yml
</code></pre><p>Just test it by launching <code>https://dashboard.YOUR_DOMAIN.tld</code>. This domain will use an SSL certificate and you can log in with <strong><em>admin/testpassword</em></strong> if you followed the above instructions.</p>
<h2 id="heading-portainer">Portainer</h2>
<blockquote>
<p><em>Portainer is a powerful, GUI-based Container-as-a-Service solution that helps organizations manage and deploy cloud-native applications easily and securely.</em></p>
</blockquote>
<p>My docker-compose.yml for Portainer is not special. You can find it <a target="_blank" href="https://ftp.f1nalboss.de/data/docker-compose.portainer.yml?ref=paulsblog.dev">here</a> <strong>if you need it</strong>. Normally there are no differences between my and the one you find if you google for Portainer service.</p>
<p>If you use mine you have to <strong><strong>add a label to your manager!</strong></strong> <strong><strong>This is very important</strong></strong> because Portainer needs a connection to your docker socket. You can add the label with these commands:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> NODE_ID=$(docker info -f <span class="hljs-string">'{{.Swarm.NodeID}}'</span>)
docker node update --label-add portainer.portainer-data=<span class="hljs-literal">true</span> <span class="hljs-variable">$NODE_ID</span>
</code></pre>
<p>Furthermore declare the <code>PORTAINER_DOMAIN</code> which will be used.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> PORTAINER_DOMAIN=portainer.<span class="hljs-variable">$PRIMARY_DOMAIN</span>
</code></pre>
<p>After you have done this you can deploy Portainer:</p>
<pre><code class="lang-bash">docker stack deploy -c docker-compose.portainer.yml portainer
</code></pre>
<p><strong>Timing Note:</strong> Make sure you log in and create your credentials soon after Portainer is ready, or it will automatically shut down itself for security. If you didn’t create the credentials on time and it shut down itself automatically, you can force it to restart with:</p>
<pre><code class="lang-bash">docker service update portainer_portainer --force
</code></pre>
<h2 id="heading-docker-registry">Docker Registry</h2>
<p>The Registry is a stateless, highly scalable server side application that stores and lets you distribute Docker images. The Registry is open-source, under the permissive Apache license.</p>
<p>The docker registry is important to have in a swarm environment if you don't want to upload your code/data to a public registry. If you don't have any private registry for yourself you have to upload the resulting image to docker-hub each time you extend an official image with your application code. While this is great if you only want to extend functionality in a general way it is not advisable if you copy your website with your closed source code into the image.</p>
<p>Imagine you have a modified Nginx image where the HTML folder is copied into the image:</p>
<pre><code class="lang-docker"><span class="hljs-keyword">FROM</span> nginx
<span class="hljs-keyword">COPY</span><span class="bash"> html /usr/share/nginx/html</span>
</code></pre>
<p>In many cases the <code>html</code> folder will contain your website which you don't want someone to use. If you have a private registry you can build the image and upload it but if you don't have one you have to use a public registry.</p>
<p><strong>That's why I think that every swarm needs to have access to a private registry.</strong> And that's why I created one. Normally you can set up a registry with a simple <code>docker run</code> command but I wanted to have a registry that can be reached from everywhere. Because of that, I created a registry that could be accessed by username and password.</p>
<p>My personal <code>docker-compose.yml</code> can be downloaded <a target="_blank" href="https://ftp.f1nalboss.de/data/docker-compose.registry.yml?ref=paulsblog.dev">here</a>. It will run within my Traefik environment and create a new registry that I can use from every node in my swarm. To deploy the service you have to declare some environment variables which are used while deploying: <code>REGISTRY_USERNAME</code>, <code>REGISTRY_HASHED_PASSWORD</code> and <code>REGISTRY_HOST</code>:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> REGISTRY_USERNAME=reg_adm
<span class="hljs-built_in">export</span> REGISTRY_HASHED_PASSWORD=$(openssl passwd -apr1 regsupersecret)
<span class="hljs-built_in">export</span> REGISTRY_HOST=reg.YOUR_DOMAIN.tld
</code></pre>
<p>With these variables you can deploy the service:</p>
<pre><code class="lang-bash">docker stack deploy -c docker-compose.registry.yml registry
</code></pre>
<p>To use the registry you have to do two more things. The first thing is a quality of life feature to easily change the domain of the registry without affecting every container which uses an image from the private registry. <strong><strong>Add DOCKER_REGISTRY to .profile for</strong></strong> <code>****root****</code> <strong><strong>or whatever user you are using so it is known if docker-compose files should be pushed/downloaded</strong></strong>. The second task is very important. You have to execute <code>docker login</code> on every node of your swarm so that every node is allowed to pull images.</p>
<p>Now you can start using your private registry in docker-compose.yml. If you created a <code>DOCKER_REGISTRY</code> environment variable you can use it like this in your docker-compose.yml:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">myapp:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">${DOCKER_REGISTRY}/simple-app</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">./</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
</code></pre>
<p>If you want to deploy a service that contains the above part you have to build and push your image to deploy it correctly:</p>
<pre><code class="lang-bash">docker-compose build
docker-compose push
docker stack deploy -c docker-compose.yml www
</code></pre>
<p>If you only build and deploy it other nodes in your swarm cannot pull the image and so the service cannot be deployed.</p>
<h2 id="heading-ftp">FTP</h2>
<p>Another important service is an FTP server for saving files you want to use anywhere in any app or service you create while working with the swarm.</p>
<p>I decided to use “pure-ftp” because it was the one I found while googling for a nice FTP server that runs within a docker environment. Because I want to keep it simple I created an FTP server without any <code>traefik</code> configuration. <strong>BUT</strong> I did a simple trick in putting the FTP server together with a website in a docker-compose.yml. I have done this because I want to have the possibility to download files from the server over <code>https</code> which I uploaded with <code>ftp</code>.</p>
<p>After configuration, I came up with <a target="_blank" href="https://ftp.f1nalboss.de/data/docker-compose.web.yml?ref=paulsblog.dev">this docker-compose.yml</a>. Within the file, you can see that I use a custom Dockerfile for the web service which contains:</p>
<pre><code class="lang-docker"><span class="hljs-keyword">FROM</span> nginx
<span class="hljs-keyword">COPY</span><span class="bash"> html /usr/share/nginx/html</span>
</code></pre>
<p>The trick I describe is just the defined volume within the <code>docker-compose.yml</code>. As you can see I defined data in both services. Within <code>web-service</code> I just have an extra folder within the Nginx HTML folder so that I can access it from the web. And within the <code>ftp-service</code> I define data as the place where user can upload their data.</p>
<p>Before it is possible to deploy this service you have to declare environment variables: <code>FTP_USERNAME</code>, <code>FTP_PASSWORD</code>, <code>FTP_DOMAIN_FOR_CERT</code>, <code>FTP_ORG_FOR_CERT</code>, <code>FTP_COUNTRYCODE_FOR_CERT</code> and <code>WEBSERVICE_DOMAIN</code>:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> WEBSERVICE_DOMAIN=www.MYDOMAIN.tld
<span class="hljs-built_in">export</span> FTP_USERNAME=SUPERUSER
<span class="hljs-built_in">export</span> FTP_PASSWORD=clearTextPW
<span class="hljs-built_in">export</span> FTP_DOMAIN_FOR_CERT=<span class="hljs-variable">$WEBSERVICE_DOMAIN</span>
<span class="hljs-built_in">export</span> FTP_ORG_FOR_CERT=mybusiness
<span class="hljs-built_in">export</span> FTP_COUNTRYCODE_FOR_CERT=DE
</code></pre>
<p>Furthermore, you have to add a label to any node of your swarm. To achieve this use <code>docker node ls</code> to find out the ID from every node and execute:</p>
<pre><code class="lang-bash">docker node update --label-add www.ftp-data=<span class="hljs-literal">true</span> ID_OF_NODE_TO_USE
</code></pre>
<p>After this is done you can safely deploy your website with enabled FTP</p>
<pre><code class="lang-bash">docker stack deploy -c docker-compose.web.yml webandftp
</code></pre>
<p>Now it is possible to connect with an <a target="_blank" href="https://filezilla-project.org/?ref=paulsblog.dev">FTP client</a> to your <code>WEBSERVICE_DOMAIN</code> and upload a file (<code>test.txt</code>) which then can be accessed by this URL: <code>WEBSERVICE_DOMAIN/data/test.txt</code></p>
<p>The whole FTP docker service can be downloaded from my GitHub:</p>
<p>https://github.com/paulscode-de/ftp-server</p>
<p>It is very important to say that you only can connect with your WEBSERVICE_DOMAIN if this domain also has an A record to your manager node!</p>
<h2 id="heading-5-closing-notes">5. Closing Notes</h2>
<p>I hope you find this article helpful and can use my provided files to set up these services within your own Docker Swarm.</p>
<p>In my humble opinion, these four services should be present in every Docker Swarm environment because they are mandatory (or exchanged with function-like services).</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/services-you-want-to-have-in-a-swarm-environment/">https://www.paulsblog.dev/services-you-want-to-have-in-a-swarm-environment/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@frankiefoto?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">frank mckenna</a> / <a target="_blank" href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Deploy Free Figma Alternative Penpot With Docker]]></title><description><![CDATA[Paul Knulst  in  Docker • Oct 6, 2022 • 8 min read

As we all probably heard Adobe has acquired the popular design tool Figma for a whopping $20 billion. Unfortunately, this "strategy" of eliminating competition by acquiring businesses is common for ...]]></description><link>https://hashnode.knulst.de/deploy-free-figma-alternative-penpot-with-docker</link><guid isPermaLink="true">https://hashnode.knulst.de/deploy-free-figma-alternative-penpot-with-docker</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Open Source]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Tue, 06 Jun 2023 12:01:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1686052701027/d8a79f29-b5c7-4080-ba39-1cd578b380fa.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/docker/">Docker</a> • Oct 6, 2022 • 8 min read</p>
<hr />
<p>As we all probably heard Adobe has acquired the popular design tool <a target="_blank" href="https://www.figma.com/">Figma</a> for a whopping <strong>$20 billion</strong>. Unfortunately, this "strategy" of eliminating competition by acquiring businesses is common for big tech companies.</p>
<p>But, <strong>luckily</strong>, there is a <strong>free and open-source</strong> design tool that also does some things better. Also, it was inspired by Figma.</p>
<h2 id="heading-penpot-free-amp-open-source-design-tool">Penpot: Free &amp; Open-Source Design Tool</h2>
<p><a target="_blank" href="https://penpot.app/">Penpot</a> is an open-source project that is actively developed and totally free to use for everyone.</p>
<p><strong>Here are some major features that make Penpot interesting</strong></p>
<ul>
<li><p>Free and open-source (of course).</p>
</li>
<li><p><strong>Option to Self-host (will be covered here primarily).</strong></p>
</li>
<li><p>Cross-platform.</p>
</li>
<li><p><strong>Using SVG</strong> as the native format.</p>
</li>
<li><p><strong>Web-based</strong>.</p>
</li>
<li><p>Featuring industry-standard features (inspired by Figma).</p>
</li>
</ul>
<p>Watch this official Penpot video to learn about the basics of Penpot:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=JozESuPcVpg">https://www.youtube.com/watch?v=JozESuPcVpg</a></div>
<p> </p>
<p>A very important feature of Penpot is the use of SVG as their native format instead of PNG/JPG because it enables you to be compatible with many vector graphic editing tools.</p>
<p>This is especially useful because you will not get locked by being forced to use a proprietary file format that no other application can use.</p>
<p>Also, Penpot aims to use the absolute best of open standards that already exist. The <strong>CEO of Penpot,</strong> <em>Pablo Ruiz-Múzquiz</em>, mentions more about it:</p>
<blockquote>
<p>If you go for SVG (open standard, web, mobile, etc) at the storage level, you can suddenly integrate all your Penpot designs with your code repos. You could make changes to the actual representation of the design itself thanks to SVG and not yet another closed format. That opens the door to massive opportunities for designers AND devs. Also, SVG means we are low-code ready for free. You can pick any element in Penpot and ask for its SVG (and CSS) representation knowing it's actually what it is, no translation. That brings a more trustworthy relationship between designers and devs and allows frontend devs to try out their design skillset.</p>
</blockquote>
<hr />
<p>As mentioned before a very important feature of Penpot is the <strong>ability to self-host</strong> it in a Docker container on your local machine and also on any server (that runs Docker). The following steps will show how easily you can use Docker to host your own Penpot to replace Figma as your design tool.</p>
<p><em>If you want to test Penpot before installing it as a self-hosted version you can go to</em> <a target="_blank" href="https://design.penpot.app/"><em>https://design.penpot.app/</em></a> <em>log in with GitHub/Gitlab/Google account or create n new account to test it. But I would avoid doing this and sharing your personal data because deploying it locally is done super fast (if you already have Docker installed)</em></p>
<h2 id="heading-deploy-penpot-locally-with-docker">Deploy Penpot locally with Docker</h2>
<h3 id="heading-install-docker">Install Docker</h3>
<p>To deploy Penpot locally (or remotely) you need to have Docker installed. To install Docker on your system follow the <a target="_blank" href="https://docs.docker.com/get-docker/">official tutorial on</a> <a target="_blank" href="http://docker.com">docker.com</a>. If you are using Windows and <strong>aren't allowed to install Docker Desktop</strong> you can follow this guide:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/how-to-install-docker-without-docker-desktop-on-windows/">How To Install Docker Without Docker Desktop On Windows</a></p>
<h3 id="heading-deploy-penpot">Deploy Penpot</h3>
<p>If Docker is installed it is very easy to start up a Penpot instance locally. To do this you can simply retrieve the latest Compose and config file from <a target="_blank" href="https://github.com/penpot/penpot">the official Penpot Github project</a> by download them:</p>
<pre><code class="lang-bash">    wget https://raw.githubusercontent.com/penpot/penpot/main/docker/images/docker-compose.yaml
    wget https://raw.githubusercontent.com/penpot/penpot/main/docker/images/config.env
</code></pre>
<p>The config file from the GitHub project is quite big and contains several variables that you will never use working locally with Penpot. It would be sufficient to use the following config as it only contains mandatory values:</p>
<pre><code class="lang-bash">    <span class="hljs-comment"># Standard database connection parameters (only postgresql is supported):</span>
    PENPOT_DATABASE_URI=postgresql://penpot-postgres/penpot
    PENPOT_DATABASE_USERNAME=penpot
    PENPOT_DATABASE_PASSWORD=penpot

    <span class="hljs-comment"># Redis is used for the websockets notifications.</span>
    PENPOT_REDIS_URI=redis://penpot-redis/0

    ASSETS_STORAGE_BACKEND=assets-fs
    PENPOT_STORAGE_ASSETS_FS_DIRECTORY=/opt/data/assets

    PENPOT_TELEMETRY_ENABLED=<span class="hljs-literal">false</span>

    <span class="hljs-comment"># Enable or disable external user registration process.</span>
    PENPOT_REGISTRATION_ENABLED=<span class="hljs-literal">false</span>
</code></pre>
<p>Now that you downloaded (or created) both files switch to the folder and start the Docker service with:</p>
<pre><code class="lang-bash">    docker-compose -p penpot up -d
</code></pre>
<p>If the process finishes you can open <a target="_blank" href="http://localhost:9001">http://localhost:9001</a> and will see the following screen if Penpot was installed correctly:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1686052630130/e5fa9023-a80e-40ad-ad95-6b6373cb0d9a.jpeg" alt="Login screen for Penpot after deploying it to localhost with Docker" class="image--center mx-auto" /></p>
<h3 id="heading-create-a-penpot-user">Create a Penpot user</h3>
<p>Although you deployed Penpot on your local machine you cannot create a user using the Web interface because you started Penpot without configuring an SMTP account to receive registration emails. However, this is no problem because you can simply use this Docker command to manually create an already-activated user:</p>
<pre><code class="lang-bash">    docker <span class="hljs-built_in">exec</span> -it penpot-penpot-backend-1 ./manage.sh create-profile
</code></pre>
<p><strong>IMPORTANT</strong>: Check if <code>penpot-penpot-backend-1</code> is the correct name of your backend container. You can do this by executing <code>docker ps</code>.</p>
<p>After executing the command you could get the following output which is confusing:</p>
<pre><code class="lang-bash">    [2022-10-06 10:18:13.523] I app.config - hint=<span class="hljs-string">"flags initialized"</span>, flags=<span class="hljs-string">"backend-api-doc,secure-session-cookies,login,backend-worker,registration"</span>
    [2022-10-06 10:18:14.295] I app.metrics - action=<span class="hljs-string">"initialize metrics"</span>
    Email:  [2022-10-06 10:18:14.334] I app.db - hint=<span class="hljs-string">"initialize connection pool"</span>, name=<span class="hljs-string">"main"</span>, uri=<span class="hljs-string">"postgresql://penpot-postgres/penpot"</span>, read-only=<span class="hljs-literal">false</span>, with-credentials=<span class="hljs-literal">true</span>, min-size=0, max-size
    =30
</code></pre>
<p>Ignore everything and just type an email that you want to use in your local installation, then set your name and password. The resulting log will look like this:</p>
<pre><code class="lang-bash">    [2022-10-06 10:22:33.900] I app.config - hint=<span class="hljs-string">"flags initialized"</span>, flags=<span class="hljs-string">"backend-api-doc,secure-session-cookies,login,backend-worker,registration"</span>
    [2022-10-06 10:22:34.636] I app.metrics - action=<span class="hljs-string">"initialize metrics"</span>
    Email:  [2022-10-06 10:22:34.675] I app.db - hint=<span class="hljs-string">"initialize connection pool"</span>, name=<span class="hljs-string">"main"</span>, uri=<span class="hljs-string">"postgresql://penpot-postgres/penpot"</span>, read-only=<span class="hljs-literal">false</span>, with-credentials=<span class="hljs-literal">true</span>, min-size=0, max-size
    =30
    user@local.de
    Full Name:  Paul Knulst
    Password:
    User created successfully.
</code></pre>
<p>After your user was created you can log in to your Penpot installation at <a target="_blank" href="http://localhost:9001/">http://localhost:9001/</a> and start using Penpot.</p>
<h2 id="heading-deploy-penpot-remotely-with-docker-and-traefik">Deploy Penpot remotely with Docker and Traefik</h2>
<p>Having a running installation of Penpot locally is very useful if you are the only person working with it. Normally, you will work in teams where multiple designers, developers, or others collaborate together on different designs. To allow doing this with Penpot you can deploy Penpot on any server using Docker and Traefik.</p>
<h3 id="heading-installing-traefik">Installing Traefik</h3>
<p>To install Traefik on your server and use it to forward URLs to Docker service and automatically assign SSL certificates you can follow this guide:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/how-to-setup-traefik-with-automatic-letsencrypt-certificate-resolver/">How to setup Traefik v2 with automatic Let’s Encrypt certificate resolver</a></p>
<p>Keep in mind that you need at least one URL that points to the server where your Traefik instance will run. If you do not have any URL you have to buy one and map it to your server.</p>
<h3 id="heading-updating-config">Updating Config</h3>
<p>Deploying Penpot on a server with a specific URL where multiple users can work together needs some changes to the prior shown config file because the registration process should be enabled. Therefore, you have to add an email account that will be used by Penpot to send registration emails. A simple Google/GMX/Apple Mail account will be sufficient.</p>
<p>Change the <code>PENPOT_REGISTRATION_ENABLED</code> environment variable to true and add the following variables (with correct values) to the config file:</p>
<pre><code class="lang-bash">    PENPOT_REGISTRATION_DOMAIN_WHITELIST=<span class="hljs-string">""</span>

    <span class="hljs-comment">#Your public penpot url</span>
    PENPOT_PUBLIC_URI=

    <span class="hljs-comment"># Enable Email </span>
    PENPOT_SMTP_ENABLED=<span class="hljs-literal">true</span>
    PENPOT_SMTP_DEFAULT_FROM=
    PENPOT_SMTP_DEFAULT_REPLY_TO=
    PENPOT_SMTP_HOST=
    PENPOT_SMTP_PORT=
    PENPOT_SMTP_USERNAME=
    PENPOT_SMTP_PASSWORD=
    PENPOT_SMTP_TLS=
    PENPOT_SMTP_SSL=
</code></pre>
<p>An important setting here is <code>PENPOT_REGISTRATION_DOMAIN_WHITELIST=""</code> where you can add a comma-separated list of allowed email domains to register. If you leave it empty every domain is allowed. <a target="_blank" href="https://gist.github.com/paulknulst/18829ae330bf6fcd3917aee7ff4a3b7d">Find the full config in this gist on Github</a>.</p>
<h3 id="heading-adjust-compose-file-for-traefik-usage">Adjust Compose file for Traefik usage</h3>
<p>After updating the config file you need to update the Compose file and add the Traefik network and the labels for Traefik to automatically generate an SSL certificate after deploying Penpot. Copy the content of the following Compose file into yours:</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">version:</span> <span class="hljs-string">"3.5"</span>

    <span class="hljs-attr">networks:</span>
      <span class="hljs-attr">default:</span>
        <span class="hljs-attr">external:</span> <span class="hljs-literal">false</span>
      <span class="hljs-attr">traefik-public:</span>
        <span class="hljs-attr">external:</span> <span class="hljs-literal">true</span>

    <span class="hljs-attr">volumes:</span>
      <span class="hljs-attr">postgres_data:</span>
      <span class="hljs-attr">assets_data:</span>

    <span class="hljs-attr">services:</span>
      <span class="hljs-attr">frontend:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">"penpotapp/frontend:latest"</span>
        <span class="hljs-attr">hostname:</span> <span class="hljs-string">frontend.penpot</span>
        <span class="hljs-attr">volumes:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">assets_data:/opt/data</span>
        <span class="hljs-attr">env_file:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">config.env</span>
        <span class="hljs-attr">depends_on:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">penpot-backend</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">penpot-exporter</span>
        <span class="hljs-attr">networks:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik-public</span>
        <span class="hljs-attr">labels:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.enable=true</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.docker.network=traefik-public</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.constraint-label=traefik-public</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.penpot-http.rule=Host(`${PENPOT_URL}`)</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.penpot-http.entrypoints=http</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.penpot-http.middlewares=https-redirect</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.penpot-https.rule=Host(`${PENPOT_URL}`)</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.penpot-https.entrypoints=https</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.penpot-https.tls=true</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.penpot-https.tls.certresolver=le</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.services.penpot.loadbalancer.server.port=80</span>
      <span class="hljs-attr">backend:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">"penpotapp/backend:latest"</span>
        <span class="hljs-attr">hostname:</span> <span class="hljs-string">backend.penpot</span>
        <span class="hljs-attr">volumes:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">assets_data:/opt/data</span>
        <span class="hljs-attr">depends_on:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">postgres</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">redis</span>
        <span class="hljs-attr">env_file:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">config.env</span>
        <span class="hljs-attr">networks:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>

      <span class="hljs-attr">exporter:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">"penpotapp/exporter:latest"</span>
        <span class="hljs-attr">environment:</span>
          <span class="hljs-comment"># Don't touch it; this uses internal docker network to</span>
          <span class="hljs-comment"># communicate with the frontend.</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">PENPOT_PUBLIC_URI=http://frontend.penpot</span>
        <span class="hljs-attr">networks:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>

      <span class="hljs-attr">postgres:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">"postgres:13"</span>
        <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
        <span class="hljs-attr">stop_signal:</span> <span class="hljs-string">SIGINT</span>
        <span class="hljs-attr">environment:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_INITDB_ARGS=--data-checksums</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_DB=penpot</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_USER=penpot</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">POSTGRES_PASSWORD=penpot</span>
        <span class="hljs-attr">volumes:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">postgres_data:/var/lib/postgresql/data</span>
        <span class="hljs-attr">networks:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>

      <span class="hljs-attr">redis:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">redis:6</span>
        <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
        <span class="hljs-attr">networks:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>
</code></pre>
<h3 id="heading-deploy-penpot-1">Deploy Penpot</h3>
<p>Before you finally can deploy the Docker service and use Penpot with your coworkers/friends you have to set your URL. This has to be the same URL that you used in the config (without HTTPS). Also, the URL has to point to your server!</p>
<pre><code class="lang-bash">    <span class="hljs-built_in">export</span> PENPOT_URL=your-website.com
</code></pre>
<p>Now, switch to your folder and deploy your Penpot service:</p>
<pre><code class="lang-bash">    docker-compose up -d
</code></pre>
<p>Switch to your URL and register a new user. You should get an email from Penpot and can use it with your coworkers.</p>
<p>Keep in mind that you still can deactivate registration by setting the appropriate environment variable within the config and redeploying the server. If you do this you can create new users by executing the following command and manually set username/password:</p>
<pre><code class="lang-bash">    docker <span class="hljs-built_in">exec</span> -it penpot-penpot-backend-1 ./manage.sh create-profile
</code></pre>
<p>With this approach, you will not need an email account and still be able to work with others.</p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>Congratulations if you followed my approach you have just installed Penpot as your own Figma replacement on either your local machine or on a server to use with collaborators.</p>
<p>The full Compose file (and config) for remote deployment can be found <a target="_blank" href="https://gist.github.com/paulknulst/18829ae330bf6fcd3917aee7ff4a3b7d">within the GitHub Gist</a> I created for this article.</p>
<p>This is the end of this tutorial. Hopefully, you are now able to set up your personal installation. If you still have questions about setting up Penpot as a replacement for Figma you can just ask in the comment section. Also, if you enjoyed reading this article consider commenting with your valuable thoughts! I would love to hear your feedback about my tutorial or Penpot in general.</p>
<p>Furthermore, share this article with fellow designers to help them to replace Figma with Penpot.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/deploy-free-figma-alternative-penpot-with-docker/">https://www.paulsblog.dev/deploy-free-figma-alternative-penpot-with-docker/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@theshubhamdhage?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">GuerrillaBuzz Crypto PR</a> / <a target="_blank" href="https://unsplash.com/s/photos/figma?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[How to Build a Video Player in Vanilla Javascript And HTML5]]></title><description><![CDATA[Probably, a decade ago it was impossible to play your video or audio inside of your browser without any third-party services such as Flash or Silverlight. You needed to install a plugin and only play your media while using it, so as you can see it wa...]]></description><link>https://hashnode.knulst.de/how-to-build-a-video-player-in-vanilla-javascript-and-html5</link><guid isPermaLink="true">https://hashnode.knulst.de/how-to-build-a-video-player-in-vanilla-javascript-and-html5</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[HTML5]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Wed, 31 May 2023 05:57:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1685512267080/ba6965e2-e956-42c3-bcb1-9087d6fb05cc.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Probably, a decade ago it was impossible to play your video or audio inside of your browser without any third-party services such as Flash or Silverlight. You needed to install a plugin and only play your media while using it, so as you can see it was very uncomfortable, with low speed and high delays. Nowadays, we have JavaScript with the new version of HTML5, with these new technologies and tools we can stream our video much quicker, easier, and without any latency. To do it you will need only a simple <code>&lt;video&gt;</code> tag that was added in HTML5 and give a link to your video stored on your computer. Then, by using one simple attribute called <code>controls</code> you’ll have a default video player which was built into the browser. It’s elementary and doesn’t have many features, so if you want to stream a video on your website in a more professional way using your personal video player, you’ll need to use JavaScript. And we’ll teach you how to do it in this article!</p>
<p>By the end of this guide you’ll have something <a target="_blank" href="https://codepen.io/paulknulst/pen/qBYNxxa">similar to this Codepen</a>, so if you’re excited, keep reading and follow this tutorial step-by-step!</p>
<h2 id="heading-setting-up-the-project">Setting Up the Project</h2>
<p>Assuming you are working with UNIX system (or have Git BASH on Windows) you can create all three files that are necessary to build a video player in JavaScript with this command:</p>
<pre><code class="lang-bash">mkdir video-player
<span class="hljs-built_in">cd</span> video-player
touch index.html script.js style.css
</code></pre>
<p>To add a simple video player to our application we have to add the following code to our <code>index.html</code>:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>How to build a video player in Javascript<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"style.css"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"player"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">video</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"video"</span> <span class="hljs-attr">controls</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">source</span>
                    <span class="hljs-attr">src</span>=<span class="hljs-string">"https://ftp.f1nalboss.de/data/imgly/videoplayer/testvideo.mp4"</span>
                    <span class="hljs-attr">type</span>=<span class="hljs-string">"video/mp4"</span>
                /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">source</span>
                    <span class="hljs-attr">src</span>=<span class="hljs-string">"https://ftp.f1nalboss.de/data/imgly/videoplayer/testvideo.mp4"</span>
                    <span class="hljs-attr">type</span>=<span class="hljs-string">"video/webm"</span>
                /&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>No HTML5 video supported<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">video</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"script.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Within the above code, the <code>&lt;video&gt;</code> element uses a remote video from my FTP. You can either use my default video or add any video from your local computer by adjusting the <code>src</code> attribute. HTML5 specification supports three different video formats and the snippet used multiple <code>&lt;source&gt;</code> tags to make the videos available in MP4 and WebM. Furthermore, the <code>&lt;p&gt;</code> tag is used to display pre-defined content to user agents that do not support the <code>video</code> element.</p>
<p>The HTML5 <code>&lt;video&gt;</code> tag accepts several native attributes. For example, the <code>controls</code> attribute displays the standard player controls when added or set to true. You can find out more about <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-controls">all video attributes here</a>.</p>
<p>Before continuing you should apply all styles that are needed within this tutorial by populating your <code>style.css</code> with all styles <a target="_blank" href="https://codepen.io/paulknulst/pen/qBYNxxa">from this CodePen</a>. Save and open your <code>index.html</code> and load it within the browser to see the embedded video player as seen below:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685512386161/06f37a95-8e3f-435a-b22d-ad032300ee23.png" alt="Screenshot of the video player UI" class="image--center mx-auto" /></p>
<h2 id="heading-customize-the-video-player-with-javascript">Customize the Video Player With JavaScript</h2>
<p>To customize the video player, you first have to remove the <code>controls</code> attribute that displays <code>Play</code>, <code>Pause</code>, <code>Volume</code>, etc because you will implement your own custom controls within this tutorial. Now, check your browser you will recognize that the controls are gone and you cannot play the video anymore.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1685512515668/869c3481-c17f-43ca-a18b-425983fd4123.png" alt="Screenshot of the video player UI without controls attribute" class="image--center mx-auto" /></p>
<h3 id="heading-add-play-and-pause">Add Play and Pause</h3>
<p>To enable play and pause the video you have to add a new button to the <code>index.html</code>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"controls"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
            <span class="hljs-attr">class</span>=<span class="hljs-string">"controls__btn playPauseBtn"</span>
            <span class="hljs-attr">title</span>=<span class="hljs-string">"Toggle Play"</span>
            &gt;</span>
        ►
    <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Afterward, open your <code>script.js</code> and enable functionality by adding this code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> videoContainer = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">".video-container"</span>);
<span class="hljs-keyword">const</span> playPauseBtn = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">".playPauseBtn"</span>);
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">togglePlay</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (videoContainer.paused || videoContainer.ended) {
    videoContainer.play();
  } <span class="hljs-keyword">else</span> {
    videoContainer.pause();
  }
}
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">updatePlayBtn</span>(<span class="hljs-params"></span>) </span>{
  playPauseBtn.innerHTML = videoContainer.paused ? <span class="hljs-string">"►"</span> : <span class="hljs-string">"❚❚"</span>;
}
playPauseBtn.addEventListener(<span class="hljs-string">"click"</span>, togglePlay);
videoContainer.addEventListener(<span class="hljs-string">"click"</span>, togglePlay);
videoContainer.addEventListener(<span class="hljs-string">"play"</span>, updatePlayBtn);
videoContainer.addEventListener(<span class="hljs-string">"pause"</span>, updatePlayBtn);
</code></pre>
<p>Within this javascript code first the <code>video-container</code> element and the <code>playPauseBtn</code> is selected (Line 1 and 2). Then two functions will are defined: <code>togglePlay()</code> and <code>updatePlayBtn()</code>. <code>togglePlay()</code> is used to stop and start the video based on its actual state. <code>updatePlayBtn</code> is used to switch between the Icon which is shown within the video player.</p>
<p>In the last part of the snippet, a click event listener is added to the <code>playPauseBtn</code> that executes the <code>togglePlay()</code> function. Next, three click event listeners are added to the <code>videoContainer</code> that executes <code>togglePlay()</code> on mouse click and also executes <code>updatePlayBtn</code> based on the video's state.</p>
<p>Now you can reload your <code>index.html</code> and should be able to play and pause the video by either clicking the video or the button:</p>
<p><img src="https://www.paulsblog.dev/content/images/2022/09/playpausecontrol.gif" alt="Animated demonstration of play and pause of the video player" class="image--center mx-auto" /></p>
<h3 id="heading-add-a-progress-bar">Add A Progress Bar</h3>
<p>Next, a progress bar will be implemented to show the current timestamp of the video when played. First, add a <code>&lt;div&gt;</code> tag to the <code>index.html</code> which will act as the progress bar:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"controls"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"progress__filled"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    // ..
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</code></pre>
<p>Then open the <code>script.js</code> and add the following snippet:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> progress = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">".progress"</span>);
<span class="hljs-keyword">const</span> progressBar = <span class="hljs-built_in">document</span>.querySelector(<span class="hljs-string">".progress__filled"</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleProgress</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> progressPercentage = (videoContainer.currentTime / videoContainer.duration) * <span class="hljs-number">100</span>;
  progressBar.style.flexBasis = <span class="hljs-string">`<span class="hljs-subst">${progressPercentage}</span>%`</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">jump</span>(<span class="hljs-params">e</span>) </span>{
  <span class="hljs-keyword">const</span> position = (e.offsetX / progress.offsetWidth) * videoContainer.duration;
  videoContainer.currentTime = position;
}

videoContainer.addEventListener(<span class="hljs-string">"timeupdate"</span>, handleProgress);
progress.addEventListener(<span class="hljs-string">"click"</span>, jump);
<span class="hljs-keyword">let</span> mousedown = <span class="hljs-literal">false</span>;
progress.addEventListener(<span class="hljs-string">"mousedown"</span>, <span class="hljs-function">() =&gt;</span> (mousedown = <span class="hljs-literal">true</span>));
progress.addEventListener(<span class="hljs-string">"mousemove"</span>, <span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> mousedown &amp;&amp; jump(e));
progress.addEventListener(<span class="hljs-string">"mouseup"</span>, <span class="hljs-function">() =&gt;</span> (mousedown = <span class="hljs-literal">false</span>));
</code></pre>
<p>In this snippet, the <code>progress</code> container and the <code>progress__filled</code> element will be selected and two functions will be added: <code>handleProgress()</code> and <code>jump(e)</code>. <code>handleProgress()</code> will be responsible for updating the progress bar. The <code>jump(e)</code> function is used to enable clicking on the progress bar to jump to the position within the video.</p>
<p>The last part contains all event listeners that are needed for the progress bar. The <code>handleProgress()</code> will be called at every <code>timeupdate</code> event. Also clicking anywhere on the progress bar will call the <code>jump(e)</code> method and the video will jump to the position. Additionally, <code>mousedown</code>, <code>mousemove</code>, and <code>mouseup</code> will be used to enable <em>sliding</em> through the video while holding the mouse button down on the progress bar.</p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>Congratulations, if you followed the tutorial you learned how to implement your own video player and add custom controls using JavaScript. Now, you can use <a target="_blank" href="https://codepen.io/paulknulst/pen/qBYNxxa">my CodePen</a> and start implementing more controls like <strong>volume control</strong>, <strong>keyboard shortcuts</strong>, or <strong>skip controls</strong> to build your own customized video player.</p>
<p>At the moment, I am working on a follow-up tutorial where I extend this code to add keyboard controls and add additional functionality. It will be published on <a target="_blank" href="https://www.paulsblog.dev/">my personal blog</a> and later on Dev.to/Medium.</p>
<p>Have any questions? No problem, just ask in the comments section. I will answer everything.</p>
<p>This article was published on my blog at <a target="_blank" href="https://www.paulsblog.dev/how-to-build-a-video-player-in-vanilla-javascript-and-html5/">https://www.paulsblog.dev/how-to-build-a-video-player-in-vanilla-javascript-and-html5/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my personal blog</a>, <a target="_blank" href="https://medium.knulst.de">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p><a target="_blank" href="https://www.freepik.com/free-vector/media-player-concept-illustration_9936448.htm#query=video%20player&amp;position=6&amp;from_view=search">Image by storyset</a> on Freepik</p>
]]></content:encoded></item><item><title><![CDATA[Optimize Android App Development With Docker, SonarQube, Detekt, and MobSF]]></title><description><![CDATA[Paul Knulst  in  Android App Development • 13 min read

As an Android app developer, you should always want to implement the best code that is possible. Also, your app should be as secure as possible.
Code quality is very important for any kind of so...]]></description><link>https://hashnode.knulst.de/optimize-android-app-development-with-docker-sonarqube-detekt-and-mobsf</link><guid isPermaLink="true">https://hashnode.knulst.de/optimize-android-app-development-with-docker-sonarqube-detekt-and-mobsf</guid><category><![CDATA[Devops]]></category><category><![CDATA[Android]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Security]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Tue, 16 May 2023 09:28:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1684228968846/af4213e3-3166-411d-8fc2-aa25b6d61504.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/author/paulknulst/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/android-app-development/">Android App Development</a> • 13 min read</p>
<hr />
<p>As an Android app developer, you should always want to implement the best code that is possible. Also, your app should be as secure as possible.</p>
<p>Code quality is very important for any kind of software and you should always try to optimize your implementation. This article shows and explains different quality tools that will be combined in an Android <strong>software-quality-chain</strong> to achieve better code quality, a more secure app, and improved maintainability.</p>
<p><strong>tl;dr:</strong> <a target="_blank" href="https://github.com/paulknulst/android-software-quality-chain?ref=paulsblog.dev"><em>Clone this GitHub repository</em></a> <em>into your Android project folder and execute the full chain with:</em> <code>sh</code> <a target="_blank" href="http://software-quality-chain.sh"><code>software-quality-chain.sh</code></a>. Afterward, open the URL that will be logged in to the console. Log in with admin:admin12345 and see your Android project report.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<h3 id="heading-docker">Docker</h3>
<p><strong>To run the script Docker needs to be installed on your operating system</strong>.</p>
<p>Docker is a widely used platform for developing, shipping, and running all kinds of applications. It enables you to separate infrastructure from your applications to quickly deliver software from one machine to another.</p>
<p>Implementing software while using Docker often negates infrastructure problems like the common "works on my machine" problem:</p>
<p><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7-Y0s7tJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://www.paulsblog.dev/content/images/2022/10/image.png" alt="Created with memegenerator - Disaster Kid" /></p>
<p>To install Docker on your system follow the <a target="_blank" href="https://docs.docker.com/get-docker/?ref=paulsblog.dev">official tutorial on</a> <a target="_blank" href="http://docker.com">docker.com</a>. If you are using Windows and <strong>aren't allowed to install Docker Desktop</strong> you can follow this guide:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/how-to-install-docker-without-docker-desktop-on-windows/">https://www.paulsblog.dev/how-to-install-docker-without-docker-desktop-on-windows/</a></p>
<h3 id="heading-detekt">Detekt</h3>
<p>Detekt is</p>
<blockquote>
<p><strong><em>a static code analysis tool for the Kotlin programming language. It operates on the abstract syntax tree provided by the Kotlin compiler.</em></strong></p>
</blockquote>
<p>It can be implemented in any Kotlin-based Android app and can be used with a self-defined set of rules to check an app. Detekt is a mandatory prerequisite to executing the script and has to be installed within your Android App. Fortunately, this procedure is very easy and can be done in four simple steps:</p>
<p><strong>1. Add Detekt to project</strong> <code>build.gradle</code></p>
<pre><code class="lang-plaintext">dependencies { 
    [...]
    classpath("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:1.17.0")
}
</code></pre>
<p><strong>2. Create a detekt.yml with a defined set of rules.</strong><br />Download a sample <a target="_blank" href="https://ftp.f1nalboss.de/data/sample-detekt.yml?ref=paulsblog.dev">gradle detek.yml file here</a> and store it within <code>project_folder/detekt/detekt.yml</code></p>
<p><strong>3. Apply the detekt plugin in app</strong> <code>build.gradle</code></p>
<pre><code class="lang-plaintext">apply plugin: "io.gitlab.arturbosch.detekt"
</code></pre>
<p><strong>4. Add detekt block to app</strong> <code>build.gradle</code></p>
<pre><code class="lang-plaintext">detekt {
    toolVersion = "1.17.0"
    buildUponDefaultConfig = false
    allRules = false
    config = files("../detekt/detekt.yml")
    baseline = file("../detekt/baseline.xml")
    input = files("src/main/java/com")
    debug = false
    reports {
        html {
            enabled = true
            destination = file("build/reports/detekt.html")
        }
        xml {
            enabled = true
            destination = file("build/reports/detekt.xml")
        }
        txt.enabled = false
        sarif.enabled = false
    }
</code></pre>
<h2 id="heading-other-tools-used">Other Tools Used</h2>
<h3 id="heading-mobsf">MobSF</h3>
<p>Mobile Security Framework (MobSF)</p>
<blockquote>
<p>is an automated, all-in-one mobile application (Android/iOS/Windows) pen-testing, malware analysis and security assessment framework capable of performing static and dynamic analysis.</p>
</blockquote>
<p>Before continuing, it is necessary to understand mobile security and how to create an android app securely. Luckily, <a target="_blank" href="https://owasp.org/?ref=paulsblog.dev">OWASP</a> (Open Web Application Security Project), a popular nonprofit organization that is known for community-led open-source software projects and for improving software security created the "OWASP Mobile Top Ten" to improve mobile security. These include the most critical security issues that should be avoided in any Android app:</p>
<ul>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m1-improper-platform-usage?ref=paulsblog.dev">M1: Improper Platform Usage</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m2-insecure-data-storage?ref=paulsblog.dev">M2: Insecure Data Storage</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m3-insecure-communication?ref=paulsblog.dev">M3: Insecure Communication</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m4-insecure-authentication?ref=paulsblog.dev">M4: Insecure Authentication</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m5-insufficient-cryptography?ref=paulsblog.dev">M5: Insufficient Cryptography</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m6-insecure-authorization?ref=paulsblog.dev">M6: Insecure Authorization</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m7-client-code-quality?ref=paulsblog.dev">M7: Client Code Quality</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m8-code-tampering?ref=paulsblog.dev">M8: Code Tampering</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m9-reverse-engineering?ref=paulsblog.dev">M9: Reverse Engineering</a></p>
</li>
<li><p><a target="_blank" href="https://owasp.org/www-project-mobile-top-10/2016-risks/m10-extraneous-functionality?ref=paulsblog.dev">M10: Extraneous Functionality</a></p>
</li>
</ul>
<p>Fortunately, MobSF can help you to identify many possible security issues in your Android app in an automated way. The security framework works best for:</p>
<ul>
<li><p>local environment</p>
</li>
<li><p>performing a quick security test</p>
</li>
<li><p>implemented in CI/CD with mobsfscan (will be used here)</p>
</li>
</ul>
<p>To test MobSF you can switch to <a target="_blank" href="https://mobsf.live">https://mobsf.live</a> and upload any Android APK that will then be analyzed for common security issues.</p>
<p><strong>My advice:</strong> all developers should use MobSF to identify many security vulnerabilities during development by doing a static code analysis. This analysis will check the source code without running the application. It can be used during mobile application development and should be carried out regularly. Normally, it should be executed before every app release, update and Pull Request submission.</p>
<p><strong>Keep in mind that using MobSF for static code analysis does not guarantee that your mobile application is safe! But it will help to identify the most obvious security flaws.</strong></p>
<h3 id="heading-sonarqube">SonarQube</h3>
<p>SonarQube</p>
<blockquote>
<p><strong><em>is an open-source platform developed by SonarSource for continuous inspection of code quality to perform automatic reviews with static analysis of code to detect bugs, code smells, and security vulnerabilities in 20+ programming languages.</em></strong></p>
</blockquote>
<p>For your projects, SonarQube analyzes source code (not only Android/Kotlin apps), evaluates the quality, and generates reports. It allows for the continuous monitoring of quality throughout time and combines static and dynamic analytic methods. SonarQube inspects and evaluates everything that has an impact on our code base, from inconspicuous styling decisions to serious design flaws.</p>
<p>As a result, developers can access and track code analysis information on everything from styling mistakes, potential bugs, and code defects to design inefficiencies, code duplication, inadequate test coverage, and excessive complexity. The Sonar platform examines source code from a variety of angles; as a result, it digs down to your code layer by layer, progressing from the module level to the class level. Additionally, it generates metrics and data at each level, highlighting problematic regions in the source that need to be examined or improved. It automatically detects what type of software you use: Kotlin-based Android app.</p>
<p><strong>Other Features:</strong><br />SonarQube does more than merely highlight issues. It also provides quality-management tools to proactively assist you in making corrections.</p>
<ul>
<li><p>SonarQube provides information on a dashboard on code standards, test coverage, duplications, API documentation, complexity, and architecture in addition to issues.</p>
</li>
<li><p>It provides you with a current snapshot of the quality of your Android app code as well as trends in trailing (what has previously gone wrong) and leading (what is most likely to go wrong in the future) quality indicators.</p>
</li>
<li><p>It offers measurements to guide you in making the best choice for your Android app.</p>
</li>
</ul>
<h3 id="heading-sonarscanner">SonarScanner</h3>
<p>The SonarScanner CLI is a scanner that can be used every time if there is no specific scanner for your build system. Additionally, the SonarScanner CLI can be run within a Docker container to test the Android app source code.</p>
<p>It is a simple command line tool that scans a provided folder (the Android app folder) with SonarQube-specific roles. After a successful analysis, it uploads all data to a SonarQube server where the results can be viewed.</p>
<h2 id="heading-prepare-dockerfiles">Prepare Dockerfiles</h2>
<p>Now, after explaining the prerequisites and tools they will be combined in Docker Compose files to be then used in a single bash script that will result in the <strong><em>software-quality-chain.</em></strong></p>
<h3 id="heading-sonarqubemobsfscan-docker-compose-file">SonarQube/mobsfscan Docker Compose File</h3>
<p>As described earlier SonarQube and mobsfscan will be used within the Android <strong>software-quality-chain</strong>. To be able to use them they will be deployed with Docker Compose as a service onto your operating system.</p>
<p>The following Compose file contains the SonarQube server, a PostgreSQL database for SonarQube, and the mobsfscan container to scan your Android app:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">sonarqube:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">sonarqube:9.6-community</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">sonarqube_db:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">SONAR_JDBC_URL:</span> <span class="hljs-string">jdbc:postgresql://sonarqube_db:5432/sonar</span>
      <span class="hljs-attr">SONAR_JDBC_USERNAME:</span> <span class="hljs-string">sonar</span>
      <span class="hljs-attr">SONAR_JDBC_PASSWORD:</span> <span class="hljs-string">sonar</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> <span class="hljs-string">wget</span> <span class="hljs-string">--no-verbose</span> <span class="hljs-string">--tries=1</span> <span class="hljs-string">--spider</span> <span class="hljs-string">http://localhost:9000/api/system/status</span> <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">interval:</span> <span class="hljs-string">30s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">10</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">sonarqube_data:/opt/sonarqube/data</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">sonarqube_extensions:/opt/sonarqube/extensions</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">sonarqube_logs:/opt/sonarqube/logs</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"9000:9000"</span>
  <span class="hljs-attr">sonarqube_db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">postgres:12</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">POSTGRES_USER:</span> <span class="hljs-string">sonar</span>
      <span class="hljs-attr">POSTGRES_PASSWORD:</span> <span class="hljs-string">sonar</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> [ <span class="hljs-string">"CMD-SHELL"</span>, <span class="hljs-string">"pg_isready"</span> ]
      <span class="hljs-attr">interval:</span> <span class="hljs-string">10s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">5</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgresql:/var/lib/postgresql</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">postgresql_data:/var/lib/postgresql/data</span>
  <span class="hljs-attr">mobsfscan:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">opensecurity/mobsfscan</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-attr">sonarqube:</span>
        <span class="hljs-attr">condition:</span> <span class="hljs-string">service_healthy</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./../app/src:/src</span>
    <span class="hljs-attr">entrypoint:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">mobsfscan</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--sonarqube</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">-o</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/src/result.json</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/src</span>
<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">sonarqube_data:</span>
  <span class="hljs-attr">sonarqube_extensions:</span>
  <span class="hljs-attr">sonarqube_logs:</span>
  <span class="hljs-attr">postgresql:</span>
  <span class="hljs-attr">postgresql_data:</span>
</code></pre>
<p>Within this file three services are defined: SonarQube, PostgreSQL, and mobsfscan. While SonarQube and PostgreSQL will just use a standardized Docker service that gets started, mobsfscan will have the project root bound as a volume and an overwritten entry point that executes instantly after the SonarQube container is started correctly and marked healthy (see healthcheck).</p>
<h3 id="heading-sonarscanner-compose-file">SonarScanner Compose File</h3>
<p>Luckily, SonarScanner utility is also available as Docker Image and can be used and configured in a Compose file:</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">sonarscanner:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">sonarsource/sonar-scanner-cli</span>
    <span class="hljs-attr">network_mode:</span> <span class="hljs-string">"host"</span>
    <span class="hljs-attr">env_file:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">.sq.env</span>
    <span class="hljs-attr">command:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">-Dsonar.projectKey=YourAppName</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">-Dsonar.exclusions=**/*.java</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">-Dsonar.externalIssuesReportPaths=./output/result.json</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">-Dsonar.kotlin.detekt.reportPaths=./app/build/reports/detekt.xml</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">"./../:/usr/src"</span>
</code></pre>
<p>As you can see in this file we define a projectKey (YourAppName), set some exclusions (comma-separated list), and also add additional reports to the execution of SonarScanner (relative path from <code>/usr/src</code>). Also, we add the Android project root as volume and map it to <code>/usr/src</code>.</p>
<p>Additionally, this Compose file needs an environment file that will contain the SonarQube URL and a User Token which will be used to upload the analysis:</p>
<pre><code class="lang-plaintext">SONAR_HOST_URL=http://localhost:9000
SONAR_LOGIN=squ_3d9c193d3699d18db4f539725090d67caef0b964
</code></pre>
<h2 id="heading-combining-the-tools">Combining the Tools</h2>
<p>Now, all Dockerfiles will be combined within the bash script called <strong><em>software-quality-chain</em></strong> which consists of these steps:</p>
<ol>
<li><p>Increasing the heap count of the Docker environment</p>
</li>
<li><p>Install, configure, and start SonarQube and mobsfscan</p>
</li>
<li><p>Waiting until mobsfscan finish the analysis</p>
</li>
<li><p>Running complete Gradle checks</p>
</li>
<li><p>Importing a clean PostgreSQL database.</p>
</li>
<li><p>Executing SonarScanner and combining results before uploading to SonarQube</p>
</li>
</ol>
<p>This bash script contains the final solution which will execute all steps:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Removing old output"</span>
rm -f ../output/detekt.xml
rm -f ../output/result.json

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Increasing heap count for SonarQube"</span>
sysctl -w vm.max_map_count=262144

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting SonarQube, mobsfscan"</span>
DOCKER_BUILDKIT=1 docker-compose -f docker-compose.sq.yml up -d

<span class="hljs-built_in">echo</span> <span class="hljs-string">"SonarQube is started, running mobsfscan"</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Waiting until scan is succesfully finished"</span>
until [ -f ../app/src/result.json ]
<span class="hljs-keyword">do</span>
     sleep 5
<span class="hljs-keyword">done</span>

mv ../app/src/result.json ../output/result.json

<span class="hljs-built_in">echo</span> <span class="hljs-string">"finished mobsfscan"</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"running gradle detekt"</span>
<span class="hljs-built_in">cd</span> ..
sed -i <span class="hljs-string">'s/baseline = file("..\/detekt\/baseline.xml")/\/\/baseline = file("..\/detekt\/baseline.xml")/g'</span> app/build.gradle
./gradlew detekt
sed -i <span class="hljs-string">'s/\/\/baseline = file("..\/detekt\/baseline.xml")/baseline = file("..\/detekt\/baseline.xml")/g'</span> app/build.gradle
<span class="hljs-built_in">cd</span> docker
cp ../app/build/reports/detekt.xml ../output/detekt.xml
<span class="hljs-built_in">echo</span> <span class="hljs-string">"finished gradle detekt"</span>

<span class="hljs-built_in">echo</span> <span class="hljs-string">"preparing files to import into SonarQube"</span>
sed -i <span class="hljs-string">'s/\/src\/main\//app\/src\/main\//g'</span> ../output/result.json
sed -i <span class="hljs-string">'s/"filePath": null/"filePath": "app\/src\/main\/AndroidManifest.xml"/g'</span> ../output/result.json

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Restoring SonarQube database with default user"</span>
docker <span class="hljs-built_in">exec</span> -i docker-sonarqube_db-1 //bin//bash -c <span class="hljs-string">"PGPASSWORD=sonar psql --username sonar sonar"</span> &lt; dump.sql

<span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting SonarScanner"</span>
DOCKER_BUILDKIT=1  docker-compose -f docker-compose.scan.yml up -d

<span class="hljs-built_in">echo</span> <span class="hljs-string">"SonarScanner started, will scan code now and upload data"</span>
docker logs -f docker-sonarscanner-1
</code></pre>
<h3 id="heading-increasing-the-heap-count-of-the-docker-environment">Increasing the heap count of the Docker environment:</h3>
<p>To execute the SonarQube server without any problems you have to increase the max_map_count of the operating system running the Docker container because the default value is not sufficient.</p>
<p>You can read about it in <a target="_blank" href="https://docs.sonarqube.org/latest/requirements/requirements/?ref=paulsblog.dev">the SonarQube documentation</a>.</p>
<h3 id="heading-install-configure-and-start-sonarqube-and-mobsfscan">Install, configure, and start SonarQube and mobsfscan:</h3>
<p>As the Docker Compose files already contain every important setting the only command that has to be executed here is running the Compose file in the background</p>
<h3 id="heading-waiting-until-mobsfscan-finish-the-analysis">Waiting until mobsfscan finish the analysis:</h3>
<p>Unfortunately, the Docker Compose service of mobsfscan is only started by running the Compose file and the analysis is executed afterward. Because of this, the script has to block until the mobsfscan analysis finishes and successfully creates a result file. This "blocking" is done in lines 16 - 21 where the script waits until the result.json is created and then moves it to the output folder.</p>
<h3 id="heading-running-complete-gradle-detekt-checks">Running complete Gradle Detekt checks:</h3>
<p>The next step is executing a complete gradle detekt task. It's called complete because sometimes developers use a baseline.xml file in their detekt configuration to mark legacy code as "ignored". Within this step, the baseline.xml file will be removed by commenting it out with sed and then running the gradle detekt task. Afterward, the detekt report will be copied to the output folder, and the usage of <code>baseline.xml</code> file will be restored.</p>
<h3 id="heading-preparing-files-to-import-into-sonarqube">Preparing files to import into SonarQube:</h3>
<p>There are two small bugs if combining mobsfscan utility and SonarQube while creating and importing the result.json.</p>
<ol>
<li><p>mobsfscan also finds issues in the project that is not suited to specific source files. Unfortunately, these issues cannot be imported correctly into SonarQube because SonarQube always needs a file to add an issue. Luckily, it is easy to identify these issues because they all have <code>filePath=null</code>.</p>
</li>
<li><p>mobsfscan lists all file paths of files from the app folder while SonarQube lists all files from the project folder.</p>
</li>
</ol>
<p>Both issues will be fixed in line 35 and 36 where <code>sed</code> is used to replace the wrong entries with the correct one:</p>
<ul>
<li><p><strong>Line 35:</strong> <code>src/main</code> -&gt; <code>app/src/main</code></p>
</li>
<li><p><strong>Line 36:</strong> <code>filePath=null</code> -&gt; <code>filePath="app/src/main/AndroidManifest.xml"</code></p>
</li>
</ul>
<h3 id="heading-importing-a-clean-postgresql-database">Importing a clean PostgreSQL database:</h3>
<p>Before starting the SonarScanner to analyze the source code and upload the results to the SonarQube instance a clean PostgreSQL database backup will be imported into the SonarQube instance (Line 39). This has to be done because the freshly installed SonarQube instance does not contain a User Token that can be used to upload data. The provided PostgreSQL backup contains a User (admin:admin12345) and a User Token that already is set within the SonarScanner environment variables.</p>
<p><em>Without this step, you would have to switch to your SonarQube instance and log in with the default user (admin:admin). Then you have to switch to your account configuration and create a new User Token that can be used for analysis.</em></p>
<h3 id="heading-executing-sonarscanner-and-combining-results-before-uploading-to-sonarqube">Executing SonarScanner and combining results before uploading to SonarQube:</h3>
<p>The last step will be executing the Compose file for the SonarScanner (Line 42) and opening the docker logs for this instance to be notified if the analysis is complete.</p>
<p>Afterward, you will get an URL within the terminal that you can open in the browser, log in with <strong><em>admin:admin12345,</em></strong> and see the results of the executed analysis.</p>
<h2 id="heading-run-the-chain">Run the Chain</h2>
<p>Now, that everything is set upped and explained you can start software-quality-chain. If you followed the tutorial and created the files you still need <a target="_blank" href="https://github.com/paulknulst/android-software-quality-chain/blob/master/docker/dump.sql?ref=paulsblog.dev">this PostgreSQL database dump</a> that you can save as <code>dump.sql</code> within the docker folder. If you did not manually create every file you can <a target="_blank" href="https://github.com/paulknulst/android-software-quality-chain?ref=paulsblog.dev">clone the complete GitHub repository</a> into your Android project root.</p>
<p>Then you can run it by executing:</p>
<pre><code class="lang-bash">sh software-quality-chain.sh
</code></pre>
<p>Grab a coffee and wait until the chain finishes. There will be an URL within the CLI that you can copy to open the SonarQube instance. Log in with <strong>admin:admin12345</strong> and start checking your improvements, security issues, and other stuff.</p>
<h2 id="heading-possible-errors">Possible Errors</h2>
<p>If you are using this software-quality-chain you maybe encounter some errors that can be fixed easily!</p>
<h3 id="heading-sonarqube-does-not-start">SonarQube does not start</h3>
<p>As told before, to run SonarQube you have to increase the max_map_count. Normally, this will be done by the command in Line 8. If you are working with Windows or do not have permission you have to update the command:</p>
<p><strong>For Windows:</strong></p>
<pre><code class="lang-plaintext">wsl sysctl -w vm.max_map_count=262144
</code></pre>
<p><strong>Not able because of permissions:</strong></p>
<pre><code class="lang-plaintext">sudo sysctl -w vm.max_map_count=262144
</code></pre>
<h3 id="heading-cannot-run-docker-compose-windows">Cannot run docker-compose (Windows)</h3>
<p>If you do not have Docker Desktop (or Rancher Desktop) it could happen that docker-compose cannot be executed from your terminal.</p>
<p>Sometimes it happens because you have to add wsl in front of every docker-compose command (c.f. Line 11):</p>
<pre><code class="lang-plaintext">DOCKER_BUILDKIT=1 wsl docker-compose -f docker-compose.sq.yml up -d
</code></pre>
<h3 id="heading-cannot-import-postgresql-backup-windows">Cannot import PostgreSQL backup (Windows)</h3>
<p>If you try to run the software-quality-chain within the Powershell or Git-bash while working with windows you normally will encounter an error while the script tries to restore the PostgreSQL database.</p>
<p>This happens because of a problem with the file encoding of the PostgreSQL dump that is used. To fix this problem connect to your WSL and restart the <strong>software-quality-chain</strong>. Alternatively, you can log into the running SonarQube instance with default user <strong>admin:admin</strong>, switch to accounts settings, and create a new user token. Then you can update the <code>sq.env</code> file to use this new user token. If you do this you have to remove the part from the <strong>software-quality-chain</strong> where the PostgreSQL backup will be inserted.</p>
<h2 id="heading-software-quality-chain-improvementschanges">Software-Quality-Chain Improvements/Changes</h2>
<p>As this chain is mainly suited for developers that want to run a quick full test for their android apps on their own computer, this chain could also be changed in some ways.</p>
<p>An important feature of the described chain is that it will run on ANY device running Docker because it automatically inserts a database while executing. This means that every time you run this chain you will have a freshly installed SonarQube that only contains results from the executed analysis.</p>
<p>But if you want to have a curve where you can see improvements while developing you have to persist the data from every consecutive analysis. Also, you can work with several members on the same SonarQube instance and use this chain within your CI/CD pipeline to automatically check after a Pull Request is merged into your release branch.</p>
<p>The following chapters cover changes that have to be made to use the chain in any of these ways.</p>
<h3 id="heading-persisting-analysis-data">Persisting Analysis Data</h3>
<p>To persist data between consecutive analyses you simply have to adjust the <strong>software-quality-chain</strong> after the first run.</p>
<p>After your first successful run of the chain switch to lines 38 and 39 where the database import takes place.</p>
<p>You still need the first run because it will populate the database to have a working user token and the user to log in <strong><em>admin</em></strong>:<strong><em>admin12345</em></strong>.</p>
<h3 id="heading-use-a-different-sonarqube-instance">Use a different SonarQube instance</h3>
<p>If you are using a different SonarQube instance within the <strong>software-quality-chain</strong> you have to adjust the two files:</p>
<p><strong>docker-compose.sq.yml</strong>: Remove the SonarQube and PostgreSQL container and only keep the mobsfscan. The Compose file will look like this:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">mobsfscan:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">opensecurity/mobsfscan</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">./../app/src:/src</span>
    <span class="hljs-attr">entrypoint:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">mobsfscan</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--sonarqube</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">-o</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/src/result.json</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/src</span>
</code></pre>
<p><strong>sq.env:</strong> Replace the URL with your SonarQube instance URL and update the Token to your user token</p>
<p>Also, remove the PostgreSQL database backup (Line 38/39).</p>
<h3 id="heading-use-it-in-cicd-chain">Use it in CI/CD chain</h3>
<p>To use this script in a CI/CD chain you have to combine previous sections. Remove the SonarQube instance by editing the Compose file, changing the URL/Token of <code>sq.env</code>, and removing the PostgreSQL backup.</p>
<h2 id="heading-additional-tools">Additional Tools</h2>
<p>To further improve the quality of Android apps it is possible to enhance your Android project with two additional extra tools/commands that work well in combination with the <strong>software-quality-chain</strong>.</p>
<h3 id="heading-sonarlint-plugin-for-android">SonarLint Plugin for Android</h3>
<p>The SonarLint plugin can be <a target="_blank" href="https://plugins.jetbrains.com/plugin/7973-sonarlint?ref=paulsblog.dev">downloaded and installed manually from the JetBrains marketplace</a> or you can easily install it from within Android Studio by opening Settings-&gt;Plugins and installing it.</p>
<h3 id="heading-pre-push-hook-for-executing-gradle-detekt">Pre-Push Hook For Executing Gradle Detekt</h3>
<p>To guarantee that all pushed files did not contain any error that will be found with Gradle you can install a pre-push hook into your .git folder. To do this open .git/hooks, create a new file called pre-push, make it executable (<code>chmod +x</code>), and paste the following content:</p>
<pre><code class="lang-bash"><span class="hljs-meta">#!/bin/bash</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting detekt check"</span>
./gradlew detekt
</code></pre>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>I hope you enjoyed reading this article and will now use my <strong>software-quality-chain</strong> to significantly <strong>improve your code quality</strong> and <strong>make your app more secure</strong>. Keep in mind that software that has high quality can be maintained and enhanced more easily!</p>
<p>All files that were explained in this tutorial can be found <a target="_blank" href="https://github.com/paulknulst/android-software-quality-chain?ref=paulsblog.dev">in this GitHub repository</a>. Download the whole folder, put it into your Android project root, and execute the <strong>software-quality-chain</strong> with:</p>
<pre><code class="lang-plaintext">sh software-quality-chain.sh
</code></pre>
<p>Keep in mind that on Windows you have to be connected to the WSL!</p>
<p>This is the end of this tutorial. Hopefully, you are now able to use my <strong>Android software-quality-chain</strong>. If you still have questions about anything that is not fully described you can just ask in the comment section. Also, if you enjoyed reading this article consider commenting with your valuable thoughts! I would love to hear your feedback about my developed chain.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/optimize-android-app-development-with-docker-sonarqube-detekt-and-mobsf/">https://www.paulsblog.dev/optimize-android-app-development-with-docker-sonarqube-detekt-and-mobsf/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@jamomca?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Jamison McAndie</a> / <a target="_blank" href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Use NestJS, MongoDB and Docker to Create an URL Shortener]]></title><description><![CDATA[Paul Knulst  in  Programming • Sep 6, 2022 • 10 min read

This tutorial will cover all work to build a simple URL shortener API with NestJS and MongoDB. Additionally, I will show how to deploy it to a (Docker and Docker Swarm) production environment ...]]></description><link>https://hashnode.knulst.de/use-nestjs-mongodb-and-docker-to-create-an-url-shortener</link><guid isPermaLink="true">https://hashnode.knulst.de/use-nestjs-mongodb-and-docker-to-create-an-url-shortener</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Mon, 08 May 2023 08:47:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683535574794/536c68f2-426e-48d4-99de-35adaa834667.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/author/paulknulst/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/programming/">Programming</a> • Sep 6, 2022 • 10 min read</p>
<hr />
<p>This tutorial will cover all work to build a simple URL shortener API with NestJS and MongoDB. Additionally, I will show how to deploy it to a (Docker and Docker Swarm) production environment using Docker.</p>
<p>The source code is published on GitHub and can be used freely:</p>
<p><a target="_blank" href="https://github.com/paulknulst/paulsshortener?ref=dev.to">https://github.com/paulknulst/paulsshortener</a></p>
<h2 id="heading-create-the-nestjs-project">Create the NestJS Project</h2>
<p>First, create a NestJS project which will work as a baseline for the URL shortener. To do this, you need to install the <a target="_blank" href="https://github.com/nestjs/nest-cli?ref=paulsblog.dev">Nest-CLI</a> on your system which can be done by:</p>
<pre><code class="lang-bash">npm install -g @nestjs/cli
</code></pre>
<p>Then switch to your projects folder and use the Nest-CLI to create a new NestJS project by executing:</p>
<pre><code class="lang-bash">nest new paulsshortener
</code></pre>
<p>During this process, you will be asked which package manager you want to use. Within this tutorial <code>npm</code> will be used.</p>
<hr />
<p>Now, you  can start the NestJS project in "<em>watch-mode</em>" to instantly see all changes you will make during this tutorial by executing:</p>
<pre><code>npm run start:dev
</code></pre><p>To test if everything is started correctly hit <a target="_blank" href="http://localhost:3000/">http://localhost:3000</a> which will just show "Hello World" within the browser.</p>
<p>Within the project open the AppService (<code>/src/app.service.ts</code>) and change <code>return 'Hello World!';</code> to <code>return 'This will be your URL shortener';</code>. After reloading http://localhost:3000 the updated response will be seen.</p>
<h2 id="heading-set-up-mongodb-database">Set Up MongoDB database</h2>
<p>For simplicity, MongoDB will be used during this tutorial. To avoid trouble setting up the correct version, replacing an old installation, and so on you should use Docker to deploy it. Save this minimal Compose file into your project root and name it <code>docker-compose.local.yml</code>:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.6'</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">mongo:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mongo</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MONGO_INITDB_ROOT_USERNAME:</span> <span class="hljs-string">root</span>
      <span class="hljs-attr">MONGO_INITDB_ROOT_PASSWORD:</span> <span class="hljs-string">supersafe</span>
</code></pre>
<p><em>If you are not familiar with Docker Compose and do not want to use it to set up a MongoDB you can have a look at <a target="_blank" href="https://www.mongodb.com/docs/manual/installation/?ref=paulsblog.dev">the official MongoDB documentation</a> to learn how you can install it on your machine. Keep in mind that Docker is also used to deploy this URL shortener later within this tutorial!</em></p>
<p>Now, to deploy a MongoDB, switch to your project root within a terminal and execute:</p>
<pre><code class="lang-bash">docker-compose -f docker-compose.local.yml up -d
</code></pre>
<p>Afterward, you have successfully set up MongoDB and your machine and can start using it.</p>
<h2 id="heading-connect-your-nestjs-project-to-mongodb">Connect Your NestJS project to MongoDB</h2>
<p>To connect the project to MongoDB you should use Mongoose which is the most popular MongoDB object modeling tool. Start by installing the required dependencies into your project:</p>
<pre><code class="lang-bash">npm i @nestjs/mongoose mongoose
</code></pre>
<p>Once you have installed the dependency you can import the Mongoose module into the project by editing the AppModule (<code>/src/app.module.ts</code>) that it looks like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Module } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> { AppController } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.controller'</span>;
<span class="hljs-keyword">import</span> { AppService } <span class="hljs-keyword">from</span> <span class="hljs-string">'./app.service'</span>;
<span class="hljs-keyword">import</span> { MongooseModule } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/mongoose'</span>;

@Module({
  <span class="hljs-attr">imports</span>: [MongooseModule.forRoot(<span class="hljs-string">'mongodb://localhost:12345/paulsshortener'</span>)],
  <span class="hljs-attr">controllers</span>: [AppController],
  <span class="hljs-attr">providers</span>: [AppService],
})

<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppModule</span> </span>{}
</code></pre>
<p>The connection will now be automatically established and you can create the schema that will be used to work with the database.</p>
<p>Now use the Nest-CLI to create a new module within your NestJS project which will handle everything related to URLs:</p>
<pre><code class="lang-bash">nest g res url
</code></pre>
<p>The routine will ask two questions that you should answer.</p>
<ul>
<li>What transport layer do you use? <strong>-&gt; REST API</strong></li>
<li>Would you like to generate CRUD entry points? <strong>-&gt; No</strong></li>
</ul>
<p>Switch to the newly generated <code>url</code> folder, create a new folder schema, and add a file <code>url.schema.ts</code>. Then create a class (<code>Url</code>) add two properties (<code>url</code>, <code>shortenedUrl</code>) to the file. Also, add exports for Document and Schema. Your file should look like this:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> { Prop, Schema, SchemaFactory } <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/mongoose'</span>;
<span class="hljs-keyword">import</span> { Document } <span class="hljs-keyword">from</span> <span class="hljs-string">'mongoose'</span>;

<span class="hljs-keyword">export</span> type UrlDocument = Url &amp; Document;

@Schema()
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Url</span> </span>{
  @Prop()
  <span class="hljs-attr">url</span>: string;

  @Prop()
  <span class="hljs-attr">shortenedUrl</span>: string;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> UrlSchema = SchemaFactory.createForClass(Url);
</code></pre>
<p>Open up <code>url.module.ts</code>, add two new imports, and modify the <code>@Module</code> annotation:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {Module} <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> {UrlService} <span class="hljs-keyword">from</span> <span class="hljs-string">'./url.service'</span>;
<span class="hljs-keyword">import</span> {UrlController} <span class="hljs-keyword">from</span> <span class="hljs-string">'./url.controller'</span>;
<span class="hljs-keyword">import</span> {Url, UrlSchema} <span class="hljs-keyword">from</span> <span class="hljs-string">"./schemas/url.schema"</span>;
<span class="hljs-keyword">import</span> {MongooseModule} <span class="hljs-keyword">from</span> <span class="hljs-string">"@nestjs/mongoose"</span>;

@Module({
    <span class="hljs-attr">imports</span>: [MongooseModule.forFeature([{<span class="hljs-attr">name</span>: Url.name, <span class="hljs-attr">schema</span>: UrlSchema}])],
    <span class="hljs-attr">controllers</span>: [UrlController],
    <span class="hljs-attr">providers</span>: [UrlService]
})
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UrlModule</span> </span>{
}
</code></pre>
<h2 id="heading-implement-the-url-link-shortner-function">Implement the URL Link Shortner Function</h2>
<p>To shrink an URL you will be going to use the CRC32 hash algorithm which has to be added to the project to use it:</p>
<pre><code class="lang-bash">$ npm install crc-32 --save
</code></pre>
<p>Open the UrlService (<code>/src/url/url.service.ts</code>) and replace the content of the file with the following snippet:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {Injectable} <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> {InjectModel} <span class="hljs-keyword">from</span> <span class="hljs-string">"@nestjs/mongoose"</span>;
<span class="hljs-keyword">import</span> {Url, UrlDocument} <span class="hljs-keyword">from</span> <span class="hljs-string">"./schemas/url.schema"</span>;
<span class="hljs-keyword">import</span> {Model} <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> CRC <span class="hljs-keyword">from</span> <span class="hljs-string">"crc-32"</span>;

@Injectable()
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UrlService</span> </span>{

    <span class="hljs-keyword">constructor</span>(@InjectModel(Url.name) private urlModel: Model&lt;UrlDocument&gt;) {
    }

    private shrink(url: string) {
        <span class="hljs-keyword">return</span> CRC.str(url).toString(<span class="hljs-number">16</span>)
    }

    <span class="hljs-keyword">async</span> create(url: string) {
        <span class="hljs-keyword">const</span> createdUrl = <span class="hljs-keyword">new</span> <span class="hljs-built_in">this</span>.urlModel({<span class="hljs-attr">url</span>: url, <span class="hljs-attr">shortenedUrl</span>: <span class="hljs-built_in">this</span>.shrink(url)});
        <span class="hljs-keyword">await</span> createdUrl.save();
        <span class="hljs-keyword">return</span> createdUrl.shortenedUrl;
    }

    <span class="hljs-keyword">async</span> find(shortenedUrl: string) { <span class="hljs-comment">//-3c666ac</span>
        <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.urlModel.findOne({<span class="hljs-attr">shortenedUrl</span>: shortenedUrl}).exec();
        <span class="hljs-keyword">return</span> url.url;
    }
}
</code></pre>
<p>This snippet contains three important functions:</p>
<ul>
<li><strong>shrink:</strong> Converts the given URL into an 8-character string by using the CRC32 algorithm. Then returns only the 8-character string.</li>
<li><strong>create:</strong> Use the shrink function to create an 8-character string and saves a new UrlSchema document into the MongoDB</li>
<li><strong>find:</strong> Retrieves the saved URL from the MongoDB.</li>
</ul>
<h2 id="heading-add-routes-to-nestjs-api">Add Routes to NestJS API</h2>
<p>To use the URL shortener in a Client we need to create three REST endpoints/functions.</p>
<ul>
<li><strong>GET /shrink:</strong> Create a new shortened URL using the HTTP Get method. The path parameter contains the unshortened URL. This can be done within any browser.</li>
<li><strong>POST /shrink:</strong> Create a new shortened URL using the HTTP Post method. The body contains the unshortened URL. You need an API or Postman to use this.</li>
<li><strong>GET  /s:</strong> Takes the 8-character string as a path parameter and returns the unshortened URL. In the end, it will automatically forward to the unshortened URL.</li>
</ul>
<p>As you are working with NestJS you can easily implement these endpoints by adding three new functions to the UrlController (<code>url.controller.ts</code>) within the URL module and annotate it with <code>@Get</code> and <code>@Post</code>. Open the UrlController and replace the content with:</p>
<pre><code class="lang-js"><span class="hljs-keyword">import</span> {Body, Controller, Get, Param, Post} <span class="hljs-keyword">from</span> <span class="hljs-string">'@nestjs/common'</span>;
<span class="hljs-keyword">import</span> {UrlService} <span class="hljs-keyword">from</span> <span class="hljs-string">'./url.service'</span>;

@Controller(<span class="hljs-string">''</span>)
<span class="hljs-keyword">export</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UrlController</span> </span>{
    <span class="hljs-keyword">constructor</span>(private readonly urlService: UrlService) {
    }

    @Get(<span class="hljs-string">'/shrink/:url'</span>)
    getShrink(@Param(<span class="hljs-string">'url'</span>) url: string) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.urlService.create(url)
    }

    @Post(<span class="hljs-string">'/shrink'</span>)
    postShrink(@Body() body: { <span class="hljs-attr">url</span>: string }) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.urlService.create(body.url)
    }

    @Get(<span class="hljs-string">'/s/:shortenedUrl'</span>)
    unshrink(@Param(<span class="hljs-string">'shortenedUrl'</span>) shortenedUrl: string) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">this</span>.urlService.find(shortenedUrl)
    }
}
</code></pre>
<p>Within this snippet, two new GET resources (<strong>/shrink/</strong> and <strong>/s/</strong>) and one POST resource that calls the previously created functions from the UrlService are created. Also, the annotation above the class is changed from <code>@Controller('url')</code>  to <code>@Controller('')</code> to further shrink the resulting URL.</p>
<h2 id="heading-testing-the-url-shortener">Testing the URL shortener</h2>
<p>To test the functionality you can simply use your browser because we have implemented both resources as GET calls.</p>
<p>Keep in mind that as you use GET for providing an URL as a path parameter <strong>you have to encode the URL</strong>! This means that an URL like this:</p>
<pre><code>https:<span class="hljs-comment">//www.paulsblog.dev/manage-time-more-efficiently-with-the-pomodoro-technique/</span>
</code></pre><p>will become this:</p>
<pre><code>https%<span class="hljs-number">3</span>A%<span class="hljs-number">2</span>F%<span class="hljs-number">2</span>Fwww.paulsblog.dev%<span class="hljs-number">2</span>Fmanage-time-more-efficiently-<span class="hljs-keyword">with</span>-the-pomodoro-technique%<span class="hljs-number">2</span>F
</code></pre><p>With this information, you can create a shortened URL by opening the following URL in our browser:</p>
<p><a target="_blank" href="http://localhost:3000/shrink/https%3A%2F%2Fwww.paulsblog.dev%2Fmanage-time-more-efficiently-with-the-pomodoro-technique%2F">http://localhost:3000/shrink/https%3A%2F%2Fwww.paulsblog.dev%2Fmanage-time-more-efficiently-with-the-pomodoro-technique%2F</a></p>
<p>With this GET call your API will return the 8-character string: <strong>-5f1a8349</strong> (It should be the same within your project)</p>
<p>Append this string to your GET /s/ call to receive the unshortened version of the URL by opening: <a target="_blank" href="http://localhost:3000/s/-5f1a8349">http://localhost:3000/s/-5f1a8349</a></p>
<p><strong>The result in the browser will be the unshortened version of the previously provided URL.</strong></p>
<h2 id="heading-implement-forwarding-to-unshortened-url">Implement Forwarding to Unshortened URL</h2>
<p>Now, that you have developed an API that can shorten URLs with help of the CRC-32 algorithm you should enable the functionality to forward the request and automatically open the URL it finds within the database.</p>
<p>With NestJS this is easy because you only have to adjust the unshrink function within the UrlController (<code>src/url/url.controller.ts</code>). Change the previously created function to this implementation:</p>
<pre><code class="lang-js">@Get(<span class="hljs-string">'/s/:shortenedUrl'</span>)
<span class="hljs-keyword">async</span> unshrink(@Res() res, @Param(<span class="hljs-string">'shortenedUrl'</span>) shortenedUrl: string) {
    <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">await</span> <span class="hljs-built_in">this</span>.urlService.find(shortenedUrl)
    <span class="hljs-keyword">return</span> res.redirect(url)
}
</code></pre>
<h2 id="heading-deploy-the-url-shortener-with-docker">Deploy The URL Shortener With Docker</h2>
<p>Let's assume you want to deploy the URL shortener in a Docker environment and have to develop a Compose file that can be used to do this.</p>
<p>The first step to do will be to create a new Compose file (<code>docker-compose.prod.yml</code>) and copy the content from the previously created MongoDB file into it. Then add a new service called backend which will be used to install, compile and run the NestJS project within the Docker environment.</p>
<p>As you have developed a custom piece of software there will not be a suitable image on DockerHub and you have to create one from scratch. Because the project is based on NestJS, which is working in a NodeJS environment, you can create a new Dockerfile and use the latest version of <code>node</code> as a base image. Then simply copy the source code, install, build and run the project. The following Dockerfile will be sufficient and should be created in the project root:</p>
<pre><code class="lang-dockerfile">FROM node:latest

WORKDIR /usr/src/app
COPY package*.json ./

RUN npm install
COPY . .
RUN npm run build

CMD [ "node", "dist/main.js" ]
</code></pre>
<p>This Dockerfile can now be used as the image for the backend service in the Compose file.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.6'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mongo</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MONGODB_USER:</span> <span class="hljs-string">paul</span>
      <span class="hljs-attr">MONGODB_DATABASE:</span> <span class="hljs-string">paulsshortener</span>
      <span class="hljs-attr">MONGODB_PASS:</span> <span class="hljs-string">paulspw</span>
  <span class="hljs-attr">backend:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">paulsshortener</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">3001</span><span class="hljs-string">:3000</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
</code></pre>
<p>Unfortunately, running this Compose file will not work because the URL for the links within our project are hard-coded and will not automatically adjust. Also, the DB hostname and port within the AppModule (<code>app.module.ts</code>) are static.</p>
<p>To fix these problems, you have to add and change something within the AppModule (<code>app.module.ts</code>) and the UrlService (<code>url.service.ts</code>). To be specific, add three new variables that hold the database port, the database URL, and the base URL of the resulting service.</p>
<p>Switch to the AppModule (<code>app.module.ts</code>) and add these two variables above the class definition:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> DB_HOST = process.env.DB_HOST || <span class="hljs-string">'localhost'</span>
<span class="hljs-keyword">const</span> DB_PORT = process.env.DB_PORT || <span class="hljs-string">'12345'</span>
</code></pre>
<p>Additionally, change the Mongoose part within the imports from the module to correctly use these variables:</p>
<pre><code class="lang-js">MongooseModule.forRoot(<span class="hljs-string">'mongodb://'</span> + DB_HOST + <span class="hljs-string">':'</span> + DB_PORT + <span class="hljs-string">'/paulsshortener'</span>)
</code></pre>
<p>Then open the UrlService (<code>url.service.ts</code>) and add the <code>baseurl</code> variable above the class definition:</p>
<pre><code class="lang-js"><span class="hljs-keyword">const</span> basepath = process.env.BASE_URL || <span class="hljs-string">'http://localhost:3000/'</span>
</code></pre>
<p>Lastly, change the create function to return the complete shortened URL using the newly introduced base variable.</p>
<pre><code class="lang-js"><span class="hljs-keyword">async</span> create(url: string) {
    <span class="hljs-keyword">const</span> createdUrl = <span class="hljs-keyword">new</span> <span class="hljs-built_in">this</span>.urlModel({<span class="hljs-attr">url</span>: url, <span class="hljs-attr">shortenedUrl</span>: <span class="hljs-built_in">this</span>.shrink(url)});
    <span class="hljs-keyword">await</span> createdUrl.save();
    <span class="hljs-keyword">return</span> basepath + <span class="hljs-string">"s/"</span> + createdUrl.shortenedUrl;
}
</code></pre>
<hr />
<p>Now, that you applied these changes you should adjust your Compose file by adding the available environment variables and adjusting them to your needs:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.6'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mongo</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MONGODB_USER:</span> <span class="hljs-string">paul</span>
      <span class="hljs-attr">MONGODB_DATABASE:</span> <span class="hljs-string">paulsshortener</span>
      <span class="hljs-attr">MONGODB_PASS:</span> <span class="hljs-string">paulspw</span>
  <span class="hljs-attr">backend:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">paulsshortener</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">BASE_URL=https://locahost:3001</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=db</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=27017</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
    <span class="hljs-attr">ports:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-number">3001</span><span class="hljs-string">:3000</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
</code></pre>
<p>Finally, deploy it on your localhost:</p>
<pre><code class="lang-bash">docker-compose up -d --build
</code></pre>
<p><strong>Your URL shortener is now correctly installed and can be used from your local environment.</strong></p>
<h2 id="heading-deploy-to-production-using-traefik">Deploy to Production Using Traefik</h2>
<p>Now that you have a running Docker Service that can be deployed anywhere you can use it to deploy it in a production environment using Traefik. To do this you will edit the Compose file, add all Traefik-related keywords (labels, networks), and adjust it to your needs.</p>
<p>If you are not familiar with deploying Docker Services using Traefik I can recommend the following tutorials covering basic installation on a single server and a server cluster installation using Docker Swarm:</p>
<ul>
<li><strong><a target="_blank" href="https://levelup.gitconnected.com/how-to-setup-traefik-v2-with-automatic-lets-encrypt-certificate-resolver-83de0ed0f542?ref=dev.to">How to setup Traefik v2 with automatic Let’s Encrypt certificate resolver</a></strong></li>
<li><strong><a target="_blank" href="https://betterprogramming.pub/deploy-any-ssl-secured-website-with-docker-and-traefik-27fbeb1343d3?ref=dev.to">Deploy Any SSL Secured Website With Docker And Traefik</a></strong></li>
<li><strong><a target="_blank" href="https://levelup.gitconnected.com/docker-swarm-in-a-nutshell-ed2a9c42cd7c?ref=dev.to">Setup Docker Swarm (For Traefik)</a></strong></li>
<li><strong><a target="_blank" href="https://levelup.gitconnected.com/the-most-important-services-everyone-should-deploy-in-a-docker-swarm-8e120b5a66?ref=dev.to">Install Traefik On Docker Swarm</a></strong></li>
</ul>
<p>To deploy your URL shortener using Traefik adjust the labels section. The following part will show and explain how it is done in a single server setup and additionally there will be a Docker Swarm configuration to download:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.6'</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">db:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">mongo</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">MONGODB_USER:</span> <span class="hljs-string">paul</span>
      <span class="hljs-attr">MONGODB_DATABASE:</span> <span class="hljs-string">paulsshortener</span>
      <span class="hljs-attr">MONGODB_PASS:</span> <span class="hljs-string">paulspw</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db:/data/db</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>
  <span class="hljs-attr">backend:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">paulsshortener</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">BASE_URL=https://at0m.de/</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_HOST=db</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">DB_PORT=27017</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">default</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik-public</span>
    <span class="hljs-attr">depends_on:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db</span>
    <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>
    <span class="hljs-attr">labels:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.enable=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.docker.network=traefik-public</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.constraint-label=traefik-public</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.pauls-shortener-http.rule=Host(`at0m.de`)</span> <span class="hljs-string">||</span> <span class="hljs-string">Host(`www.at0m.de`)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.pauls-shortener-http.entrypoints=http</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.pauls-shortener-http.middlewares=https-redirect</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.pauls-shortener-https.rule=Host(`at0m.de`)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.pauls-shortener-https.entrypoints=https</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.pauls-shortener-https.tls=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.pauls-shortener-https.tls.certresolver=le</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.services.pauls-shortener.loadbalancer.server.port=3000</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-pauls-shortener.redirectregex.regex=^https://www.at0m.de/(.*)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-pauls-shortener.redirectregex.replacement=https://at0m.de/$${1}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-pauls-shortener.redirectregex.permanent=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-https.middlewares=redirect-pauls-shortener</span>
<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">db:</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">traefik-public:</span>
    <span class="hljs-attr">external:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>Before you can successfully deploy this service with Compose you have to adjust the Host: <strong>Use your own BASE_URL</strong> and update the traefik configuration (For me, it is <a target="_blank" href="https://at0m.de/?ref=dev.to">at0m.de</a>). After changing both values deploy it with:</p>
<pre><code class="lang-bash">docker-compose -f docker-compose.prod.yml up -d
</code></pre>
<hr />
<p>Using a Docker Swarm you can use <a target="_blank" href="https://raw.githubusercontent.com/paulknulst/paulsshortener/master/docker-compose.prod-swarm.yml">this Compose file</a>. But, you have to adjust BASE_URL <strong>and</strong> the placement constraints for the MongoDB service. Then <strong>build</strong>, <strong>push</strong> the image to our registry, and <strong>deploy</strong> it onto your Docker Swarm:</p>
<pre><code class="lang-bash">docker-compose -f docker-compose.prod-swarm.yml build
docker-compose -f docker-compose.prod-swarm.yml push
docker stack deploy -c docker-compose.prod-swarm.yml paulsshortener
</code></pre>
<h2 id="heading-additional-adjustments-for-live-version">Additional Adjustments for Live Version</h2>
<p>Because you deployed it publicly on your server you should add rate limiting by following this approach: <a target="_blank" href="https://docs.nestjs.com/security/rate-limiting?ref=paulsblog.dev">https://docs.nestjs.com/security/rate-limiting</a>.</p>
<h3 id="heading-tldr">tl;dr:</h3>
<p>Install needed package within the project:</p>
<pre><code class="lang-bash">npm i --save @nestjs/throttler
</code></pre>
<p>In AppModule (<code>app.module.ts</code>) extend imports-array:</p>
<pre><code class="lang-js">ThrottlerModule.forRoot({
    <span class="hljs-attr">ttl</span>: <span class="hljs-number">60</span>,
    <span class="hljs-attr">limit</span>: <span class="hljs-number">10</span>,
})
</code></pre>
<p>Then, add ThrottlerGuard to the providers array:</p>
<pre><code class="lang-js">{
    <span class="hljs-attr">provide</span>: APP_GUARD,
    <span class="hljs-attr">useClass</span>: ThrottlerGuard,
}
</code></pre>
<p>Add the SkipThrottle annotation to the Get /s/ endpoint within the UrlController (<code>url.controller.ts</code>) to ignore rate limiting this specific call:</p>
<pre><code>@SkipThrottle()
</code></pre><p><strong>Finally</strong>, redeploy your Docker service. <strong>Don't forget to rebuild before deploying!</strong></p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>I hope you enjoyed reading my tutorial and are now able to create, build, and deploy your URL shortener website within a Docker container.</p>
<p>Keep in mind that this is a very basic example without any error handling, and no URL checking. However, this tutorial should be a starting point for developing your version.</p>
<p>If you enjoyed reading this article consider commenting your valuable thoughts in the comments section. I would love to hear your feedback about this URL shortener. Furthermore, if you have any questions about implementing your own version, please jot them down below. I try to answer them if possible. Also, share this article with your friends and colleagues to show them how to use NestJs, MongoDB, and Docker to create their own URL shortener.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/use-nestjs-mongodb-and-docker-to-create-an-url-shortener/">https://www.paulsblog.dev/use-nestjs-mongodb-and-docker-to-create-an-url-shortener/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/free">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p><a target="_blank" href="https://www.freepik.com/free-vector/man-woman-with-chains-background_1086772.htm#query=chain&amp;from_query=small%20chain&amp;position=30&amp;from_view=search">Image by Vectorarte</a> on Freepik</p>
]]></content:encoded></item><item><title><![CDATA[How To Remove Local Git Branches By Creating Git Aliases]]></title><description><![CDATA[After optimizing your Git workflow and starting working with Pull Requests you will probably have a ton of local branches that all have been merged already and could be deleted.
Within this short tutorial, I will show how you can easily do this and h...]]></description><link>https://hashnode.knulst.de/how-to-remove-local-git-branches-by-creating-git-aliases</link><guid isPermaLink="true">https://hashnode.knulst.de/how-to-remove-local-git-branches-by-creating-git-aliases</guid><category><![CDATA[Git]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Wed, 19 Apr 2023 05:57:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1681883796226/a543ed0d-cb5d-44ff-800f-def6d966b8be.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After optimizing your Git workflow and starting working with Pull Requests you will probably have a ton of local branches that all have been merged already and could be deleted.</p>
<p>Within this short tutorial, I will show how you can easily do this and how you can create a Git alias that you can simply reuse everywhere.</p>
<h2 id="heading-remove-a-single-local-branch">Remove A Single Local Branch</h2>
<p>To delete a single local branch you can open your favorite Git GUI interface and just press delete on the selected branch.
However, you cannot call yourself a developer if you are not able to use the terminal correctly!
Switch to your favorite terminal and use the following command to delete a branch:</p>
<pre><code>git branch -d YOUR_BRANCH
</code></pre><p>Unfortunately (or luckily), this command will only work if the selected branch that you want to delete is already merged. However, if you want to delete a branch that is not merged you can use the capital D:</p>
<pre><code>git branch -D YOUR_BRANCH
</code></pre><h2 id="heading-remove-all-local-branches">Remove All Local Branches</h2>
<p>When You have multiple local branches, you probably want to delete them all with one command instead of executing the delete command for every single branch.
To implement a function that does this it is important to know that git branch -D can handle several files at once.
With this in mind, we first write a command that finds all branches in your repository:</p>
<pre><code>git branch | grep -v \*
</code></pre><p>If you execute this command in your CLI you will see that also your main and/or protected branches are included: master, main, release, ...
You can adjust the find command to your needs (for me it's main/master/develop):</p>
<pre><code>git branch | grep -v <span class="hljs-string">"main\|master\|develop"</span>
</code></pre><p>Furthermore, you can add the --merged flag to indicate that you only want to find branches that are already merged</p>
<pre><code>git branch --merged | grep -v <span class="hljs-string">"main\|master\|develop"</span>
</code></pre><p>Now, you can pipe the result to the delete command introduced in the previous chapter:</p>
<pre><code>git branch --merged | grep -v <span class="hljs-string">"main\|master\|develop"</span> | xargs git branch -D
</code></pre><p>Executing this command in any Git repository (where main/master is the protected branch) will delete every branch that is already merged and have no local changes. You can also use -d to abort if you forgot <code>--merged</code>.
<strong>IMPORTANT:</strong>
If you execute this script while you are on a branch that has changed it does not work correctly because there will be the following error:</p>
<pre><code>error: branch <span class="hljs-string">'*'</span> not found.
</code></pre><p>To avoid this issue you can adjust the command by adding a <code>\|*</code> in the end:</p>
<pre><code>git branch --merged | grep -v <span class="hljs-string">"main\|master\|develop\*"</span> | xargs git branch -D
</code></pre><h2 id="heading-create-a-git-alias">Create A Git Alias</h2>
<h3 id="heading-simple-git-aliases">Simple Git Aliases</h3>
<p>Git Aliases are a feature that improves your Git experience by making your workflow simple and easy. It packs commands into an alias so you can use it wherever you want. 
Important aliases that you maybe already used:</p>
<ul>
<li>co for checkout</li>
<li>ci for commit
To set up these aliases you can execute:<pre><code>git config --<span class="hljs-built_in">global</span> alias.co checkout
git config --<span class="hljs-built_in">global</span> alias.ci commit
</code></pre></li>
</ul>
<p>Unfortunately, in simple Git aliases, it is not possible to use the pipe operator correctly and forward the results of a command to another one.
<strong>Luckily, Git lets you shell out in aliases!</strong></p>
<h3 id="heading-complex-git-aliases">Complex Git Aliases</h3>
<p>By using the ! (bang) operator your alias can escape to a shell and you are welcome to a new world of possibilities for all your aliases. With this technique you can use:</p>
<ul>
<li>Shell parameters and  expansions</li>
<li>Multiple git commands</li>
<li>Greps, Pipes, and all Unix command-line tools that you have installed</li>
</ul>
<p>To create a new Git alias you can use this template and add your own command:</p>
<pre><code>fancy_alias = <span class="hljs-string">"!f() { (your complex commands here) }; f"</span>
</code></pre><p>Within this alias, you use a simple trick by wrapping the actual git command in an anonymous bash-function (or better a function named <code>f()</code>). When you follow this approach you can access the command line variables and shell expansions as the following:</p>
<ul>
<li>$1, $2, $3 for the first, second, and third parameters passed to the Git alias</li>
<li>Chain git commands with &amp;&amp;</li>
<li>Pipe results with | </li>
<li>use the entire Unix toolkit
To demonstrate the functionality of Complex Git Alias You can have a look at the following 2 examples </li>
</ul>
<h4 id="heading-1-show-commits-since-your-last-git-command">1. Show commits since your last Git command</h4>
<pre><code>git config --<span class="hljs-built_in">global</span> alias.new = !sh -c <span class="hljs-string">'git log $1@{1}..$1@{0} "$@"'</span>
</code></pre><p>Can be used by executing <code>git new</code> and providing the HEAD</p>
<pre><code>git <span class="hljs-keyword">new</span> HEAD
</code></pre><h4 id="heading-2-add-a-remote-to-your-local-repository">2. Add a remote to your local repository</h4>
<pre><code>git config --<span class="hljs-built_in">global</span> alias.ra = <span class="hljs-string">"!f() { git remote add $1 $2; }; f"</span>
</code></pre><p>Can be used by executing git ra and adding the remote name and remote URL:</p>
<pre><code>git ra cool_remote git@github.com:paulknulst/nestjs-starter.git
</code></pre><h2 id="heading-create-an-alias-to-delete-branches">Create An Alias To Delete Branches</h2>
<p>Now, that you know how to create an extended Git alias we can use the previous information and wrap the command with an anonymous bash function called <code>f()</code>:</p>
<pre><code><span class="hljs-string">'!f() { git branch --merged | grep -v "main\|master\|develop\|*" | xargs git branch -D; }; f'</span>
</code></pre><p>Afterward, you can use the  git config command to create a global Git alias for your current logged-in user:</p>
<pre><code>git config --<span class="hljs-built_in">global</span> alias.clean-branches <span class="hljs-string">'!f() { git branch --merged | grep -v "main\|master\|develop\|*" | xargs git branch -D; }; f'</span>
</code></pre><h2 id="heading-conclusion">Conclusion</h2>
<p>You can have multiple local branches that you forget to delete instantly after closing a pull request. Maybe the remote branch is already deleted but your local folder keeps growing every day. Hopefully, you now know a technique to fix this problem and can use it in the CLI to completely remove every local branch that is already been merged.</p>
<p>Furthermore, I hope you learned an excellent way to use Git Aliases and Complex Git Alias with the ! (bang) operator to optimize your Git workflow.</p>
<p>If you enjoyed reading this article consider commenting your valuable thoughts in the comments section. I would love to hear your feedback about my tutorial. Furthermore, share this article with fellow developers to also optimize their Git workflow.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/how-to-remove-local-git-branches-by-creating-git-aliases/">https://www.paulsblog.dev/how-to-remove-local-git-branches-by-creating-git-aliases/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/free">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@ujesh">Ujesh Krishnan</a> / Unsplash</p>
]]></content:encoded></item><item><title><![CDATA[How To Host Your Website For Free]]></title><description><![CDATA[Paul Knulst  in  Programming • Jun 24, 2022 • 3 min read
If you are a student or a professional, you typically work on some projects that you create in your free time for fun, for learning something new, or for your portfolio. Often, you also want th...]]></description><link>https://hashnode.knulst.de/how-to-host-your-website-for-free</link><guid isPermaLink="true">https://hashnode.knulst.de/how-to-host-your-website-for-free</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[hosting]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Fri, 24 Mar 2023 12:52:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679662304983/dec30e28-2d7f-456e-94a8-7474989c9084.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/programming/">Programming</a> • Jun 24, 2022 • 3 min read</p>
<p>If you are a student or a professional, you typically work on some projects that you create in your free time for fun, for learning something new, or for your portfolio. Often, you also want that the projects could be shown to your fellow developers so that they can benefit from them. But only showing the code on GitHub is not sufficient because you want to show a running version that can be tested out by everyone before downloading your source code.</p>
<p>Luckily, today exist multiple services that you can use to show your work to the public. Although there are some paid options like AWS, Azure, and GCP (if you are out of the free tier), I will introduce some services that you can use without spending any money on them.</p>
<h3 id="heading-1-github-pageshttpspagesgithubcomrefpaulsblogdev"><a target="_blank" href="https://pages.github.com/?ref=paulsblog.dev">1. Github Pages⁣</a></h3>
<p>GitHub Pages is a feature from GitHub that enables every user or company to host a public webpage directly from your GitHub repository. You can edit, push, and your changes will be live.</p>
<h4 id="heading-pros-of-this-solution">Pros of this solution:</h4>
<ul>
<li>Widely used</li>
<li>Free hosting</li>
<li>Fast and easy to setup</li>
<li>Great customer service</li>
<li>Great documentation</li>
<li>Security and reliable</li>
</ul>
<h4 id="heading-cons-of-this-solution">Cons of this solution:</h4>
<ul>
<li>GitHub pages are limited to static websites</li>
<li>Special measures should be done for SEO</li>
<li>No support for any problem. You have to rely on forums and documentation</li>
</ul>
<h3 id="heading-2-netlifyhttpswwwnetlifycomrefpaulsblogdev"><a target="_blank" href="https://www.netlify.com/?ref=paulsblog.dev">2.  Netlify</a></h3>
<p>Netlify is an American cloud computing company that offers hosting and serverless backend services for web applications and static websites. Normally, you have to pay for their services but they provide <strong>a free plan for personal websites</strong>. You can build, deploy, and host any static website or app with a drag and drop interface. Also, it can automatically deploy from GitHub or Bitbucket.</p>
<h4 id="heading-pros-of-this-solution-1">Pros of this solution:</h4>
<ul>
<li>Provides continuous integration and continuous deployment</li>
<li>Drag and drop functionality is intuitive</li>
<li>Customer support service is good</li>
<li>Easy deploy service and use</li>
<li>Support Custom domains</li>
<li>SSL support</li>
<li>Ability to use plugins to customize build workflow</li>
</ul>
<h4 id="heading-cons-of-this-solution-1">Cons of this solution:</h4>
<ul>
<li>Dynamic content cannot be displayed</li>
<li>Supports only static websites</li>
</ul>
<h3 id="heading-3-vercelhttpsvercelcomrefpaulsblogdev"><a target="_blank" href="https://vercel.com/?ref=paulsblog.dev">3. Vercel</a></h3>
<p>A cloud platform for serverless deployment. It enables developers to host websites and web services that deploy instantly, scale automatically, and require no supervision, all with minimal configuration. Vercel is a tool in the <strong>Static Web Hosting</strong> category of a tech stack.</p>
<h4 id="heading-pros-of-this-solution-2">Pros of this solution:</h4>
<ul>
<li>Simple deployment</li>
<li>Easy, production-ready deployment with one command.</li>
<li>Simple scaling, including auto-scaling</li>
<li>SSR (Server-Side Rendering) support</li>
<li>Free tier</li>
<li>Free SSL</li>
</ul>
<h4 id="heading-cons-of-this-solution-2">Cons of this solution:</h4>
<ul>
<li>The platform is a bit confusing</li>
<li>Lack of guides and manuals</li>
</ul>
<h3 id="heading-4-herokuhttpswwwherokucomrefpaulsblogdev"><a target="_blank" href="https://www.heroku.com/?ref=paulsblog.dev">4. Heroku</a></h3>
<p>Heroku is a cloud platform with a free tier that allows up to 1000 dyno hours per month and includes deployment from Git and Docker. Normally, it is used to build, deliver, monitor, and scale all kinds of apps in different languages/frameworks: Node.js, Ruby, Python, Java, and much more.</p>
<p>Furthermore, with Heroku, you are allowed to use custom domains, container orchestration, and automatic OS patching.</p>
<h4 id="heading-pros-of-this-solution-3">Pros of this solution:</h4>
<ul>
<li>Deployments with Git and Docker</li>
<li>Easy to start</li>
<li>Custom domains</li>
<li>Free Tier</li>
<li>Container orchestration</li>
<li>Automatic OS patching</li>
</ul>
<h4 id="heading-cons-of-this-solution-3">Cons of this solution:</h4>
<ul>
<li>Cannot control exact configuration of applications</li>
<li>Could be too overwhelming</li>
<li>No SSL</li>
<li>Could stop working/has some downtime</li>
</ul>
<h3 id="heading-5-firebasehttpsfirebasegooglecomrefpaulsblogdev"><a target="_blank" href="https://firebase.google.com/?ref=paulsblog.dev">5. Firebase</a></h3>
<p>Firebase from Google is primarily an app development platform that could be used as a backend for apps and games. It provides several features like Authentication, Remote configuration, A/B testing, and analytics out of the box within the free tier (and more).</p>
<h3 id="heading-pros-of-this-solution-4">Pros of this solution:</h3>
<ul>
<li>Authentication</li>
<li>Analytics</li>
<li>Test Lab</li>
<li>Free Tier</li>
<li>Free SSL</li>
</ul>
<h3 id="heading-cons-of-this-solution-4">Cons of this solution:</h3>
<ul>
<li>Limitations of real-time database</li>
<li>More complicated</li>
</ul>
<h3 id="heading-closing-notes">Closing Notes</h3>
<p>All of the mentioned services could be easily used to host your private project. Although some of them have more features than others you could pick the best service for a specific project. Furthermore, you can use every service if you have multiple projects that you want to show.</p>
<p>Use them wisely to spread your work to the public crowd!</p>
<p>If you enjoyed reading this article consider commenting your valuable thoughts in the comments section. I would love to hear your feedback about my tutorial. Furthermore, share this article with fellow developers to help them get hosting their own services for free!</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/how-to-host-your-website-for-free/">https://www.paulsblog.dev/how-to-host-your-website-for-free/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/free">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p><a target="_blank" href="https://www.freepik.com/free-photo/young-engineer-is-showing-rock-gestures-with-hands-by-sitting-front-tablet-blue-background_26714573.htm#query=surprised%20developer&amp;position=23&amp;from_view=search&amp;track=ais">Image by 8photo</a> on Freepik</p>
]]></content:encoded></item><item><title><![CDATA[Increase Your Productivity With The Ivy-Lee Method]]></title><description><![CDATA[Do you know that, too? You have a lot planned for the day, and hey presto! – before you know it, your colleague is calling for lunch. You look at the clock in disbelief. How can several hours have passed without you making any significant progress? O...]]></description><link>https://hashnode.knulst.de/increase-your-productivity-with-the-ivy-lee-method</link><guid isPermaLink="true">https://hashnode.knulst.de/increase-your-productivity-with-the-ivy-lee-method</guid><category><![CDATA[Productivity]]></category><category><![CDATA[Time management]]></category><category><![CDATA[software development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Tue, 21 Mar 2023 06:40:38 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1679380706396/fa5aab46-51fa-4386-b5b8-f4e3e2135b9a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Do you know that, too? You have a lot planned for the day, and hey presto! – before you know it, your colleague is calling for lunch. You look at the clock in disbelief. How can several hours have passed without you making any significant progress? Okay...there was a "short" meeting...a few phone calls...emails...and the intern had a problem with his concept.</p>
<p>But still: there must be a way to use the time more sensibly. But how? Let's ask the expert Ivy Lee for his advice because his method promises a lot: More productivity, better results, and at the same time less stress with almost minimal effort.</p>
<p>Sounds tempting, after all, most of the tasks tend to be more rather than less. To-do lists take on absurd lengths and the overview was lost some time ago. Work is being done everywhere, only to realize with horror that nothing has really been accomplished, deadlines cannot be met and the quality of the work is also suffering. The Ivy Lee Productivity Method aims to solve this problem and help you to stop working or work longer and get the important things done.</p>
<h2 id="heading-who-was-ivy-lee-and-what-did-he-do"><strong>Who Was Ivy Lee and What Did He Do?</strong></h2>
<p><strong>First of all, do you know Charles M. Schwab?</strong> At the beginning of the 20th century, he was one of the richest men in the world and President of Bethlehem Steel, the second-largest steel company of the time. Known initially as the world's largest shipbuilder, the steel magnate and his company pioneered the skyscraper age.</p>
<p>Schwab generally had a reputation for doing everything to be successful and constantly striving for improvement. It is not without reason that Thomas Edison once said of him that he is constantly looking for an advantage over his competitors. It was probably this search that prompted Schwab to hire management consultant and productivity expert Ivy Lee in 1918. He was dissatisfied with his work and the performance of his teams and sought a solution.</p>
<p>His task for Ivy Lee was simple: Show me how I can do more in the same amount of time. He didn't hesitate for long, but replied confidently:</p>
<blockquote>
<p><em>I need 15 minutes with you and the executives. You can then test my method and if it works, pay me what you think is reasonable after three months.</em></p>
</blockquote>
<p>Three months later, the two meet again and Schwab presents Ivy Lee with a check for $25,000 - <strong>the equivalent of about $400,000 today</strong>. The steel entrepreneur later said that this was the best and most profitable lesson of his life.</p>
<p>The enthusiasm is also reflected in the pay, which was unimaginably high for the time and hardly seems to explain given the working hours - but for Schwab it was worth every dollar.</p>
<h2 id="heading-how-the-ivy-lee-productivity-method-works"><strong>How The Ivy Lee Productivity Method Works</strong></h2>
<p>And what made Ivy Lee so convincing? With the price Charles M. Schwab's paid to show his satisfaction, one could think that it must be a complex technology that enables the tasks that arise to be processed more effectively or a complex concept with which the work processes are to be optimized.</p>
<p>Luckily, there is nothing complicated about the Ivy Lee Productivity Method. All you need is a pen, a sheet of paper, and a few minutes. So it's no wonder that the productivity expert needed just 15 minutes to explain the method.</p>
<p>The whole thing can be understood and learned <strong>in just six steps</strong>.</p>
<h3 id="heading-1-write-down-six-tasks-for-the-next-day"><strong>1. Write Down Six Tasks For The Next Day</strong></h3>
<p>What is coming up for you the next day, which projects are pending, and which tasks have to be completed? Take a blank piece of paper and write down the six most important tasks and challenges for the coming day.</p>
<h3 id="heading-2-arrange-these-tasks-in-order-of-priority"><strong>2. Arrange These Tasks In Order Of Priority</strong></h3>
<p>Within the second step of the Ivy Lee Productivity Method, take a close look at your task list and assign a number to each task. This number will be the priority of the task. Then put the tasks in the appropriate order so that the most important task is numbered 1 at the top.</p>
<h3 id="heading-3-complete-the-first-task-on-the-list"><strong>3. Complete The First Task On The List</strong></h3>
<p>The next working day according to the Ivy Lee method begins with the task that has the highest priority. And without distractions, no casually checking e-mails, and also no I'll do something else first.</p>
<p>Concentrate fully on the most important task until it is actually completed.</p>
<h3 id="heading-4-check-the-priorities-again"><strong>4. Check The Priorities Again</strong></h3>
<p>Before you tackle the second task, go back to the list and see if the priorities are still right. Maybe something new has been added that urgently needs to be done or a task is no longer that important. It is important to heck the priorities again to make sure they are correct and adjust the order if necessary.</p>
<h3 id="heading-5-get-to-the-next-task"><strong>5. Get To The Next Task</strong></h3>
<p>The priorities are still correct or have they been put in the right order? Then it's time for the second task. According to this method, you work your way through the entire day until the end of the day: complete a task, check priorities, and move on.</p>
<h3 id="heading-6-make-a-new-list-in-the-evening"><strong>6. Make A New List In The Evening</strong></h3>
<p>When the work day comes to an end, complete the current task and prepare the same routine for the next day. That means: Make another list of the six most important tasks - including those that are left over from today's list and are still relevant.</p>
<h2 id="heading-pros-of-the-ivy-lee-productivity-method"><strong>Pros Of The Ivy Lee Productivity Method</strong></h2>
<p>There are numerous tips, advice, methods, and approaches that can be used to increase productivity. Work more effectively and better - a wish of many employees and of course also of supervisors.</p>
<p>But what makes the Ivy Lee method so special and literally worth $400,000?</p>
<p>This can hardly be pinned down to a single point, but what is certain is that the method brings a whole range of advantages that speak in favor of trying out Ivy Lee's suggestion at least once to find out whether your own work benefits from it:</p>
<h3 id="heading-the-ivy-lee-productivity-method-forces-single-task-work"><strong>The Ivy Lee Productivity Method Forces Single Task Work</strong></h3>
<p>Multitasking is a complex, frequently discussed topic… If you try to do everything at the same time, you usually end up doing less and the results are worse. The Ivy Lee Productivity Method focuses on one task at a time. This means that every project gets the full attention it needs to perform at its best and really make progress. With this focus, problems can be identified and solved early on, instead of spending hours tinkering with them.</p>
<h3 id="heading-the-ivy-lee-productivity-method-enforces-priorities"><strong>The Ivy Lee Productivity Method Enforces Priorities</strong></h3>
<p>Achieving a lot is useless if the energy is invested in the wrong places. Unfortunately, this is exactly what happens very often. It is plowed and toiled, only to realize afterward that the really important things are still unfinished.</p>
<p>A major advantage of the Ivy Lee method is the targeted and repeated prioritization. This is how the tasks are processed that really have to be tackled and completed.</p>
<h3 id="heading-the-ivy-lee-productivity-method-boosts-efficiency"><strong>The Ivy Lee Productivity Method Boosts Efficiency</strong></h3>
<p>Distractions and multitasking in particular often lead to a lot of work being done but little achieved. A little here, a little there and the day is over without anything really having happened.</p>
<p>Ivy Lee teaches how to use the time at work efficiently. This does not mean that no breaks are taken - quite the opposite. However, these should best be placed between the individual tasks and not prevent them from completing them.</p>
<h3 id="heading-the-ivy-lee-productivity-method-helps-with-organization"><strong>The Ivy Lee Productivity Method Helps With Organization</strong></h3>
<p>The tasks and folders that still need to be looked through are now piling up on the desk, the boss brings something new every hour and you no longer know where your head is at. Organization? There is none.</p>
<p>With the Ivy Lee method, you automatically keep track of things and make organizing tasks a habit. By dealing with the upcoming challenges not only every evening but also between the to-dos, you always know what comes next.</p>
<h3 id="heading-the-ivy-lee-productivity-method-brings-motivation"><strong>The Ivy Lee Productivity Method Brings Motivation</strong></h3>
<p>How can fun at work arise or be maintained if the constant stress is only rewarded with the fact that the boss is dissatisfied and you yourself have doubts about your performance? Seeing you actually accomplish something, on the other hand, is a boost to motivation.</p>
<p>The feeling of ticking off the top priority is particularly motivating – and ideally several times a day. This gives confirmation that you are on the right track and have really achieved something that contributes to your personal and company success.</p>
<h2 id="heading-the-ivy-lee-productivity-method-so-simple-and-yet-so-good"><strong>The Ivy Lee Productivity Method: So Simple And Yet So Good</strong></h2>
<p>One of the most frequently asked questions about the Ivy Lee Productivity Method is: <strong>It's so simple, how is it supposed to work?</strong></p>
<p>True to the motto: If it's not complicated, it can't be really good either. After all, countless scientists are dealing with this topic and each one gives a different piece of advice that should solve all productivity problems.</p>
<p>Ironically, the simple Ivy Lee method should do particularly well? Yes, exactly that. And for good reasons:</p>
<h3 id="heading-the-ivy-lee-productivity-method-is-easy-to-use"><strong>The Ivy Lee Productivity Method Is Easy To Use</strong></h3>
<p>The simplicity is perhaps the strongest reason that the Ivy Lee method works. It's practical. Anyone can use them and, most importantly, maintain them without having to force themselves into strenuous or time-consuming procedures.</p>
<h3 id="heading-the-ivy-lee-productivity-method-creates-clarity"><strong>The Ivy Lee Productivity Method Creates Clarity</strong></h3>
<p>Many methods want to explain how you can complete many or, if possible, all tasks satisfactorily in a certain amount of time. The Ivy Lee Productivity Method method is different. It's not about doing everything on the list, it's about understanding what's really important and needs to be done.</p>
<h3 id="heading-the-ivy-lee-productivity-method-makes-it-easy-to-get-started-on-a-daily-basis"><strong>The Ivy Lee Productivity Method Makes It Easy To Get Started On A Daily Basis</strong></h3>
<p>A major obstacle to the productivity of many employees is the start of the day. First, make coffee, read e-mails, set up at the desk, and see what needs to be done. It usually takes some time before things really get going. Thanks to the Ivy Lee method, however, you already know the evening before what the next day will be like - so you can start right away.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>This method looks almost too simple to qualify as a specific productivity method. It just orders all tasks by priority in the evening so that you know what to do first in the morning. But this simplicity in particular makes the Ivy Lee Method so attractive and applicable to everyone. And Charles M. Schwab pays voluntarily $25.000 (the equivalent of about $400,000 today) to Ivy Lee for this simple advice.</p>
<p>My personal advice to you: Try it!</p>
<p>It would be the best way to find out if it is right for you. Try it for a week or two and if you do not see any results in your productivity you can still try another method. Luckily, the Ivy Lee Productivity Method is unbelievably simple so you don't waste time "learning" it.</p>
<p><img src="https://www.paulsblog.dev/content/images/size/w1000/2022/09/image--51-.webp" alt="An Ivy-Lee Productivity Checklist for daily work" /></p>
<p>If you enjoyed reading this article consider commenting your valuable thoughts in the comments section. I would love to hear your feedback about the Ivy-Lee Method. Furthermore, share this article with your co-workers and friends to increase their productivity too.</p>
<p>This post was initially posted on my Personal blog: <a target="_blank" href="https://www.paulsblog.dev/increase-your-productivity-with-the-ivy-lee-method/">https://www.paulsblog.dev/increase-your-productivity-with-the-ivy-lee-method/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev/">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/free">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@schmaendels">Andreas Klassen</a> / <a target="_blank" href="https://unsplash.com/s/photos/ivy-lee-productivity">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[How To Add Comments To Your Blog With Docker]]></title><description><![CDATA[Paul Knulst  in  Docker • Jul 1, 2022 • 7 min read

Having comments on your blog is one of the most engaging features! Unfortunately, Ghost Blogging Service does not support any comments out of the box. Although they are many different services like ...]]></description><link>https://hashnode.knulst.de/how-to-add-comments-to-your-blog-with-docker</link><guid isPermaLink="true">https://hashnode.knulst.de/how-to-add-comments-to-your-blog-with-docker</guid><category><![CDATA[Docker]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Blogging]]></category><category><![CDATA[webdev]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Mon, 27 Feb 2023 08:33:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1677486787876/dbce008f-d5d0-4b6f-a39e-7ab5800f3a8c.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/author/paulknulst/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/docker/">Docker</a> • Jul 1, 2022 • 7 min read</p>
<hr />
<p>Having comments on your blog is one of the most engaging features! Unfortunately, Ghost Blogging Service does not support any comments out of the box. Although they are many different services like Disqus or Discourse which are kind of "free" (they have inbuilt Ads in the "free" version. And Ads are  <strong>EVIL</strong>) there also exist some really free services like <a target="_blank" href="https://github.com/adtac/commento">Commento</a>, <a target="_blank" href="https://github.com/schn4ck/schnack">Schnack</a>, <a target="_blank" href="https://github.com/coralproject/talk">CoralProject Talk</a>, and <a target="_blank" href="https://github.com/posativ/isso/">Isso</a>.</p>
<p>I tested all of the free services for my blog and come to the conclusion that Isso is the best service to use in a Ghost Blog. Within this article, I will describe why Isso is the best software, how it can be installed in a Docker environment, and how Isso comments can be integrated into any Blog (not only Ghost Blogging software).</p>
<h2 id="heading-why-isso">Why Isso?</h2>
<p>Isso is a commenting server written in Python and JavaScript and aims to be a drop-in replacement for Disqus or Discourse.</p>
<p>It has several features:</p>
<ul>
<li>It is a very lightweight commenting system</li>
<li><strong>Works with Docker Compose</strong></li>
<li>Uses SQLite because comments are not Big Data!</li>
<li>A very minimal commenting system with a simple moderation system</li>
<li><strong>Privacy-first commenting system</strong></li>
<li>Supports Markdown</li>
<li><strong>It's free</strong></li>
<li>Similar to native WordPress comments</li>
<li>You can Import WordPress or Disqus</li>
<li>Embed it everywhere in a single JS file; 65kB (20kB gzipped)</li>
</ul>
<p>All of these features are good reasons to choose Isso as your backend comment system instead of one of the others. If you want to install it in a Docker (or Docker Swarm) environment you can follow my personal guide.</p>
<h2 id="heading-install-isso-comments-with-docker">Install Isso Comments With Docker</h2>
<h3 id="heading-prerequisite">Prerequisite</h3>
<ul>
<li><strong>Docker (optional Docker Swarm):</strong> To fully follow this tutorial about installing Isso for your blog you need to have a running Docker environment. I will also provide a Docker Swarm file at the end.</li>
<li><strong>Traefik</strong>: Traefik is the load balancer that forwards my request to the Docker container. You need to have one installed to access the comments with an URL. If you do not have a running Traefik you will learn <a target="_blank" href="https://www.paulsblog.dev/how-to-setup-traefik-with-automatic-letsencrypt-certificate-resolver/">within this tutorial</a> how you can set one up.</li>
</ul>
<p>If you don't want to use Docker and Traefik please follow <a target="_blank" href="https://isso-comments.de/docs/reference/installation/">the official Installation Guide</a> on the Isso website.</p>
<h3 id="heading-set-up-docker-compose-file">Set Up Docker Compose File</h3>
<p>If you prepared everything you start installing Isso. First, you have to download the latest version of Isso from the GitHub page: <a target="_blank" href="https://github.com/posativ/isso/">https://github.com/posativ/isso/</a>. You can either download the zip file containing the master branch and extract it or clone it:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> git@github.com:posativ/isso.git
</code></pre>
<p>Then switch into the <code>isso</code> folder, delete the docker-compose.yml, create a new docker-compose.yml, and paste the following code snippet into it:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.4"</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">isso:</span>
    <span class="hljs-attr">build:</span>
      <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
      <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">isso</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">GID=1000</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">UID=1000</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">db:/db</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik-public</span>
    <span class="hljs-attr">labels:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.enable=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.docker.network=traefik-public</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.constraint-label=traefik-public</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.isso-http.rule=Host(`YOUR_DOMAIN`)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.isso-http.entrypoints=http</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.isso-http.middlewares=https-redirect</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.isso-https.rule=Host(`YOUR_DOMAIN`)</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.isso-https.entrypoints=https</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.isso-https.tls=true</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.isso-https.tls.certresolver=le</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.services.isso.loadbalancer.server.port=8080</span>
    <span class="hljs-attr">healthcheck:</span>
      <span class="hljs-attr">test:</span> <span class="hljs-string">wget</span> <span class="hljs-string">--no-verbose</span> <span class="hljs-string">--tries=1</span> <span class="hljs-string">--spider</span> <span class="hljs-string">http://localhost:8080/info</span> <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
      <span class="hljs-attr">interval:</span> <span class="hljs-string">5s</span>  <span class="hljs-comment"># short timeout needed during start phase</span>
      <span class="hljs-attr">retries:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">start_period:</span> <span class="hljs-string">5s</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">3s</span>
<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">db:</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">traefik-public:</span>
    <span class="hljs-attr">external:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>Within <strong>Line 5 - 8</strong> you can see within this Compose file that the Isso image will be built from the Dockerfile that you downloaded from the official GitHub repository and will be named <code>isso</code>.</p>
<p>In <strong>Line 12-13 (and 34-35)</strong>, I defined a volume in which all comments will be saved within an SQLite DB.</p>
<p>The labels section in <strong>Line 16 - 27</strong> contains important information for Traefik. Replace YOUR_DOMAIN with the domain where the Isso backend should be accessible. This is important for the moderation of comments.</p>
<p>In <strong>Line 28 - 33</strong>, I also added a health check that will check every 5s if the Isso backend is running correctly. To learn more about Docker health checks you should read this tutorial:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/how-to-successfully-implement-a-healthcheck-in-docker-compose/">https://www.paulsblog.dev/how-to-successfully-implement-a-healthcheck-in-docker-compose/</a></p>
<h3 id="heading-configuration-of-isso-backend">Configuration of Isso Backend</h3>
<p>Before you can run your Isso backend you have to adjust the Isso configuration file to your needs.</p>
<p>The following file shows the most important settings that you HAVE to update in order to have a working Isso backend. I will explain them afterward.</p>
<pre><code class="lang-bash">[general]
dbpath = /db/comments.db
host =
    https://www.knulst.de
notify = smtp

[admin]
enabled = <span class="hljs-literal">true</span>
password = yourSuperSafePasswordThatYouNeedInAdminMenu

[moderation]
enabled = <span class="hljs-literal">true</span>

[server]
listen = http://localhost:8080
public-endpoint = https://isso.knulst.de

[smtp]
username = YOUR_EMAIL_USER
password = YOUR_EMAIL_USERPASS
host = YOUR_EMAIL_HOST
port = 587
security = starttls
to = USER_WHO_SHOULD_RECEIVE_MAIL
from = YOUR_EMAIL_USER
</code></pre>
<p><strong>[general]:</strong> Set the dbpath, the URL of your blog, and that you want to get Emails if new comments are submitted</p>
<p><strong>[admin]:</strong> Enable administration and set your admin password. This is needed to login into the admin interface.</p>
<p><strong>[moderation]:</strong> Enable moderation of new comments. If set to false every comment will be shown instantly. Not recommended!</p>
<p><strong>[server]:</strong> Set your public endpoint for your Isso backend. Should be equal to the value within the Compose file.</p>
<p><strong>[smtp]:</strong> Set your Email server settings and account data of your Email.</p>
<h3 id="heading-run-isso-backend">Run Isso Backend</h3>
<p>After adjusting the Compose and <code>isso.cfg</code> file to your needs, you can start the Isso backend by executing:</p>
<pre><code class="lang-bash">docker-compose up -d --build
</code></pre>
<p>After some seconds your Isso backend should be running at the specified domain. You can check it by going to <strong>https://your-chosen-domain.com/info</strong>.</p>
<h2 id="heading-integrate-isso-to-ghost-cms-or-any-equivalent-blog">Integrate Isso To Ghost CMS (or any equivalent Blog)</h2>
<h3 id="heading-adjust-the-source-code-snippet">Adjust The Source Code Snippet</h3>
<p>Now that you have installed your Isso backend you can integrate comments into your blog. To do this, all you have to do is insert the following snippet in your source code. I will explain how it is done in Ghost Blogging software afterward.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">data-isso</span>=<span class="hljs-string">"//isso.knulst.de/"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"//isso.knulst.de/js/embed.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"isso-thread"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
</code></pre>
<p>First of all replace <code>isso.knulst.de</code> with the domain, you used with your Isso bac<br />kend. Furthermore, you should carefully read the source code snippet because there are two important settings:</p>
<ol>
<li>The URL in data-isso starts with two <code>/</code>.</li>
<li>The URL in src also starts with two <code>/</code>.</li>
</ol>
<p>If you change the URLs to your needs keep the two <code>/</code> in front of the Isso backend URL. This is needed because the Isso backend is deployed on a different host than the blog.</p>
<h3 id="heading-add-snippet-to-your-page">Add Snippet To Your Page</h3>
<p>With the adjusted source code snippet you can now add comments to your blog. If you use the Ghost Blogging platform you should first download the design that you are using on your instance and extract it into a folder.</p>
<p>Locate the <code>post.hbs</code> file and check where the article ends. The best place for comments will be below the Tag List that is often shown at the end of an article. After you found the closing <code>&lt;/div&gt;</code> insert the snippet in front of that <code>&lt;/div&gt;</code>.</p>
<p>Zip your theme, reupload it to your Ghost Blogging Software, and activate it within the Design interface.</p>
<p><em>If you use another blogging software you should also be able to edit the <code>post</code> html/php/js file and add the snippet where you want to see comments.</em></p>
<p>Now, switch to any article on your blog and you should see the comments section:</p>
<p><img src="https://www.paulsblog.dev/content/images/2022/09/image--53-.webp" alt="Picture of Isso comments on your website" /></p>
<p>If you set up the Isso backend as I explained in the previous section anyone can now comment on every post and you will be informed by email about a new comment and can instantly activate or delete it within the email:</p>
<p><img src="https://www.paulsblog.dev/content/images/2022/09/image--54-.webp" alt="Email content after someone created a comment with Isso" /></p>
<p>After getting some cool comments they will be displayed below your post. Because activated the Gravatar feature and set the type to <code>robohash</code>, I have cool robot pictures for every user that creates a comment:</p>
<p><img src="https://www.paulsblog.dev/content/images/2022/09/image--55-.webp" alt="Picture of comments done with Isso using robohash as image creator" /></p>
<h2 id="heading-deploy-in-docker-swarm">Deploy In Docker Swarm</h2>
<p>To deploy Isso into your Docker Swarm you can use <a target="_blank" href="https://ftp.f1nalboss.de/data/docker-compose.isso.yml">my Isso Docker Compose file</a> that has the same settings as the plain Compose file. But, this will only work as intended if you set up your Docker Swarm as I describe in this tutorial:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/docker-swarm-in-a-nutshell/">https://www.paulsblog.dev/docker-swarm-in-a-nutshell/</a></p>
<p>Furthermore, you need a Traefik Load Balancer in your Docker Swarm that will be used by the Compose file. I explained how to enhance your Docker Swarm with a Traefik in this tutorial:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/services-you-want-to-have-in-a-swarm-environment/#traefik">https://www.paulsblog.dev/services-you-want-to-have-in-a-swarm-environment/#traefik</a></p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>While many writers argue that you do not need a comment system integrated into your blog I completely deny this argument! I personally think that comments are a great way to interact with your users, answer questions and build your community. Also, if users comment on your articles you could use this interaction to improve your content.</p>
<p>If you enjoyed reading this article consider commenting your valuable thoughts in the comments section. I would love to hear your feedback about my tutorial. Furthermore, share this article with fellow bloggers to help them get comments on their blogs.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/how-to-add-comments-to-your-blog-with-docker/">https://www.paulsblog.dev/how-to-add-comments-to-your-blog-with-docker/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/free">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@lunarts?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Volodymyr Hryshchenko</a> / <a target="_blank" href="https://unsplash.com/s/photos/discussion?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Advanced React UI Components To Optimize Development Process]]></title><description><![CDATA[Today, I will bring your attention to 5 lightweight free UI components that can be used to enhance the user experience on your website very easily.
1. Mantine
Mantine is a fully-featured React components library that can be used to build fully functi...]]></description><link>https://hashnode.knulst.de/advanced-react-ui-components-to-optimize-development-process</link><guid isPermaLink="true">https://hashnode.knulst.de/advanced-react-ui-components-to-optimize-development-process</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[React]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[software development]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Sun, 12 Feb 2023 19:54:41 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1676231564476/985ef8ba-6ce8-4923-a23b-7e58ff703cf3.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Today, I will bring your attention to 5 lightweight free UI components that can be used to enhance the user experience on your website very easily.</p>
<h2 id="heading-1-mantinehttpsmantinedev"><a target="_blank" href="https://mantine.dev/">1. Mantine</a></h2>
<p>Mantine is a fully-featured React components library that can be used to build fully functional accessible web applications. Furthermore, it includes several customizable React components and hooks with native Dark mode support to cover you in any situation. It is focused on usability, accessibility, and developer experience. Mantine is TypeScript based.</p>
<p><img src="https://www.paulsblog.dev/content/images/2023/02/image--8-.webp" alt="Screenshot of react component library mantine.dev homepage showing its features." /></p>
<p><a target="_blank" href="https://github.com/mantinedev/mantine">Find it on GitHub</a></p>
<h2 id="heading-2-react-easy-crop">2. react-easy-crop</h2>
<p>react-easy-crop can be used to crop images/videos with easy interactions. It supports drag, zoom, and rotating images in any format or base64 string. Also, it supports every video format that is supported in HTML5.</p>
<p>Furthermore, react-easy-crop is a mobile-friendly UI component.</p>
<p><img src="https://www.paulsblog.dev/content/images/2023/02/image--9-.webp" alt="Screenshot of React component react-easy-crop GitHub project taken by pauls dev blog" /></p>
<p><a target="_blank" href="https://github.com/ricardo-ch/react-easy-crop">Find it on GitHub</a></p>
<h2 id="heading-3-react-colorfulhttpsomgovichgithubioreact-colorful"><a target="_blank" href="https://omgovich.github.io/react-colorful/">3. react-colorful</a></h2>
<p>react-colorful provides a tiny (only 2.8 KB gzipped) color picker component for React apps (also Preact). With absolute no dependencies, it can still be used to have a cross-browser workable, mobile-friendly color picker that is built with hooks and functional components only. Also, the color picker is written in TypeScript and has all types included.</p>
<p><img src="https://www.paulsblog.dev/content/images/2023/02/image--10-.webp" alt="Screenshot of React component react-colorful project website taken by pauls dev blog" /></p>
<p><a target="_blank" href="https://github.com/omgovich/react-colorful">Find it on GitHub</a></p>
<h2 id="heading-4-react-syntax-highlighterhttpsreact-syntax-highlightergithubioreact-syntax-highlighterdemo"><a target="_blank" href="https://react-syntax-highlighter.github.io/react-syntax-highlighter/demo/">4. React Syntax Highlighter</a></h2>
<p>React Syntax Highlighter is a React UI component that enables syntax highlighting in any React app by integrating/combining two popular projects: <a target="_blank" href="https://github.com/wooorm/lowlight">lowlight</a> and <a target="_blank" href="https://github.com/wooorm/refractor">refractor</a>.</p>
<p>Highlighting the code is done with Prism.js and highlight.js by using inline styles instead of altering the DOM manually or using <code>dangerouslySetInnerHTML</code>.</p>
<p><img src="https://www.paulsblog.dev/content/images/2023/02/image--11-.webp" alt="Screenshot of React Syntax highlighter project website taken by pauls dev blog" /></p>
<p><a target="_blank" href="https://github.com/react-syntax-highlighter/react-syntax-highlighter">Find it on GitHub</a></p>
<h2 id="heading-5-tiptaphttpstiptapdev"><a target="_blank" href="https://tiptap.dev/">5. TipTap</a></h2>
<p>TipTap is a headless, framework-agnostic rich text editor that is extendable and based on ProseMirror. It enables full control over every aspect of a text editor experience. Also, it is very customizable, comes with a lot of extensions, and is fully documented.</p>
<p><img src="https://www.paulsblog.dev/content/images/2023/02/image--12-.webp" alt="Screenshot of React component TipTap project website taken by pauls dev blog" /></p>
<p>Furthermore, TipTap works with Vanilla JavaScript and is also integrated into several TypeScript-based frameworks (React, Next.js, Vue, etc…).</p>
<p><a target="_blank" href="https://github.com/ueberdosis/tiptap">Find it on GitHub</a></p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>I hope you like these UI components and will use them in your next React project.</p>
<p>Also, if you have any questions, ideas, recommendations, or want to share your own awesome UI component, please jot them down below. I try to answer your question if possible and will test your recommendations.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/advanced-react-ui-components-to-optimize-development-process/">https://www.paulsblog.dev/advanced-react-ui-components-to-optimize-development-process/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/#/portal/signup/free">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@lautaroandreani?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Lautaro Andreani</a> / <a target="_blank" href="https://unsplash.com/photos/xkBaqlcqeb4?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Introduction To 11 Core JavaScript Functions To Improve Code Quality]]></title><description><![CDATA[Paul Knulst  in  Programming • Jun 21, 2022 • 13 min read

It doesn't care if you working with backend or frontend (or even SPACESHIPS!), JavaScript could be used everywhere because it is a quite flexible language. It has hardcore functional programm...]]></description><link>https://hashnode.knulst.de/introduction-to-11-core-javascript-functions-to-improve-code-quality</link><guid isPermaLink="true">https://hashnode.knulst.de/introduction-to-11-core-javascript-functions-to-improve-code-quality</guid><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Frontend Development]]></category><category><![CDATA[Programming Blogs]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Sun, 22 Jan 2023 19:12:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1674414641117/671738dd-92e9-4f55-8f71-235524298d53.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/author/paulknulst/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/programming/">Programming</a> • Jun 21, 2022 • 13 min read</p>
<hr />
<p>It doesn't care if you working with backend or frontend (or even <a target="_blank" href="https://www.infoq.com/news/2020/06/javascript-spacex-dragon/">SPACESHIPS!</a>), JavaScript could be used everywhere because it is a quite flexible language. It has hardcore functional programming patterns as well as classes and its similarity to other "C-like" languages enables an easy transition for every developer.</p>
<p>If you want to level up your skills in JavaScript, I would suggest learning about, practicing, and mastering the following very important core functions that are available in the language. You will not need every function for solving most problems but they can help you to avoid heavy lifting in some cases, while in others, they will reduce the amount of code that you have to write.</p>
<h2 id="heading-1-map">1. map()</h2>
<p>One of the most important JavaScript functions is <code>map()</code>! Also, it is one function that gives the most trouble to people who learn JavaScript. This happens because the way this function works is an idea taken from Functional Programming which most developers are not familiar with. It seems strange or even wrong to our biased brains.</p>
<p>The function <code>map()</code> is very simple. It attaches itself to an array and converts each item into something else, resulting in a new array. This conversion is done by an anonymous function provided in the map brackets.</p>
<p><strong>That's all that map does!</strong></p>
<p>At first, the syntax for <code>map()</code> might take some time to get used to but after mastering it will be one of your best friends working with JavaScript.</p>
<h3 id="heading-why-you-might-want-to-use-map">Why You Might Want To Use <code>map()</code>?</h3>
<p>For instance, let's say we recorded the amount of water in a fountain for each day of the last week and stored it in an array. At the end of the week, you are told that the measurement tool was not as accurate as possible and the amount of water was 3.2 liters less than it should be.</p>
<p>With the <code>map()</code> function you can easily solve this issue by doing this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> dailyReadings = [<span class="hljs-number">34.2</span>, <span class="hljs-number">35.4</span>, <span class="hljs-number">40.1</span>, <span class="hljs-number">35.2</span>, <span class="hljs-number">33.5</span>, <span class="hljs-number">36.5</span>, <span class="hljs-number">38.7</span>];

<span class="hljs-keyword">const</span> correctedDailyReadings = dailyReadings.map(<span class="hljs-function"><span class="hljs-params">day</span> =&gt;</span> day + <span class="hljs-number">3.2</span>);

<span class="hljs-built_in">console</span>.log(correctedDailyReadings); <span class="hljs-comment">//gives [37.4, 38.6, 43.3, 38.4, 36.7, 39.7, 41.9]</span>
</code></pre>
<p>You can find another very practical example in the world of React when you create DOM element lists from arrays. This is a common pattern in React and will be achieved by this piece of code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span>({articles}) =&gt; {
    <span class="hljs-keyword">return</span> links.map(<span class="hljs-function"><span class="hljs-params">article</span> =&gt;</span> {
        <span class="hljs-keyword">return</span> (
            <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"link"</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{article.id}</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"article-name"</span>&gt;</span>{article.name}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"article-desc"</span>&gt;</span>{article.description}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
        );
    });
};
</code></pre>
<p>Now you can argue that <code>map()</code> is nothing but another implementation of a <code>for</code> loop and you will be right but notice that maybe this is your object-oriented trained mind that makes this argument.</p>
<h2 id="heading-2-filter">2. filter()</h2>
<p><code>filter()</code> is an unbelievable useful JavaScript function that you can use in many situations because it filters an array based on a given rule/logic. This rule/logic will be provided as anonymous functions.</p>
<p>To show how <code>filter()</code> is working we can reuse the previous fountain example. Assume that the array contains the amount of water stored within the fountain (<strong>This time measurement tool works as expected</strong>). Now, we want to find out how often the amount was lower than 36 liters. This can be achieved using the <code>filter()</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> dailyReadings = [<span class="hljs-number">34.2</span>, <span class="hljs-number">35.4</span>, <span class="hljs-number">40.1</span>, <span class="hljs-number">35.2</span>, <span class="hljs-number">33.5</span>, <span class="hljs-number">36.5</span>, <span class="hljs-number">38.7</span>];

<span class="hljs-keyword">const</span> daysWithLowAmount = dailyReadings.filter(<span class="hljs-function"><span class="hljs-params">day</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> day &lt; <span class="hljs-number">36</span>
});

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Days with low amount of water: "</span> + daysWithLowAmount.length); <span class="hljs-comment">// 4</span>
<span class="hljs-built_in">console</span>.log(daysWithLowAmount); <span class="hljs-comment">// [34.2, 35.4, 35.2, 33.5]</span>
</code></pre>
<p>It is important that the anonymous function that you pass to <code>filter()</code> has to return a Boolean value: <code>true</code> or <code>false</code>. The Boolean expression is used by the <code>filter()</code> function to determine if a value should be inserted in the newly generated array or not.</p>
<p>Within the anonymous function, you can implement any amount of complex logic to filter the data; you can read user inputs, make any API call, and so on, as long as you return a Boolean value in the end.</p>
<p><strong>BEWARE:</strong> Keep in mind that <code>filter()</code> always returns an array. This is also the case if the filtered data is empty. Often this leads to subtle bugs within code snippets because <code>filter()</code> is used this way:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> dailyReadings = [<span class="hljs-number">34.2</span>, <span class="hljs-number">35.4</span>, <span class="hljs-number">40.1</span>, <span class="hljs-number">35.2</span>, <span class="hljs-number">33.5</span>, <span class="hljs-number">36.5</span>, <span class="hljs-number">38.7</span>];

<span class="hljs-keyword">const</span> daysWithCriticalAmount = dailyReadings.filter(<span class="hljs-function"><span class="hljs-params">day</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> day &lt; <span class="hljs-number">30</span>
});

<span class="hljs-keyword">if</span>(daysWithCriticalAmount) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"The amount of water was critical"</span>);
} <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"The amount of water was normal"</span>);
}
</code></pre>
<p>Did you notice something? The <code>if</code> condition in the end checks <code>daysWithCriticalAmount</code> which is actually an array that is empty because there was no day with less water than 30.</p>
<p>Surprisingly, this mistake is done too often if you are rushing toward a deadline and just want to implement a filter method fast. The problem with this piece of code is that especially JavaScript (Yeah, JavaScript is weird) is an inconsistent language in many ways. Sometimes the "truthiness" of things is one of them.</p>
<p>As you maybe know in JavaScript <code>[] == true</code> returns false and you may think that the above code is correct.</p>
<p><strong>Unfortunately, within an <code>if</code> condition, <code>[]</code> evaluates to true!</strong> In other words, the else block will never be executed.</p>
<p>Fixing this problem is very easy because you only have to check if the length of the resulting array is greater or equal to 1.</p>
<h2 id="heading-3-reduce">3. reduce()</h2>
<p>Compared to all the other functions in this article <code>reduce()</code> is competing for the first place of "confusing, weird and complex JavaScript functions". Although this function is highly important and results in elegant code in many situations, it is avoided by many JavaScript developers. They prefer to write code without the help of <code>reduce()</code>.</p>
<p>One reason for this is the complexity of <code>reduce()</code>. It is very hard to understand the concept and the execution of this function. Furthermore, if you read its description, you have to re-read it several times, and still, you doubt yourself if you read it wrong (Happens to me until I saw it in action).</p>
<p>The <code>reduce()</code> function is used to reduce (obvious isn't it?) the given array to a single value. This could be a number, a string, a function, an object, or whatever else.</p>
<p>If you want to use the <code>reduce()</code> function to reduce an array you need to supply the needed logic by providing a function. In general, this function will be called the reducer function that transforms the first argument to <code>reduce()</code>. Also, it contains a second argument that is a starting value and can be a number, a string, etc. The function itself will look like this:</p>
<pre><code class="lang-javascript">array.reduce(reducerFunction, startValue)
</code></pre>
<p><strong>startValue:</strong> The startValue passed to <code>reduce()</code> function is the starting value (obvious...) for the calculation you want to process with the reducerFunction. For example, if you want to execute an addition you might start with 0 and if you want to do a multiplication you should use a value of 1.</p>
<p><strong>reducerFunction:</strong> As mentioned before the reducer function is used to convert the array into a single value. It has two arguments:  an <code>accumulator</code> and a variable to store the current value.</p>
<p>The <code>accumulator</code> is just a fancy name describing the variable that contains the result of the calculation (it is like using <code>total</code> to sum up all items in an array). Within the reducer function, the accumulator will be initially set to the starting value and then the calculation will be performed step by step iterating through the array while the result of each step is saved within the accumulator.</p>
<p>The second argument of the reducer function is <code>currentValue</code> and will contain the value within the array at a given position in a specific step. For example, in step 2 while executing the reducer function the variable <code>currentValue</code> will contain the second value from the input array.</p>
<p>With all this information we should look at the following example which will add every value from an input array within the reducer function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> input = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>];
<span class="hljs-keyword">const</span> result = input.reduce(<span class="hljs-function">(<span class="hljs-params">acc, currentValue</span>) =&gt;</span> acc + currentValue, <span class="hljs-number">0</span>);
<span class="hljs-built_in">console</span>.log(result); <span class="hljs-comment">// 10</span>
</code></pre>
<p>Within the first line, we define the input array that's holding all the numbers we want to add. The next line will contain the <code>input.reduce()</code> call, which defines the starting value for acc to be 0 because in an addition we should start with 0 to not influence the result. The reducer functions body <code>(acc, currentValue) =&gt; acc + currentValue</code> does a simple addition of the current value and the value that is stored in the accumulator.</p>
<p>If you would do this addition with a for loop it would look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> input = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>];
<span class="hljs-keyword">let</span> result = <span class="hljs-number">0</span>;
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; input.length; i++) {
    result += input[i]
}
<span class="hljs-built_in">console</span>.log(total)
</code></pre>
<p>As you can see the simple for loop example is much longer and not as elegant as using reduce.</p>
<h2 id="heading-4-some">4. some()</h2>
<p>Imagine you have an array of objects containing Students and their scores in an exam. Now, you want to know if there are students in your class that have a score that is greater than 90% and it does not care if there is one or multiple students that passed that exam with more than 90%.</p>
<p>If you want to do this in JavaScript you can solve the problem while using a loop and a variable that is set to true after the first person is found that has a score greater 90%:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> students = [{
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 1"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">78</span>,
}, {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 2"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">92</span>
}, {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 3"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">90</span>
}]

<span class="hljs-keyword">let</span> over90 = <span class="hljs-literal">false</span>;

<span class="hljs-keyword">for</span>(<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &lt; students.length; i++) {
    <span class="hljs-keyword">if</span>(students[i].score &gt; <span class="hljs-number">90</span>) {
        over90 = <span class="hljs-literal">true</span>;
        <span class="hljs-keyword">break</span>;
    }
}

<span class="hljs-keyword">if</span>(over90) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"There are students with exceptional score!"</span>)
} <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Unfortunately, nothing is exceptional"</span>)
}
</code></pre>
<p><em><strong>For me, this code looks very "ugly", "verbose", or "horrible"!</strong></em></p>
<p>It could be optimized by using the <code>map()</code> function that was described earlier but the solution will still be a little bit clunky.</p>
<p>Luckily, JavaScript has a rather neat function called <code>some()</code> that is available in the core language, works with arrays, and returns a Boolean value. While providing an anonymous function it will filter the input array to solve the problem elegantly:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> students = [{
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 1"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">78</span>,
}, {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 2"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">92</span>
}, {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 3"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">90</span>
}]

<span class="hljs-keyword">if</span>(students.some(<span class="hljs-function"><span class="hljs-params">student</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> student.score &gt; <span class="hljs-number">90</span>
})) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"There are students with exceptional score!"</span>)
} <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Unfortunately, nothing is exceptional"</span>)
}
</code></pre>
<p>You can see it gets the same input and will produce the same result but in a more elegant way. Also, the amount of code is reduced and it is much more readable than before.</p>
<h2 id="heading-5-every">5. every()</h2>
<p>Another function that already exists in the JavaScript core language is <code>every()</code>. This function will return a Boolean value (like <code>some()</code>) but will test all items within the input array. To do this you have to provide (again) an anonymous function. As an example, we will test if every student has passed the exam by achieving an exam score of 70 or more:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> students = [{
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 1"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">78</span>,
}, {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 2"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">92</span>
}, {
    <span class="hljs-attr">name</span>: <span class="hljs-string">"Stud 3"</span>,
    <span class="hljs-attr">score</span>: <span class="hljs-number">90</span>
}]

<span class="hljs-keyword">if</span>(students.every(<span class="hljs-function"><span class="hljs-params">student</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> student.score &gt; <span class="hljs-number">70</span>
})) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"All students passed the exam!"</span>)
} <span class="hljs-keyword">else</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Unfortunately, not all students did well"</span>)
}
</code></pre>
<p>Like <code>some()</code> this function creates elegant, readable, and maintainable code that is short.</p>
<h2 id="heading-6-includes">6. includes()</h2>
<p>Often, if I want to check an array of the existence of substrings I will use <code>indexOf()</code>. Unfortunately, I have to look up the docs to know how their return values are.</p>
<p>Luckily, there is a nice alternative in JavaScript that can be used to achieve this: <code>includes()</code>. The usage is very easy and it is case-sensitive (I guess you wished for it anyway). See the following code snippet for an example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> numbers = [<span class="hljs-number">6</span>, <span class="hljs-number">5</span>, <span class="hljs-number">13</span>, <span class="hljs-number">26</span>, <span class="hljs-number">48</span>, <span class="hljs-number">1</span>];
<span class="hljs-built_in">console</span>.log(numbers.includes(<span class="hljs-number">5</span>)); <span class="hljs-comment">// true</span>

<span class="hljs-keyword">const</span> name = <span class="hljs-string">"Paul Knulst"</span>;
<span class="hljs-built_in">console</span>.log(name.includes(<span class="hljs-string">'paul'</span>)); <span class="hljs-comment">// false, because first letter is in small</span>
<span class="hljs-built_in">console</span>.log(name.includes(<span class="hljs-string">'Paul'</span>)); <span class="hljs-comment">// true, as expected</span>
</code></pre>
<p>However, includes can only compare simple values and no objects:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> user = {<span class="hljs-attr">a</span>: <span class="hljs-number">6</span>, <span class="hljs-attr">b</span>: <span class="hljs-number">5</span>, <span class="hljs-attr">c</span>: <span class="hljs-number">26</span>};
<span class="hljs-built_in">console</span>.log(user.includes(<span class="hljs-string">'b'</span>)); <span class="hljs-comment">// does not work because objects does not have an "includes" method</span>
</code></pre>
<h2 id="heading-7-shift">7. shift()</h2>
<p>If you want to remove the first element of an array <code>shift()</code> is the method you should use because it is easy to remember and intuitive. Notice that you also can do it with <code>splice</code> (see it later) but <code>shift()</code> is easier:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> input = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>];
<span class="hljs-keyword">const</span> first = input.shift();
<span class="hljs-built_in">console</span>.log(input); <span class="hljs-comment">// gives: Array [2, 3, 4]</span>
<span class="hljs-built_in">console</span>.log(first); <span class="hljs-comment">// gives: 1</span>
</code></pre>
<p>As you can see here shift alters the array it is called on and returns the removed element.</p>
<p><strong>Notice:</strong> shift is extremely inefficient and should be avoided if you are working with large arrays. Too many calls of <code>shift()</code> on large arrays can break your applications!</p>
<h2 id="heading-8-unshift">8. unshift()</h2>
<p>In contrast to <code>shift()</code> which removes one element this function is used to add a new element at the beginning of the array:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> input = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>];
input.unshift(<span class="hljs-number">0</span>);
<span class="hljs-built_in">console</span>.log(input); <span class="hljs-comment">// expected output: Array [0, 1, 2, 3, 4]</span>
</code></pre>
<p><strong>Notice:</strong> Like shift, unshift is extremely inefficient and should be avoided if you are working with large arrays. Too many calls of <code>unshift()</code> on large arrays can break your applications!</p>
<h2 id="heading-9-slice">9. slice()</h2>
<p>In JavaScript exist a concept that is called slicing which is a function to create a new string containing a part of an original string by providing a starting index and an end index. Now, with help of the indexOf function you can slice an input string to retrieve the searched substring:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> input = <span class="hljs-string">"This is a cool article about mandatory JavaScript functions that every developer shoud know"</span>;
<span class="hljs-keyword">const</span> start = input.indexOf(<span class="hljs-string">"cool"</span>);
<span class="hljs-keyword">const</span> end = input.indexOf(<span class="hljs-string">"that"</span>);
<span class="hljs-keyword">const</span> headline = input.slice(start, end);
<span class="hljs-built_in">console</span>.log(headline); <span class="hljs-comment">// "cool article about mandatory JavaScript functions"</span>
</code></pre>
<p>Notice that the start index is included in the resulting array but the end index is not. Also, you can see that the space between "functions" and "that" is also not sliced.</p>
<p>Additionally, slicing can also be used with arrays:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> animals = [<span class="hljs-string">'ant'</span>, <span class="hljs-string">'bison'</span>, <span class="hljs-string">'camel'</span>, <span class="hljs-string">'duck'</span>, <span class="hljs-string">'elephant'</span>];

<span class="hljs-built_in">console</span>.log(animals.slice(<span class="hljs-number">2</span>)); <span class="hljs-comment">// ["camel", "duck", "elephant"]</span>
<span class="hljs-built_in">console</span>.log(animals.slice(<span class="hljs-number">2</span>, <span class="hljs-number">4</span>)); <span class="hljs-comment">// Array ["camel", "duck"]</span>
</code></pre>
<h2 id="heading-10-splice">10. splice()</h2>
<p><code>splice()</code> sounds like <code>slice()</code> and could be compared with it because both functions create new arrays or strings from the original ones. The main small but very important difference is that splice() removes, changes, or adds elements <strong>but modifies the original array</strong>. This "destructuring" of the original array can lead to huge problems if you do not fully understand how deep copies and references work in JavaScript!</p>
<p>The following example will show how <code>splice()</code> can be used:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> months = [<span class="hljs-string">'Jan'</span>, <span class="hljs-string">'March'</span>, <span class="hljs-string">'April'</span>, <span class="hljs-string">'June'</span>];
<span class="hljs-keyword">const</span> feb = months.splice(<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-string">'Feb'</span>); <span class="hljs-comment">// inserts at index 1</span>
<span class="hljs-built_in">console</span>.log(months); <span class="hljs-comment">// ["Jan", "Feb", "March", "April", "June"]</span>
<span class="hljs-built_in">console</span>.log(feb); <span class="hljs-comment">// []</span>

<span class="hljs-keyword">const</span> may = months.splice(<span class="hljs-number">4</span>, <span class="hljs-number">1</span>, <span class="hljs-string">'May'</span>); <span class="hljs-comment">// replaces 1 element at index 4</span>
<span class="hljs-built_in">console</span>.log(months); <span class="hljs-comment">// ["Jan", "Feb", "March", "April", "May"]</span>
<span class="hljs-built_in">console</span>.log(may); <span class="hljs-comment">// ["June"]</span>
</code></pre>
<p>You can see here that <code>splice()</code> manipulates the original array. If you remove an item it will also return the removed entry but if you add an entry the return array will be empty.</p>
<h2 id="heading-11-fill">11. fill()</h2>
<p>The last JavaScript function is <code>fill()</code> and is used to change several items to a single value or even reset the full array to their initial values. If you need this <code>fill()</code> can help you avoid loops to reset the values of an array. Furthermore, the function can be used to replace some or all entries from the array with a given value.</p>
<p>You can see how it works within the following example:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> input = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>, <span class="hljs-number">5</span>, <span class="hljs-number">6</span>];
<span class="hljs-built_in">console</span>.log(input.fill(<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">4</span>)); <span class="hljs-comment">// [1, 2, 0, 0, 0, 0]</span>
<span class="hljs-built_in">console</span>.log(input.fill(<span class="hljs-number">5</span>, <span class="hljs-number">1</span>)); <span class="hljs-comment">// [1, 5, 5, 5, 5, 5]</span>
<span class="hljs-built_in">console</span>.log(input.fill(<span class="hljs-number">6</span>)); <span class="hljs-comment">// [6, 6, 6, 6, 6, 6]</span>
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This list contains functions that most JavaScript developers encounter and use in their careers. Although it is long, it is by no means complete because there are so many more minor but very useful functions in JavaScript: <code>reverse()</code>, <code>sort()</code>, <code>find()</code>, <code>flat()</code>, <code>entries()</code>. I would suggest that you read about these functions because you will at any point use them in your JavaScript project.</p>
<p>Furthermore, whenever you can you should explore the core language or famous utility libraries such as lodash to deepen your JavaScript knowledge. It will result in massive productivity and you will learn to create cleaner, compact, readable, maintainable, and usable code!</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/introduction-to-11-core-javascript-functions-to-improve-code-quality/">https://www.paulsblog.dev/introduction-to-11-core-javascript-functions-to-improve-code-quality/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@kylejglenn?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Kyle Glenn</a> / <a target="_blank" href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[11 Stages To Become A JavaScript Full-Stack Engineer]]></title><description><![CDATA[Last year I created a list to kickstart your career and become a Full-Stack developer. I realized that they are still valid and want to share them here!
Paul Knulst  in  Programming • 7 min read

Last year, I have received multiple messages, mostly f...]]></description><link>https://hashnode.knulst.de/11-stages-to-become-a-javascript-full-stack-engineer</link><guid isPermaLink="true">https://hashnode.knulst.de/11-stages-to-become-a-javascript-full-stack-engineer</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Full Stack Development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Thu, 12 Jan 2023 12:40:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1673527126594/afa8564f-f235-4f91-a0fd-0937c492c46c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><strong>Last year I created a list to kickstart your career and become a Full-Stack developer. I realized that they are still valid and want to share them here!</strong></p>
<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/programming/">Programming</a> • 7 min read</p>
<hr />
<p>Last year, I have received multiple messages, mostly from newcomers that have just graduated and stepped into the world of coding. Most of the questions asked are about how to quickly improve their skills, how to become a Full Stack Developer, or how to choose a career direction.</p>
<p>While I try replying to everyone by one, I want to combine my own experiences and created a roadmap on how to become a Full-Stack Engineer that I want to share with everyone.</p>
<h2 id="heading-stage-1-html">Stage 1  - HTML</h2>
<p>Before starting with any programming language you should learn basic HTML. A good start will be the tutorial of W3Schools that teaches all the basics and also some advanced information: <a target="_blank" href="https://www.w3schools.com/html/default.asp">find it here</a></p>
<h2 id="heading-stage-2-css-grid-flex">Stage 2  -  CSS (Grid, Flex)</h2>
<p>With knowledge about HTML, you should learn about CSS to style your website. Again, W3Schools provides a good tutorial: <a target="_blank" href="https://www.w3schools.com/css/default.asp">find it here</a></p>
<h2 id="heading-stage-3-javascript-dom">Stage 3  -  JavaScript + DOM</h2>
<p>Now it's time to learn JavaScript. JavaScript is one of the most used programming languages all over the world. To learn it you can use another <a target="_blank" href="https://www.w3schools.com/js/default.asp">W3Schools tutorial</a>.</p>
<p>Also, I have written an article to learn advanced JavaScript with Gamification.</p>
<ul>
<li><p>On my blog: <a target="_blank" href="https://www.paulsblog.dev/learn-javascript-while-playing-games-gamify-your-learning/">https://www.paulsblog.dev/learn-javascript-while-playing-games-gamify-your-learning/</a></p>
</li>
<li><p>On Medium: <a target="_blank" href="https://medium.com/javascript-in-plain-english/learn-javascript-while-playing-games-gamify-your-learning-9f41d4d3dad9">https://medium.com/javascript-in-plain-english/learn-javascript-while-playing-games-gamify-your-learning-9f41d4d3dad9</a></p>
</li>
</ul>
<h2 id="heading-stage-4-nodejs">Stage 4  -  Node.js</h2>
<p>Node.js is a powerful JavaScript-based platform that is built on Google Chrome’s JavaScript V8 Engine and provides an event-driven, non-blocking (asynchronous) I/O and cross-platform runtime environment for building highly scalable server-side applications using JavaScript.</p>
<p>Start by downloading and installing the latest Node version for your OS on <a target="_blank" href="https://nodejs.dev">nodejs.dev</a>. This is mandatory for further progression as a JavaScript (TypeScript) developer.</p>
<p>Afterward, you should <a target="_blank" href="https://nodejs.dev/learn/introduction-to-nodejs">check out their introduction</a> to learn some basics about Node.js.</p>
<p>Before heading to the next stage you should make yourself familiar with the npm ecosystem. Consider answering these questions before starting with React:</p>
<ul>
<li><p>What is NPM?</p>
</li>
<li><p>How can I install something?</p>
</li>
<li><p>What is a package.json</p>
</li>
<li><p>I need package XX what should I do?</p>
</li>
</ul>
<h2 id="heading-stage-5-react">Stage 5 - React</h2>
<p>If you are proficient with JavaScript (and maybe TypeScript) you should learn React because it is the most used framework based on data from <a target="_blank" href="https://www.npmtrends.com/react-vs-angular-vs-vue-vs-preact-vs-ember-source">npmtrends.com</a>:</p>
<p><img src="https://www.paulsblog.dev/content/images/2022/09/image--46-.webp" alt="NPM Trens Report For React, Angular, Vue, Preact and Ember-Source." /></p>
<p><strong>Remember:</strong> Mastering React takes some time because it has many different concepts that all become important during development.</p>
<p>My personal advice would be to work on the three following lists step by step before continuing to MySQL:</p>
<h3 id="heading-basics">Basics</h3>
<ol>
<li><p><a target="_blank" href="https://reactjs.org/tutorial/tutorial.html#what-is-react">Understand React</a></p>
</li>
<li><p>How To Set Up Your DevEnv</p>
</li>
<li><p>JSX</p>
</li>
<li><p>Components</p>
</li>
<li><p>State</p>
</li>
<li><p>Props</p>
</li>
<li><p>Lists/Keys</p>
</li>
<li><p>Lifecycle Methods</p>
</li>
</ol>
<h3 id="heading-advanced-react-concepts">Advanced React Concepts</h3>
<ol>
<li><p>Styling</p>
</li>
<li><p>Form Handling</p>
</li>
<li><p>Data Handling</p>
</li>
<li><p>Reconciliation Process</p>
</li>
<li><p>Hooks</p>
</li>
<li><p>Custom Hooks</p>
</li>
<li><p>Context</p>
</li>
</ol>
<h3 id="heading-mastering-react">Mastering React</h3>
<ol>
<li><p>Lazy Loading</p>
</li>
<li><p>Portals</p>
</li>
<li><p>State Management</p>
</li>
<li><p>Routing</p>
</li>
<li><p>Theming</p>
</li>
<li><p>Patterns</p>
</li>
<li><p>Anti-Patterns</p>
</li>
</ol>
<p><a target="_blank" href="https://dev.to/paulknulst/simple-roadmap-to-learn-reactjs-4eoi">This article from me here on dev.to has also come links for becoming a React developer</a></p>
<h2 id="heading-stage-6-mysql">Stage 6  -  MySQL</h2>
<p>After you mastered JavaScript and React to create beautiful websites the next step will be to use persistent data within these websites. Often, this is done by connecting to a database like MySQL, PostgreSQL, or SQLite.</p>
<p><strong>For simplicity, you should learn MySQL over PostgreSQL!</strong></p>
<p>As a developer, you have to be familiar with MySQL because it is ideal for both small and large applications (PostgreSQL is an advancement in DBMS but MySQL is basic knowledge).</p>
<p>To learn MySQL you can use a <a target="_blank" href="https://www.w3schools.com/mySQl/default.asp">W3School Tutorial</a> which will teach you all the basics to understand every MySQL query you will encounter in different applications.</p>
<p>One important thing is, that you have to <strong>understand database design</strong> more than queries because in future stages you will use a technique (ORM) that helps you with queries anyway. <strong>For this, you have to understand Database Normalization. This is very important if you want to work with relational databases!</strong></p>
<h2 id="heading-stage-7-mongodb">Stage 7  -  MongoDB</h2>
<p>In contrast to MySQL, MongoDB is a document-oriented database classified as NoSQL. It uses JSON-like documents with optional schemas to store data.</p>
<p>Get familiar with NoSQL database design and <a target="_blank" href="https://www.mongodb.com/nosql-explained/data-modeling">read and understand this</a>.</p>
<h2 id="heading-stage-8-the-crud-pattern">Stage 8  -  The CRUD Pattern</h2>
<p>If you are working with persistent data it is mandatory that you understand the CRUD pattern. CRUD stands for <strong>Create</strong>, <strong>Read</strong>, <strong>Update</strong>, <strong>Delete</strong> and describes the four basic operations for persistent data.</p>
<p>It should be sufficient to read about it <a target="_blank" href="https://en.wikipedia.org/wiki/Create,_read,_update_and_delete">on Wikipedia</a>.</p>
<h3 id="heading-stage-9-orm-and-odm">Stage 9 — ORM And ODM</h3>
<p>ORM stands for <a target="_blank" href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping">Object-Relational Mapping</a>; ODM stands for Object Document Mapper. Both are techniques that are used to query and manipulate data from a database using an object-oriented paradigm.</p>
<p><strong>Often, when developers talk about ORM or ODM they refer to a library that implements any (or both) of these techniques.</strong></p>
<p>You should learn at least one of the following supporting both Database technologies:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/typeorm/typeorm"><strong>TypeOrm</strong></a> (SQL and NoSQL)</p>
</li>
<li><p><a target="_blank" href="https://www.prisma.io"><strong>Prisma</strong></a> (SQL and MongoDB)</p>
</li>
<li><p><a target="_blank" href="https://mongoosejs.com">Mongoose</a> (only MongoDB)</p>
</li>
<li><p><a target="_blank" href="https://github.com/typegoose/typegoose">Typegoose</a> (Mongoose for TypeScript)</p>
</li>
<li><p><a target="_blank" href="http://knexjs.org">Knex.js</a> (SQL)</p>
</li>
<li><p><a target="_blank" href="https://sequelize.org">sequelize</a> (SQL)</p>
</li>
</ul>
<p>Personally, I would recommend learning <strong>TypeOrm</strong> AND <strong>Prisma!</strong></p>
<h2 id="heading-stage-10-build-side-projects-to-gain-experience">Stage 10 - Build Side Projects To Gain Experience</h2>
<p>Now that you know a programming language, a framework, and how to use a database you can start creating some projects.</p>
<p>This stage is very important in becoming a full-stack developer because you never encounter real-life problems in tutorials that you will face while working on your own project.</p>
<p><strong>Here are some ideas for cool projects:</strong></p>
<h3 id="heading-goal-tracking-app">Goal Tracking App</h3>
<p>Simple app that tracks your personal goals. Save goals in a database and show them in a nice way in the front end.</p>
<h3 id="heading-clone-a-website">Clone A Website</h3>
<p>Search for a nice website and try to clone it. Copy the contents and try to extrapolate the functions that the website uses. Then try to implement it for your own website.</p>
<h3 id="heading-developer-portfolio">Developer Portfolio</h3>
<p>A very important project that you should create is a portfolio. A Portfolio is an important website that will represent yourself. If you apply for a job you will have a nice-looking website that will show your skills or introduce yourself.</p>
<p>Get inspiration here:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://betterprogramming.pub/create-your-portfolio-using-next-js-tailwind-css-stripe-and-paypal-80c723bb3fef">https://betterprogramming.pub/create-your-portfolio-using-next-js-tailwind-css-stripe-and-paypal-80c723bb3fef</a></div>
<p> </p>
<h2 id="heading-stage-11-deploy-your-side-project">Stage 11  -  Deploy Your Side Project</h2>
<p>The last stage is about deploying your project on a server. This stage is also mandatory because as a full-stack developer, you should know how to deploy your app in a production environment.</p>
<p><strong>I would recommend</strong> learning about Docker and how it can be used to host different websites. As a starting point, you can <a target="_blank" href="https://medium.com/geekculture/beginner-friendly-introduction-into-devops-with-docker-on-windows-6aac2de2db33">check out an introduction from me to Docker here</a>. If you are already into Docker <a target="_blank" href="https://levelup.gitconnected.com/how-to-setup-traefik-v2-with-automatic-lets-encrypt-certificate-resolver-83de0ed0f542">you can check out this article</a> where I explain how to deploy Traefik (a load balancer) that automatically creates SSL certificates for every service you will deploy on your server.</p>
<p>If you have read and understood both articles you can check out this article:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.paulsblog.dev/deploy-any-ssl-secured-website-with-docker-and-traefik/">https://www.paulsblog.dev/deploy-any-ssl-secured-website-with-docker-and-traefik/</a></div>
<p> </p>
<p>It describes how you can deploy a simple website in a Docker environment that already runs a Traefik.</p>
<hr />
<p><strong>Congratulations, you can call yourself a Full Stack developer!</strong></p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>Hopefully, these resources will help you to become a Full Stack Developer.</p>
<p>Also, if you have any questions, ideas, or recommendations, please jot them down below. I try to answer them if possible.</p>
<p>Writing has always been my passion and it gives me pleasure to help and inspire people.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/11-stages-to-become-a-javascript-full-stack-engineer/">https://www.paulsblog.dev/11-stages-to-become-a-javascript-full-stack-engineer/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@casparrubin?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Caspar Camille Rubin</a> / <a target="_blank" href="https://unsplash.com/@casparrubin?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Learn JavaScript while Playing Games - Gamify Your Learning]]></title><description><![CDATA[Paul Knulst in Programming • 6 min read

Within this article, I want to showcase different websites you can use to learn JavaScript while playing games. This method is called gamification and is a well-known technique nowadays.
Introduction
Often if ...]]></description><link>https://hashnode.knulst.de/learn-javascript-while-playing-games-gamify-your-learning</link><guid isPermaLink="true">https://hashnode.knulst.de/learn-javascript-while-playing-games-gamify-your-learning</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Beginner Developers]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Tue, 03 Jan 2023 19:00:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672771868339/a4c41591-a399-4ef1-b995-e6e42941cff6.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a> in <a target="_blank" href="https://www.paulsblog.dev/tag/programming/">Programming</a> • 6 min read</p>
<hr />
<p>Within this article, I want to showcase different websites you can use to learn JavaScript while playing games. This method is called gamification and is a well-known technique nowadays.</p>
<h2 id="heading-introduction">Introduction</h2>
<p>Often if you learn a new technology or language it could happen that you cannot keep your momentum. This is caused by the amount of technology that seems endless. It can get difficult to stay engaged with particularly complicated technology and learning gets stuck.</p>
<p>Gamification is a good solution to this problem. It uses a simple strategic attempt to motivate and engage users while learning something new. It is a technique of adding typical design elements from games to enhance the learning process. This is done by leveraging people’s natural desire for socializing, learning, mastery, competition, achievement, status, or self-expression. Early implementation of Gamification uses a simple reward system for players after they accomplish their tasks to engage them. The rewards include point scoring, achievement badges, or virtual currency to use.</p>
<p>Another approach of Gamification transforms the task itself in a game. This is accomplished by including a meaningful choice, an onboarding tutorial, or adding a narrative.</p>
<p>Within the next chapter, I will show different websites that can all be used to learn JavaScript by playing games or solving puzzles.</p>
<h2 id="heading-checkio">CheckiO</h2>
<p>CheckiO is a website that teaches programming by providing an online game world with many Gamification features like point scoring, leaderboard, or socialization. It helps you to improve your coding skills. Also, it contains many coding games for beginners and advanced programmers. You can complete exercises using Python or TypeScript that cover a variety of topics, including strings, loops, objects, classes, exceptions, and problem-solving. After completing challenges you will earn points, unlock new games, and advance to higher levels. Furthermore, there is a comment section and a Forum where you can consult the help or see how other users solved the puzzle.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=HFq101OFb0g">https://www.youtube.com/watch?v=HFq101OFb0g</a></div>
<p> </p>
<p>Here is a link to the website:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://checkio.org">https://checkio.org</a></div>
<p> </p>
<h2 id="heading-codecombat">CodeCombat</h2>
<p>CodeCombat is a hack-and-slash fantasy game that could be used to learn coding fundamentals. CodeCombat puts the player into action and uses programming to help you survive in a dungeon or battlefield. You will control your personal on-screen character through every challenge.</p>
<p>It contains hundreds of levels throughout different courses, including computer science, game development, and web development.</p>
<p>Within the game, you have the ability to control your personally created character and let him run down a hallway to taunt an ogre through programming.</p>
<p>Every level is set up like a puzzle and could only be solved by typing the correct code snippet. Also, after you put in a snippet the game will be executed and you will see the outcome directly.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=rOXRBQIXvlI">https://www.youtube.com/watch?v=rOXRBQIXvlI</a></div>
<p> </p>
<p>Here is a link to the website:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://codecombat.com">https://codecombat.com</a></div>
<p> </p>
<h2 id="heading-twilioquest">TwilioQuest</h2>
<p>In TwilioQuest, you will lead your intrepid crew on a mission to The Cloud. It is a free PC role-playing game that was inspired by the classics of the 16-bit era.</p>
<p>Furthermore, it is an educational game that was designed to teach programming in JavaScript or Python to new developers. The game will prepare you for real-world problems by helping you configure a local development environment and explaining tools that are used by professional programmers.</p>
<p>You will learn how to use your terminal, learn how to code in Python or JavaScript, and contribute to Open Source projects. All these practical software engineering skills will be covered if you play TwilioQuest</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=oNuB_GYqkR4">https://www.youtube.com/watch?v=oNuB_GYqkR4</a></div>
<p> </p>
<p>Here is a link to the website:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.twilio.com/quest">https://www.twilio.com/quest</a></div>
<p> </p>
<h2 id="heading-elevator-saga">Elevator Saga</h2>
<p><strong>This is a game of programming! Your task is to program the movement of elevators, by writing a program in JavaScript.</strong></p>
<p>Elevator Saga is a game where you have to use JavaScript to transport people with an elevator in an efficient manner. While progressing through the different stages you have to complete even more difficult challenges.</p>
<p>Only efficient programs will be able to complete all challenges.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.youtube.com/watch?v=J1ie70_rDXE">https://www.youtube.com/watch?v=J1ie70_rDXE</a></div>
<p> </p>
<p>Here is a link to the website: <a target="_blank" href="https://play.elevatorsaga.com">https://play.elevatorsaga.com</a></p>
<h2 id="heading-jsdares">jsdares</h2>
<p>At jsdares.com you will learn JavaScript programming by completing <strong>“dares”.</strong> These dares are short puzzles in which you minimize the number of functions used to complete the task. They are very simple in the beginning but will become more difficult as you progress.</p>
<p>At the moment jsdares only provide a small number of dares but they are working on a greater collection to start with. Also, you have to opportunity to create and share your own dares.</p>
<p><a target="_blank" href="https://www.paulsblog.dev/learn-javascript-while-playing-games-gamify-your-learning/#jsdares"><img src="https://www.paulsblog.dev/content/images/2022/09/image--42-.webp" alt="Learn JavaScript by Gamification and jsdares.com" class="image--center mx-auto" /></a></p>
<p>Here is a link to the website: <a target="_blank" href="https://jsdares.com">https://jsdares.com</a></p>
<h2 id="heading-warriorjs">WarriorJS</h2>
<p><strong>Legend tells of a legendary sword, forgotten in the ruins of an abandoned tower. Thousands of warriors have set off on a quest for the sword, whose bearer would become enlightened in the JavaScript language.</strong></p>
<p>WarriorJS is a learning platform for JavaScript that teaches you JavaScript while you playing a role-playing game. This game is designed for new or advanced JavaScript programmers and will put your skills to the test!</p>
<p><a target="_blank" href="https://www.paulsblog.dev/learn-javascript-while-playing-games-gamify-your-learning/#warriorjs"><img src="https://www.paulsblog.dev/content/images/2022/09/image--43-.webp" alt="Learn JavaScript by Gamification and WarrioJs.com" /></a></p>
<p>Here is a link to the website:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://warriorjs.com">https://warriorjs.com</a></div>
<p> </p>
<h2 id="heading-jsrobot">JSRobot</h2>
<p>JSRobot is a game where you will control a robot that collects coins and should reach the end of a level. All programming is done in JavaScript and every challenge contains information about all JavaScript functions needed to complete it.</p>
<p>Unfortunately, it is a very simple game without a fancy UI and presentation. But it can still be used to practice your coding skills.</p>
<p><a target="_blank" href="https://www.paulsblog.dev/learn-javascript-while-playing-games-gamify-your-learning/#jsrobot"><img src="https://www.paulsblog.dev/content/images/2022/09/image--44-.webp" alt="Learn JavaScript by Gamification and JSRobot" /></a></p>
<p>Here is a link to the website: <a target="_blank" href="https://lab.reaal.me/jsrobot/#level=1&amp;language=en">https://lab.reaal.me/jsrobot/#level=1&amp;language=en</a></p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>If you try any of these games they could help you to start learning JavaScript and becoming a JavaScript developer. They are encouraging funny and have a lot to offer. Besides, you will get in touch with programming and will gain deeper knowledge about syntax, algorithms, and other important concepts in programming.</p>
<p>Personally, I would recommend using CheckiO, TwilioQuest, CodeCombat, and WarriorJS. They are especially helpful for people who want to work as a developer.</p>
<p>JSRobot, ElevatorSaga, and jsdares are good to practice some basic functionality and should not be avoided but I won’t recommend starting with these games.</p>
<p>I hope you will test any of these sites and will find them as helpful as I have. If so I would love to hear your thoughts. Also, if you have any questions, ideas, or recommendations, please jot them down below. I try to answer them if possible.</p>
<p>Additionally, I would love to hear your thoughts and your feedback about these Gamification resources. Furthermore, share this article with your friends and colleagues to also learn JavaScript by Gamification.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/learn-javascript-while-playing-games-gamify-your-learning/">https://www.paulsblog.dev/learn-javascript-while-playing-games-gamify-your-learning/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo: <a target="_blank" href="https://www.freepik.com/vectors/playing-game">Playing game vector created by pikisuperstar</a> / <a target="_blank" href="https://www.freepik.com">www.freepik.com</a></p>
]]></content:encoded></item><item><title><![CDATA[Deploy Any SSL Secured Website With Docker And Traefik]]></title><description><![CDATA[Paul Knulst  in  Docker • 8 min read

What is Docker?
Docker is a platform that is used for developing, shipping, and running all kinds of applications. It enables the separation of applications from the infrastructure so that software can be deliver...]]></description><link>https://hashnode.knulst.de/deploy-any-ssl-secured-website-with-docker-and-traefik</link><guid isPermaLink="true">https://hashnode.knulst.de/deploy-any-ssl-secured-website-with-docker-and-traefik</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Developer]]></category><category><![CDATA[Devops]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Sun, 01 Jan 2023 20:44:11 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1672605378705/9f225a73-cee0-4d8a-bea1-c344ec26fdf8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/docker/">Docker</a> • 8 min read</p>
<hr />
<h2 id="heading-what-is-docker">What is Docker?</h2>
<p>Docker is a platform that is used for developing, shipping, and running all kinds of applications. It enables the separation of applications from the infrastructure so that software can be delivered without concerns about the operating system of the host or the end user.</p>
<p>To install Docker on your machine download the installation package from the official docker page here: <a target="_blank" href="https://docs.docker.com/get-docker/">https://docs.docker.com/get-docker/</a></p>
<p>To work with Docker you should get familiar with the following keywords/concepts: <strong>Container, Dockerfiles, Docker-Images, Docker-Compose</strong></p>
<h3 id="heading-container">Container</h3>
<p>A container describes a unit of software that merges code and every dependency to run an application. They provide a quick and safe way to run an application on several different devices. Also, it secures the application because of different isolation capabilities.</p>
<h3 id="heading-docker-images">Docker Images</h3>
<p>A Docker image is a read-only template that is used for creating the container to run the application within the Docker platform. It packages up applications and preconfigured server environments to create the container which will be deployed. On the official <a target="_blank" href="https://hub.docker.com/">DockerHub</a> several already configured images are available to use. You can use the search function to find any image and deploy it on your system with:</p>
<pre><code class="lang-bash">    $ docker run -it <span class="hljs-string">"NAME_OF_IMAGE_FROM_DOCKERHUB"</span>
</code></pre>
<h3 id="heading-dockerfiles">Dockerfiles</h3>
<p>A Dockerfile is a plain text document that contains every command to set up an image. It contains the base image to use (operating system) and the additional software that should be installed on the resulting image.</p>
<p>Every time custom content is used for a Docker container a Dockerfile has to be created and filled with the missing information.</p>
<p>As an example, the following two snippets will demonstrate the usage of a Dockerfile. The first one will extend an NGINX image to serve a simple HTML file that is shown in the second snippet.</p>
<pre><code class="lang-plaintext">    FROM nginx
    COPY index.html /usr/share/nginx/html
</code></pre>
<pre><code class="lang-HTML">    <span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>How To Deploy Docker<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Learning IT. Level Up.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>This is a simple static website, built and deliver from a docker container<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://www.paulsblog.dev/"</span>&gt;</span>Check out my blog here<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>If you place both files within the same folder you can run it with:</p>
<pre><code class="lang-bash">    $ docker build -t nginx-sample .
    $ docker run --name test-nginx -d -p 8080:80 nginx-sample
</code></pre>
<p>Now open your favourite web browser and hit <code>http://localhost:8080</code> (or <code>http://your-ip:8080</code> if you working on a server)</p>
<h3 id="heading-docker-compose">Docker Compose</h3>
<p>Docker Compose is a tool that was developed to create multi-container applications easily by using a YAML file that could contain several Docker containers.</p>
<p>One big advantage over simple Dockerfiles is that it enables the use of a simple “stack” file for your whole application stack. You can keep one Compose file in your repository and deploy everything with one simple command: <code>docker-compose up -d</code></p>
<p>To install Docker Compose follow the official tutorial: <a target="_blank" href="https://docs.docker.com/compose/install/">https://docs.docker.com/compose/install/</a></p>
<p>Afterwards, read the <a target="_blank" href="https://docs.docker.com/get-started/08_using_compose/">official Docker Compose guide</a> to fully understand how a Docker Compose file is created because this will not be part of this article.</p>
<h2 id="heading-what-is-traefik">What is Traefik?</h2>
<blockquote>
<p><strong>Traefik Makes Networking Boring</strong><br /><strong>Cloud-Native Networking Stack That Just Works.</strong></p>
</blockquote>
<p>Traefik is used to forward incoming requests to a service deployed in a Docker environment. Also, it can create Let<a target="_blank" href="https://letsencrypt.org/">’s Encrypt</a> SSL certificates automatically for every domain which is managed by <strong>Traefik</strong>.</p>
<p>To set up a local Traefik service within your Docker environment you can <a target="_blank" href="https://gist.github.com/paulknulst/68e5e63badaa6a9ac80b4227ca07baee#file-docker-compose-traefik-yml">download this Docker Compose file</a> and execute the following commands:</p>
<p>1. Create an external network that is used for Traefik</p>
<pre><code class="lang-bash">    $ docker create network traefik-public
</code></pre>
<p>2. Export needed variables</p>
<pre><code class="lang-bash">    $ <span class="hljs-built_in">export</span> PRIMARY_DOMAIN=yourdomain.de
    $ <span class="hljs-built_in">export</span> TRAEFIK_SSLEMAIL=youremai@yourdomain.de
</code></pre>
<p>3. Deploy</p>
<pre><code class="lang-bash">    $ docker-compose up -d
</code></pre>
<p>To access the Traefik dashboard you can hit <code>[https://dashboard.yourdomain.de](https://dashboard.yourdomain.de/)</code> and login with:</p>
<pre><code class="lang-plaintext">    username: devadmin 
    password: devto
</code></pre>
<p><strong>To get a deeper understanding of Traefik you can read about</strong> <a target="_blank" href="https://www.paulsblog.dev/how-to-setup-traefik-with-automatic-letsencrypt-certificate-resolver/"><strong>How To Deploy it within this article</strong></a>.</p>
<h2 id="heading-set-up-and-deploy-your-website">Set Up And Deploy Your Website</h2>
<h3 id="heading-create-a-simple-website">Create A Simple Website</h3>
<p>After you learned the basics and set up a Traefik instance you can start creating a Dockerfile and a Compose file to create a Docker Container (service) for your website.</p>
<p>To simplify this procedure I will deploy a <a target="_blank" href="https://github.com/paulknulst/personal-dashboard">simple dashboard project that I forked from an awesome list on GitHub</a>. To get a copy of that dashboard either <a target="_blank" href="https://github.com/paulknulst/personal-dashboard/archive/refs/tags/v1.0.0.zip">download the latest zip file here</a> or clone the repository:</p>
<pre><code class="lang-bash">    $ git <span class="hljs-built_in">clone</span> git@github.com:paulknulst/personal-dashboard.git
</code></pre>
<p><strong>In contrast to the original repository, I have adjusted the index.html and the CSS file to be kind of SEO optimized and have 5 items per row instead of 6.</strong></p>
<hr />
<p>Create a new folder (personal-dashboard) and extract (or move) the project files into a subfolder named <code>html</code>. Now, you should open it in your favorite IDE, copy the <strong>config.sample.json</strong> to <strong>config.json</strong> and open it.</p>
<p>Change it to fulfill your needs and provide your own personal links. I will use the following settings:</p>
<pre><code class="lang-json">    {
        <span class="hljs-attr">"title"</span> : <span class="hljs-string">"Pauls Simple Dashboard"</span>,
        <span class="hljs-attr">"items"</span> : [
            {
                <span class="hljs-attr">"alt"</span> : <span class="hljs-string">"Github"</span>,
                <span class="hljs-attr">"icon"</span> : <span class="hljs-string">"fab fa-github"</span>,
                <span class="hljs-attr">"link"</span> : <span class="hljs-string">"https://github.com/paulknulst"</span>
            },
            {
                <span class="hljs-attr">"alt"</span> : <span class="hljs-string">"Twitter"</span>,
                <span class="hljs-attr">"icon"</span> : <span class="hljs-string">"fab fa-twitter"</span>,
                <span class="hljs-attr">"link"</span> : <span class="hljs-string">"https://twitter.com/paulknulst"</span>
            },
            {
                <span class="hljs-attr">"alt"</span> : <span class="hljs-string">"Portfolio"</span>,
                <span class="hljs-attr">"icon"</span> : <span class="hljs-string">"fab fa-at"</span>,
                <span class="hljs-attr">"link"</span> : <span class="hljs-string">"https://www.paulknulst.de"</span>
            },
            {
                <span class="hljs-attr">"alt"</span> : <span class="hljs-string">"Medium"</span>,
                <span class="hljs-attr">"icon"</span> : <span class="hljs-string">"fab fa-medium"</span>,
                <span class="hljs-attr">"link"</span> : <span class="hljs-string">"https://medium.knulst.de"</span>
            }
        ]
    }
</code></pre>
<h3 id="heading-create-dockerfile">Create Dockerfile</h3>
<p>The Dockerfile will be created in the simple-dashboard folder and only contain the base image and one copy command to add the HTML folder into the container.</p>
<pre><code class="lang-plaintext">    FROM nginx
    COPY html /usr/share/nginx/html
</code></pre>
<h3 id="heading-create-compose-file">Create Compose File</h3>
<p>The Compose file will be created in the simple-dashboard folder too. It contains every setting to set up your personal dashboard within your Docker environment. Furthermore, it will use Traefik to enable Let’s Encrypt certificate generation.</p>
<p>The complete Docker Compose file will be explained afterwards:</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">version:</span> <span class="hljs-string">'3.4'</span>

    <span class="hljs-attr">services:</span>
      <span class="hljs-attr">web:</span>
        <span class="hljs-attr">image:</span> <span class="hljs-string">personal-dashboard</span>
        <span class="hljs-attr">build:</span>
          <span class="hljs-attr">context:</span> <span class="hljs-string">./</span>
          <span class="hljs-attr">dockerfile:</span> <span class="hljs-string">Dockerfile</span>
        <span class="hljs-attr">networks:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik-public</span>
        <span class="hljs-attr">healthcheck:</span>
          <span class="hljs-attr">test:</span> <span class="hljs-string">curl</span> <span class="hljs-string">--fail</span> <span class="hljs-string">http://localhost</span> <span class="hljs-string">||</span> <span class="hljs-string">exit</span> <span class="hljs-number">1</span>
          <span class="hljs-attr">interval:</span> <span class="hljs-string">60s</span>
          <span class="hljs-attr">retries:</span> <span class="hljs-number">5</span>
          <span class="hljs-attr">start_period:</span> <span class="hljs-string">20s</span>
          <span class="hljs-attr">timeout:</span> <span class="hljs-string">10s</span>
        <span class="hljs-attr">labels:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.enable=true</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.docker.network=traefik-public</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.constraint-label=traefik-public</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.personal-dashboard-http.rule=Host(`personal-dashboard.${PRIMARY_DOMAIN}`)</span> 
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.personal-dashboard-http.entrypoints=http</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.personal-dashboard-http.middlewares=https-redirect</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.personal-dashboard-https.rule=Host(`personal-dashboard.${PRIMARY_DOMAIN}`)</span> 
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.personal-dashboard-https.entrypoints=https</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.personal-dashboard-https.tls=true</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.personal-dashboard-https.tls.certresolver=le</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.services.personal-dashboard.loadbalancer.server.port=80</span>
          <span class="hljs-comment"># - traefik.http.routers.personal-dashboard-https.middlewares=security-headers</span>
        <span class="hljs-attr">restart:</span> <span class="hljs-string">unless-stopped</span>


    <span class="hljs-attr">networks:</span>
      <span class="hljs-attr">traefik-public:</span>
        <span class="hljs-attr">external:</span> <span class="hljs-literal">true</span>
</code></pre>
<p>Within this Compose file, you can see that we define an <strong>image</strong> in Line 5 (personal-dashboard:v1). Also, we add a <strong>build</strong> section because the image does not officially exist. Within the build section, we define that the Dockerfile which is located in the same directory should be used for creating the image.</p>
<p>The <strong>networks</strong> part (Line 9–10) contains the external network that was created during Traefik installation. Also, within Line 33–35 we especially define the network as an external network for this Compose service.</p>
<p>Another very important section is <strong>healthcheck</strong> (Line 11–16). For me, this part should be contained in every Docker Compose file because it helps to know if a Docker Container is running correctly without any runtime errors. For further reading have a look at <a target="_blank" href="https://www.paulsblog.dev/how-to-successfully-implement-a-healthcheck-in-docker-compose/">this article that explains how to implement different types of health checks.</a> that explains how to implement different types of health checks.</p>
<p>The most important part of this Compose file is the <strong>labels</strong> section. This section contains several labels that are important for Traefik. First, we enable Traefik (Line 18) for this service because in our Traefik service there was an option to not be working for every Docker service. Within Line 19 we define which external network is used for traefik.</p>
<p>In Line 20 we set the <strong>constraint-label</strong> of this service. Normally this is an optional setting and only used if we have more than one Traefik instance deployed to our Docker environment.</p>
<p>From Line 21–25 we define the URL of the service for HTTP and for HTTPS. For the HTTP part, we also activate a middleware from the Traefik configuration that will always redirect HTTP requests to HTTPS (Line 23).</p>
<p>The labels in Line 26 and 27 are necessary to create automatic SSL certificates for this Docker service.</p>
<p>Line 28 is needed to “expose” the internal Docker service port to the Traefik instance. All incoming requests to the URL defined in Line 21/24 will be forwarded to the Docker service on the Port mentioned within this label.</p>
<p>Line 29 is commented out because it is an extra middleware that I personally use in my Docker environment. I did not include this middleware within the Traefik installation but if you want to read about it have a look at <a target="_blank" href="https://www.paulsblog.dev/harden-your-website-with-traefik-and-security-headers/">my article about hardening any Traefik service</a>.</p>
<hr />
<p>Keep in mind that this file only works correctly if you followed the above steps to set up Traefik. If not you have to adjust <code>PRIMARY_DOMAIN</code>, your <strong>Docker external network,</strong> and the entry points used for Traefik (<code>http</code>, <code>https</code>).</p>
<h3 id="heading-deploy-the-website">Deploy The Website</h3>
<p>Now, that you finished creating the needed files you can open your CLI, switch to your project folder and start the deployment process by executing:</p>
<pre><code class="lang-bash">    $ docker-compose up -d --build
</code></pre>
<p>After the deployment is complete you can open your dashboard in your browser and if you followed this tutorial it should look like this:</p>
<p><img src="https://www.paulsblog.dev/content/images/2022/09/image--40-.webp" alt="Deploy Any SSL Secured Website With Docker And Traefik - Screenshot of a personal dashboard" class="image--center mx-auto" /></p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>I hope you enjoyed reading my tutorial and are now able to deploy your own website within a Docker container and enhance it with Traefik and SSL certificates.</p>
<p>Keep in mind that you can replace the HTML folder with every other static website. Also, you can extend the Compose file with other services like a database for your website. Furthermore, you can exchange the image with another public one from Docker Hub.</p>
<p>To help you, I have written another tutorial on how to deploy a Kimai2 Timetracking website that also includes a Docker Hub image, Traefik settings, a database, and an NGINX.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://medium.com/geekculture/how-to-setup-kimai2-timetracking-locally-or-on-your-server-with-docker-compose-1287d9bc3722">https://medium.com/geekculture/how-to-setup-kimai2-timetracking-locally-or-on-your-server-with-docker-compose-1287d9bc3722</a></div>
<p> </p>
<p>I would love to hear your feedback about this tutorial. Furthermore, if you already run a Traefik installation and use a different approach please comment here and explain what you have done differently. Also, if you have any questions, please jot them down below. I try to answer them if possible.</p>
<p>This blog post was initially published on my own blog at <a target="_blank" href="https://www.paulsblog.dev/deploy-any-ssl-secured-website-with-docker-and-traefik/">https://www.paulsblog.dev/deploy-any-ssl-secured-website-with-docker-and-traefik/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<h2 id="heading-support-this-content"><strong>🙌 Support this content</strong></h2>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/"><strong>supporting me</strong></a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst"><strong>buy me a coffee</strong></a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/"><strong>sign up for my newsletter</strong></a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/"><strong>contribute page</strong></a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@arianismmm">Arian Darvishi</a> / <a target="_blank" href="https://unsplash.com">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Improve Your Workflow With ChatGPT]]></title><description><![CDATA[Paul Knulst in Artificial intelligence • 5 min read

Introduction
In the last weeks, everyone has seen a big hype surrounding the new tool called ChatGPT which can basically answer every question you might ask.
This tool can be mind-blowing and playi...]]></description><link>https://hashnode.knulst.de/improve-your-workflow-with-chatgpt</link><guid isPermaLink="true">https://hashnode.knulst.de/improve-your-workflow-with-chatgpt</guid><category><![CDATA[AI]]></category><category><![CDATA[Productivity]]></category><category><![CDATA[writing]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Artificial Intelligence]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Wed, 21 Dec 2022 13:55:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1671627963734/E6MmIMw0-.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a> in <a target="_blank" href="https://www.paulsblog.dev/tag/artificial-intelligence/">Artificial intelligence</a> • 5 min read</p>
<hr />
<h2 id="heading-introduction">Introduction</h2>
<p>In the last weeks, everyone has seen a big hype surrounding the new tool called ChatGPT which can basically answer every question you might ask.</p>
<p>This tool can be mind-blowing and playing around with it will result in having much fun because you can ask whatever you want and ask again and again and again...</p>
<p>But, it can also help you with your marketing strategy and content creation if used correctly.</p>
<p>In this short article, you will learn about five different ways to harness the power of ChatGPT in your own content creation workflow.</p>
<h2 id="heading-1-ask-chatgpt-to-come-up-with-ideas-for-content">1.  Ask ChatGPT to come up with ideas for content</h2>
<p>Sometimes it can be challenging for a content creator to come up with new ideas and produce interesting content. Also, if your main marketing strategy consists of creating content for your blog, it could become challenging to find new ideas.</p>
<p>Luckily, with ChatGPT, you can now easily ask to offer some ideas on different topics that can result in a fresh and interesting article. For example, I asked ChatGPT "Give me some ideas for articles about productivity and time management" and it produces five article ideas and a short summary:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/improve-your-workflow-with-chatgpt/#1-ask-chatgpt-to-come-up-with-ideas-for-content"><img src="https://www.paulsblog.dev/content/images/2022/12/image--2-.webp" alt="Improve Your Workflow With ChatGPT - results of article idea question" class="image--center mx-auto" /></a></p>
<p>Afterward, it is really important to read and review ChatGPT answers because they are not always correct. However, if the ideas of ChatGPT contain valid information it could be enough input you need to write your next article.</p>
<h2 id="heading-2-let-chatgpt-generate-the-first-draft">2. Let ChatGPT generate the first draft</h2>
<p>If you have an idea about a blog post but are short on time you could use ChatGPT to generate a draft. This draft can then be used as a baseline to start with your final draft. <strong>Always keep in mind that you have to review the answer to ChatGPT before you use it!</strong></p>
<p>For example, if you take one of the provided titles before you could ask ChatGPT to create a draft based on its own answer.</p>
<p><a target="_blank" href="https://www.paulsblog.dev/improve-your-workflow-with-chatgpt/#2-let-chatgpt-generate-the-first-draft"><img src="https://www.paulsblog.dev/content/images/2022/12/image--3--1.webp" alt="Improve Your Workflow With ChatGPT - result of generate an article draft" class="image--center mx-auto" /></a></p>
<h2 id="heading-3-let-chatgpt-come-up-with-a-title-for-your-content">3. Let ChatGPT come up with a title for your content</h2>
<p>One of the biggest benefits of tools like ChatGPT is that it could come up with some good titles easily. And as a content creator, you will know that a good title is very important. For example, you can have the best article explaining why a specific technique should be used, but if the title does not pull people, it will never be read.</p>
<p>Now, after you improved the draft from ChatGPT and filled it with your own researched items you can ask ChatGPT to produce some high-converting titles for your article.</p>
<p><a target="_blank" href="https://www.paulsblog.dev/improve-your-workflow-with-chatgpt/#3-let-chatgpt-come-up-with-a-title-for-your-content"><img src="https://www.paulsblog.dev/content/images/2022/12/image--4-.webp" alt="Improve Your Workflow With ChatGPT - results of asking for titles" class="image--center mx-auto" /></a></p>
<p>You can either choose to take one of the titles ChatGPT generates or you could use them as inspiration for your own title.</p>
<h2 id="heading-4-ask-chatgtp-to-help-you-with-your-research">4. Ask ChatGTP to help you with your research.</h2>
<p>If you are researching information for your content you will normally use Google or Wikipedia as a primary source. With ChatGPT, you now have another tool that you can use.</p>
<p>You can ask ChatGPT questions about studies or find a source of information. This should be seen as a major advantage.</p>
<p>Given the example with the Mindfulness draft, you can ask ChatGPT, "Which Studies have shown that mindfulness practices, such as meditation, can improve our ability to concentrate and focus on tasks?". It will answer the studies and summarize the results:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/improve-your-workflow-with-chatgpt/#4-ask-chatgtp-to-help-you-with-your-research"><img src="https://www.paulsblog.dev/content/images/2022/12/image--5-.webp" alt="Improve Your Workflow With ChatGPT - results of the question" class="image--center mx-auto" /></a></p>
<p>Next, ask ChatGPT to state the sources for the studies and it will output where you can find them:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/improve-your-workflow-with-chatgpt/#4-ask-chatgtp-to-help-you-with-your-research"><img src="https://www.paulsblog.dev/content/images/2022/12/image--6-.webp" alt="Improve Your Workflow With ChatGPT - results of asking for sources and links" class="image--center mx-auto" /></a></p>
<p>All these answers will be a good starting point to start your research and produce high-quality content.</p>
<h2 id="heading-5-allow-chatgpt-to-shorten-the-text-for-platforms-with-character-limits">5. Allow ChatGPT to shorten the text for platforms with character limits.</h2>
<p>As you often write articles you maybe want to promote them on Instagram, LinkedIn, Twitter, and Pinterest. To do this you need to have a short summary or a resized article while maintaining the message of the post.</p>
<p>To do this paste the whole article into ChatGPT and ask to shorten this text to X characters while maintaining the tone.</p>
<p>For example, if you use the article draft from the beginning and let ChatGPT to shorten it, it will produce the following summarized sentences:</p>
<p><a target="_blank" href="https://www.paulsblog.dev/improve-your-workflow-with-chatgpt/#5-allow-chatgpt-to-shorten-the-text-for-platforms-with-character-limits"><img src="https://www.paulsblog.dev/content/images/2022/12/image--7-.webp" alt="Improve Your Workflow With ChatGPT - results of asking ChatGPT to generate a short version of an article for Twitter or Instagram" class="image--center mx-auto" /></a></p>
<p>Keep in mind that the resulting text should be not posted as is. <strong>Proofreading is always needed</strong>. However, it will be a fantastic baseline for a shorter version of your article.</p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>Like many other A.I. tools that were released recently, ChatGPT is a huge step forward. You could either avoid it or use it. But if used properly, ChatGPT can be a good tool for you.</p>
<p>In the right hands, it will help to provide much better content because it can be used easily without any major obstacles.</p>
<p><strong>But, always keep in mind: Do not use answers of ChatGPT without proofreading!</strong></p>
<p>I hope this article gave you a quick and neat overview of how you can use ChatGPT to create better content more frequently.</p>
<p>I would love to hear your feedback! Furthermore, if you already used ChatGPT in your workflow to create some content, please comment here and explain what you have done. Also, if you have any questions, please ask them in the comments. I try to answer them if possible.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/improve-your-workflow-with-chatgpt/">https://www.paulsblog.dev/improve-your-workflow-with-chatgpt/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de/">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<h2 id="heading-support-this-content">🙌 Support this content</h2>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Title Photo by <a target="_blank" href="https://www.pexels.com/@pixabay/">Pixabay</a> / <a target="_blank" href="https://www.pexels.com/photo/clear-light-bulb-355948/">Pexels</a></p>
]]></content:encoded></item><item><title><![CDATA[Patterns And Best Practices In JavaScript: Dealing With Callback Functions]]></title><description><![CDATA[Paul Knulst  in  Programming • 6 min read

As in any other programming language, JavaScript has a number of best practices and associated bad practices. Due to its dynamic properties, JavaScript also has various pitfalls.
Callback functions are funct...]]></description><link>https://hashnode.knulst.de/patterns-and-best-practices-in-javascript-dealing-with-callback-functions</link><guid isPermaLink="true">https://hashnode.knulst.de/patterns-and-best-practices-in-javascript-dealing-with-callback-functions</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[best practices]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Frontend Development]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Tue, 13 Dec 2022 07:58:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670918226369/1qtfFXB_X.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/programming/">Programming</a> • 6 min read</p>
<hr />
<p>As in any other programming language, JavaScript has a number of best practices and associated bad practices. Due to its dynamic properties, JavaScript also has various pitfalls.</p>
<p>Callback functions are functions that are passed as parameters to other functions and called by them. They are a common design pattern in asynchronous JavaScript development. The basic (not yet optimal) structure of this design pattern is as follows:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params">callback</span>) </span>{
  <span class="hljs-comment">/* ... */</span>
  callback();
  <span class="hljs-comment">/* ... */</span>
}
</code></pre>
<p>The <code>doStuff()</code> function expects a function as a parameter and calls it at a specific point in time:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">justLogSomething</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'callback called'</span>);
}
doStuff(justLogSomething);  <span class="hljs-comment">// Output: "callback called"</span>
</code></pre>
<h2 id="heading-check-the-callback-function-type">Check The Callback Function Type</h2>
<p>Due to the weak typing of JavaScript, a function that expects a (callback) function as a parameter can in principle also be passed any other value (or no value at all). However, calling this supposed function inevitably leads to a type error ("callback is not a function"). Based on the example above, the following calls would be possible, but not advisable:</p>
<pre><code class="lang-javascript">doStuff(<span class="hljs-number">1337</span>);              <span class="hljs-comment">// type error -&gt; number</span>
doStuff(<span class="hljs-string">'Paul Knulst'</span>);     <span class="hljs-comment">// type error -&gt; string</span>
doStuff();                  <span class="hljs-comment">// type error -&gt; undefined</span>
</code></pre>
<p>For this reason, it is important to first check the type of the callback parameter inside the function and make sure that it really is a function. This can be achieved using the <code>typeof</code> operator, as shown in the following listing. If this returns the value "function" for the passed parameter, it is a function and nothing stands in the way of calling it:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params">callback</span>) </span>{
  <span class="hljs-comment">/* ... */</span>
  <span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> callback === <span class="hljs-string">'function'</span>) {
    callback();
  }
  <span class="hljs-comment">/* ... */</span>
}
</code></pre>
<p>If there are several places within a function where the callback function could be called, this check would have to precede all these places:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params">callback</span>) </span>{
  <span class="hljs-comment">/* ... */</span>
  <span class="hljs-keyword">if</span>(someBool) {
    <span class="hljs-comment">/* ... */</span>
    <span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> callback === <span class="hljs-string">'function'</span>) {
      callback();
    }
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">/* ... */</span>
    <span class="hljs-keyword">if</span>(<span class="hljs-keyword">typeof</span> callback === <span class="hljs-string">'function'</span>) {
      callback();
    }
  }
  <span class="hljs-comment">/* ... */</span>
}
</code></pre>
<p>This can be avoided if, as in the following listing, you carry out the check at the beginning of the function and, in the event that the callback parameter is not a function, simply redefine it with an anonymous empty function. This simple but efficient trick secures all the following calls to the callback function in one fell swoop:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params">callback</span>) </span>{
  <span class="hljs-keyword">if</span>(!(<span class="hljs-keyword">typeof</span> callback === <span class="hljs-string">'function'</span>)) {
    callback = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{};  <span class="hljs-comment">// redefinition</span>
  }
  <span class="hljs-comment">/* ... */</span>
  <span class="hljs-keyword">if</span>(someBool) {
    <span class="hljs-comment">/* ... */</span>
    callback();
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">/* ... */</span>
    callback();
  }
  <span class="hljs-comment">/* ... */</span>
}
</code></pre>
<p>With the help of the conditional operator, the whole thing can even be reduced to one line of code:</p>
<pre><code class="lang-javascript">callback = (<span class="hljs-keyword">typeof</span> callback === <span class="hljs-string">'function'</span>) ? callback : <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{};
</code></pre>
<h2 id="heading-parameters-of-a-callback-function">Parameters Of A Callback Function</h2>
<p>Since callback functions are called asynchronously and can neither supply a direct return value nor throw errors, appropriate parameters should be provided for at least these two cases:</p>
<ol>
<li>one parameter that contains information about the error that occurred in the event of an error,</li>
<li>one parameter that normally contains the result of the asynchronous calculation.</li>
</ol>
<p>Even if the order of these two parameters does not matter in principle, it has become a convention - especially when developing Node.js modules - to list the error as the first parameter and the result as the second parameter (if there is no error, the first parameters corresponding to zero).</p>
<pre><code class="lang-javascript">doStuff(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">
  error,            <span class="hljs-regexp">//</span> first parameter: error object
  result            <span class="hljs-regexp">//</span> second parameter: result object
  </span>) </span>{
});
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params">callback</span>) </span>{
  <span class="hljs-comment">/* ... */</span>
  <span class="hljs-keyword">var</span> result = <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// code that throws error</span>
  } <span class="hljs-keyword">catch</span>(error) {
    callback(error);
  }
  callback(<span class="hljs-literal">null</span>, result);
}
</code></pre>
<p>You can use a simple if query within the callback function to find out whether an error has occurred:</p>
<pre><code class="lang-javascript">doStuff(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">error, result</span>) </span>{
  <span class="hljs-keyword">if</span>(error) {
    <span class="hljs-comment">// error handling</span>
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-comment">// normal workflow</span>
  }
});
</code></pre>
<h2 id="heading-return-of-the-callback-call">Return Of The Callback Call</h2>
<p>In some cases, calling callback functions as shown in the previous examples can lead to unintended program behavior. For example, in the penultimate listing, the callback function is called twice in case an error occurs.</p>
<p>Here is the code again:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params">callback</span>) </span>{
  <span class="hljs-comment">/* ... */</span>
  <span class="hljs-keyword">var</span> result = <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// code that throws error</span>
  } <span class="hljs-keyword">catch</span>(error) {
    callback(error);
  }
  callback(<span class="hljs-literal">null</span>, result);
}
</code></pre>
<p>The problem here is that the code after the try-catch block will always be called. Even if the catch block was previously jumped due to an error and the callback function was called there. This is a fact that you can quickly overlook when just reading the source code. For this reason, you should place a "return" before each call of a callback function, which jumps directly out of the calling function.</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params">callback</span>) </span>{
  <span class="hljs-comment">/* ... */</span>
  <span class="hljs-keyword">var</span> result = <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// code that throws error</span>
  } <span class="hljs-keyword">catch</span>(error) {
    <span class="hljs-keyword">return</span> callback(error, <span class="hljs-literal">null</span>);
  }
  <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>, result);
}
</code></pre>
<p>The prerequisite for this is, of course, that the calling function is structured accordingly and the call of the callback function always represents the end of the function.</p>
<p>For example, the following snippet shows the wrong way to do it:</p>
<pre><code class="lang-javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doStuff</span>(<span class="hljs-params">callback</span>) </span>{
  <span class="hljs-comment">/* ... */</span>
  <span class="hljs-keyword">var</span> result = <span class="hljs-literal">null</span>;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-comment">// code that throws error</span>
  } <span class="hljs-keyword">catch</span>(error) {
    <span class="hljs-keyword">return</span> callback(error, <span class="hljs-literal">null</span>);
  }
  <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>, result);
  <span class="hljs-comment">// the code below will never be executed</span>
  <span class="hljs-keyword">if</span>(foo) {
    <span class="hljs-comment">/* ... */</span>
  }
}
</code></pre>
<h2 id="heading-the-execution-context-of-the-callback-function">The Execution Context Of The Callback Function</h2>
<p>Particular caution is required when passing functions as callback parameters that access the execution context via <code>this</code>. In this case, you have to take advantage of the <code>bind()</code> function and use it to create a new function that is bound to the desired execution context.</p>
<p>Then this new function is passed as a callback parameter:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">var</span> person = {
  <span class="hljs-attr">name</span>: <span class="hljs-string">'Paul Knulst'</span>, 
  <span class="hljs-attr">printName</span>: <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-built_in">this</span>.name);
  }
}
doStuff(person.printName);  <span class="hljs-comment">// WRONG</span>

<span class="hljs-keyword">var</span> printNameBound = person.printName.bind(person);
doStuff(printNameBound);    <span class="hljs-comment">// CORRECT</span>
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>When working with callback functions, several best practices should be considered, including type checking, the order of callback parameters, calling callback functions once, and setting the execution context.</p>
<p>The fundamental question of whether to use promises, generator functions or the async/await combination for asynchronous programming instead of callback functions will be discussed in the following articles.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/patterns-and-best-practices-in-javascript-dealing-with-callback-functions/">https://www.paulsblog.dev/patterns-and-best-practices-in-javascript-dealing-with-callback-functions/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, and <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>.</p>
<hr />
<h2 id="heading-support-this-content">🙌 Support this content</h2>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@carlheyerdahl?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Carl Heyerdahl</a> / <a target="_blank" href="https://unsplash.com/s/photos/technology-software-deve?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[An Introduction To Software Design Patterns]]></title><description><![CDATA[Paul Knulst  in  Programming • 8 min read

Design Pattern is a term used for a general, reusable solution to a commonly occurring problem in software design. It is important to understand that Design Patterns are not finished pieces of code that can ...]]></description><link>https://hashnode.knulst.de/an-introduction-to-software-design-patterns</link><guid isPermaLink="true">https://hashnode.knulst.de/an-introduction-to-software-design-patterns</guid><category><![CDATA[Programming Blogs]]></category><category><![CDATA[software development]]></category><category><![CDATA[Software Engineering]]></category><category><![CDATA[design patterns]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Wed, 07 Dec 2022 21:05:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670447037153/9sFy1Z-l9.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/programming/">Programming</a> • 8 min read</p>
<hr />
<p>Design Pattern is a term used for a general, reusable solution to a commonly occurring problem in software design. It is important to understand that Design Patterns are not finished pieces of code that can be transformed and directly applied to your software. Rather they are more like a template, a description, or a concept that can give you an idea of how to solve a problem that can be used in many different situations.</p>
<p>They can incredibly useful when used in the right places and for the right reasons. When used strategically, they enhance your software and make it more efficient by methods, already refined by a myriad of developers.</p>
<h2 id="heading-why-should-you-use-a-design-pattern">Why Should You Use A Design Pattern?</h2>
<p>Using Design Patterns in software development has several benefits:</p>
<ul>
<li><strong>They are proven solutions:</strong> Design patterns are often used by many different developers, so you can be sure that they work as intended. Furthermore, you can be confident that they were revised multiple times and optimization has taken place already.</li>
<li><strong>They are easily reusable:</strong> Design patterns document a reusable solution that can be modified to solve multiple particular problems because they are not tied to a specific problem.</li>
<li><strong>They are expressive:</strong> Large solutions can be explained elegantly by Design Patterns.</li>
<li><strong>They ease communication:</strong> Many developers are familiar with Design Patterns and use them easily to communicate about possible solutions to given problems with each other.</li>
<li><strong>They prevent the need for refactoring code:</strong> Often if you use Design Patterns while developing software you could avoid refactoring it later. This only applies if you use the correct one for a given problem because it describes already an optimal solution.  </li>
<li><strong>They lower the size of the codebase:</strong> Because Design Patterns are usually elegant, optimized, and well-documented solutions they require less code than other solutions.</li>
</ul>
<h2 id="heading-where-it-all-began">Where It All Began</h2>
<p>In 1994 an iconic computer science book "<a target="_blank" href="https://amzn.to/3VRn3gC">Design Patterns: Elements of Reusable Object-Oriented Software</a>" was first published by four authors: <a target="_blank" href="http://en.wikipedia.org/wiki/Erich_Gamma">Erich Gamma</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/Richard_Helm">Richard Helm</a>, <a target="_blank" href="http://en.wikipedia.org/wiki/Ralph_Johnson">Ralph Johnson</a>, and <a target="_blank" href="http://en.wikipedia.org/wiki/John_Vlissides">John Vlissides</a>. In technology circles, you'll often see these authors named "The Gang of Four" shortened to GoF.</p>
<p>Although the Gang of Four wrote the book in a C++ context it still remains very relevant to Java, JavaScript, Python, or any other object-oriented programming language. The authors, through their experience in creating large-scale enterprise systems using C++, saw 23 common patterns emerge. These 23 Design Patterns are not especially unique to C++ but can be applied in any other language.</p>
<p>Normally, as a developer that creates small to enterprise-class applications, you will encounter the Gang of Four Design Patterns on a daily basis.</p>
<h2 id="heading-the-23-gof-design-patterns">The 23 GoF Design Patterns</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://www.paulsblog.dev/content/images/2022/09/image--37-.webp" alt="All 23 Design Patterns introduced by Gang of Four in 1994" /></td></tr>
</thead>
<tbody>
<tr>
<td><center><em>All 23 Design Patterns introduced by the Gang of Four</em></center></td></tr>
</tbody>
</table>
</div><p>The 23 Gang of Four Design Patterns are divided into three categories:</p>
<ol>
<li><strong>Creational</strong>: For handling Object creation mechanisms. They are used to create objects, rather than having to instantiate objects directly. Your program will be more flexible in deciding which object it creates for a specific use case.</li>
<li><strong>Structural</strong>: For identifying ways to realize relationships between objects. This concerns class/object composition with inheritance to define different ways to gain new functionality.</li>
<li><strong>Behavioral</strong>: For handling communication between different objects.</li>
</ol>
<h3 id="heading-creational">Creational</h3>
<p>There are 5 Design Patterns in the creational category designed by the Gang of Four.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Abstract_factory_pattern"><strong>1. Abstract Factory:</strong></a> Creates an instance of several families of classes without detailing concrete classes. It allows the creation of a factory for factory classes.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Builder_pattern">2. Builder:</a></strong> Separates object construction from its representation, and always creates the same type of object. It creates an object step by step and a method to finally get the object instance.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Factory_method_pattern"><strong>3. Factory Method:</strong></a> Moves the responsibility of instantiating an object from the class to a Factory. The Factory then makes an instance of several derived classes based on interfaced data or events.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Prototype_pattern">4. Prototype:</a></strong> This pattern provides a mechanism to copy or clone the original object to a new object and then modify it according to your needs to avoid costly object creation.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Singleton_pattern"><strong>5. Singleton:</strong></a> Restricts the initialization of a class to ensure that only one instance of the class can be created. Multiple initialization attempts will result in returning the same object over and over again.</p>
<h3 id="heading-structural">Structural</h3>
<p>There are 7 Design Patterns in the structural category designed by the Gang of Four.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Adapter_pattern"><strong>1. Adapter:</strong></a> Provides an interface between two unrelated entities in a way that they both can work together. It wraps an interface between already existing classes to enable working with each other without modifying the source code directly.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Bridge_pattern"><strong>2. Bridge:</strong></a> Defines a strategy to decouple the interfaces from implementation and hide the implementation details from the client program. So both can vary independently. It uses encapsulation, aggregation, and inheritance to separate responsibilities into multiple classes.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Composite_pattern"><strong>3. Composite:</strong></a> This pattern is used if you have to implement a part-whole hierarchy. It creates a group of objects that should be treated equally as a single instance of one object. It composes zero-or-more objects in a way that they can be manipulated as one single object.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Decorator_pattern">4. Decorator:</a></strong> Is used for modifying the behavior of objects during runtime. It dynamically adds alternate processing to objects without affecting other objects from the same class.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Facade_pattern">5. Facade:</a></strong> Provides a unified interface to a set of interfaces in a subsystem. It defines a higher level that helps to easier use of the subsystem. It masks the underlying complex or structural code and serves as a "<em>front-facing interface</em>"</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Flyweight_pattern">6. Flyweight:</a></strong> Describes a fine-grained instance used for the efficient sharing of information. It caches and reuses object instances, used with immutable objects to minimize memory usage.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Proxy_pattern">7. Proxy:</a></strong> Provides a placeholder for another object. You should use it to control access, reduce cost, and reduce complexity.</p>
<h3 id="heading-behavioral">Behavioral</h3>
<p>There are 11 Design Patterns in the behavioral category designed by the Gang of Four.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern">1. Chain of Responsibility:</a></strong> Describes a technique to delegate commands or pass requests between a chain of objects to find the object that can handle the command or request.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Command_pattern">2. Command:</a></strong> This pattern encapsulates a command request as an object to enable, log, and/or queue requests. Furthermore, it provides error handling for unhandled command requests,</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Interpreter_pattern">3. Interpreter:</a></strong> Includes language-specific elements in the application to match the grammar of the intended language. It defines a grammatical representation of a language and the interpreter that works with this grammar.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Iterator_pattern">4. Iterator:</a></strong> Describes a standardized way to traverse through a group of objects. It accesses the containing objects sequentially without exposing their underlying representation.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Mediator_pattern">5. Mediator:</a></strong> Provides centralized communication between different objects in a system to enable loose coupling between different objects. It encapsulates how these different objects interact with each other.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Memento_pattern">6. Memento:</a></strong> Stores a state of an object whenever it is saved to enable restoring previous states by providing undo operations.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Observer_pattern">7. Observer:</a></strong> Notifies state changes to a set of subscribing classes to ensure consistency between all of them.</p>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/State_pattern"><strong>8. State:</strong></a> Enables behavioral changes based on an internal state of an object. If the state of an object changes it also changes how it reacts to requests.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Strategy_pattern">9. Strategy:</a></strong> This pattern is used when multiple algorithms for a specific task exist and a client decides which implementation should be used at runtime. It encapsulates the algorithm inside the class separating selection from implementation.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Template_method_pattern">10. Template Method:</a></strong> Defines an abstract skeleton of an algorithm in such a way that subclasses have to implement the concrete behavior.</p>
<p><strong><a target="_blank" href="https://en.wikipedia.org/wiki/Visitor_pattern">11. Visitor:</a></strong> Separates an algorithm from an object structure on which it operates. This enables adding new operations to existing object structures without changing the structure.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Now that you have read this short introduction to software Design Patterns, you should have an idea about several important techniques used in nearly every programming language.</p>
<p>You should now hone your skills by implementing all or at least some of these Design Patterns into your side projects or just as practice. Also, you can start to contemplate how to apply them in your real projects.</p>
<p>Although, there are several implementations that benefit from utilizing Design Patterns keep in mind that they were never meant to be hacked together shortcuts to be applied in a 'one-size-fits-all' manner to your code. There is no perfect substitute for a genuine problem-solving ability in software engineering.</p>
<p>It is paramount to understand, Design Patterns provide a common language to conceptualize repeating problems and solutions while working with a team or managing large code bases.</p>
<p>That being said, the most important caveat is ensuring that the <strong>how</strong> and <strong>why</strong> behind each Design Pattern are fully understood by the developer.</p>
<hr />
<p>If you enjoyed reading this article consider commenting your valuable thoughts in the comments section. I would love to hear your feedback about all the Design patterns I mentioned here. Furthermore, share this article with your friends and colleagues to also help them to know about Design Patterns.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/an-introduction-to-software-design-patterns/">https://www.paulsblog.dev/an-introduction-to-software-design-patterns/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, and <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>.</p>
<hr />
<p><strong>🙌 Support this content</strong></p>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo generated by <a target="_blank" href="https://www.wordclouds.com">WordClouds.com</a></p>
]]></content:encoded></item><item><title><![CDATA[How To Self Host Ghost CMS Blogging Platform On Docker / Docker Swarm]]></title><description><![CDATA[If you are reading this you decided to create a personal blog using the Ghost blogging software and are looking for a way to install it within a Docker (or Docker Swarm) environment. Don't search any longer, you are at the right place!
Within this tu...]]></description><link>https://hashnode.knulst.de/how-to-self-host-ghost-cms-blogging-platform-on-docker-docker-swarm</link><guid isPermaLink="true">https://hashnode.knulst.de/how-to-self-host-ghost-cms-blogging-platform-on-docker-docker-swarm</guid><category><![CDATA[Developer]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Devops]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[Blogging]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Sun, 04 Dec 2022 18:24:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1670178183976/ZUYFdtZP4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you are reading this you decided to create a personal blog using the Ghost blogging software and are looking for a way to install it within a Docker (or Docker Swarm) environment. Don't search any longer, you are at the right place!</p>
<p>Within this tutorial, I will show you how to set up Ghost with Docker and run the blog with a subdomain/primary domain within your environment. Primarily I will show how to set up everything within a Docker Swarm environment but I will also provide all the necessary files to deploy it in a simple Docker environment.</p>
<h2 id="heading-why-use-ghost">Why Use Ghost?</h2>
<p>Ghost is an open-source blogging platform that is used to create a professional blog. It was released in October 2013 as a simple alternative to WordPress because it was getting overly complex. Ghost runs within Node.js and is written in JavaScript.</p>
<p>Here are the main features that Ghost blogging software provides:</p>
<ul>
<li>Share a private link to any post. This is useful for a review. Also, you can share it with everyone and not only with people having an account on the article platform</li>
<li>Ghost has a great editor that allows <strong>embedding code snippets</strong> (with Prismjs), <strong>YouTube</strong>, <strong>Twitter</strong>, <strong>Codepen</strong>, etc</li>
<li>You can schedule every post so that it will be published on the exact date you selected.</li>
<li>Every post/article can be optimized in terms of SEO</li>
<li>You are able to add and manage authors easily</li>
<li>Ghost already has more than hundreds of apps like <strong>Slack</strong>, <strong>Stripe</strong>, <strong>Shopify</strong>, etc</li>
<li>There are several themes that can be customized and you are able to manually inject your personal code on the site and for every article</li>
<li>Users can register for a newsletter and/or <strong>can become sponsors</strong> of your blog</li>
</ul>
<p>Also, there are many more features that are provided by Ghost.</p>
<h2 id="heading-prerequisite">Prerequisite</h2>
<p>To follow every step within this tutorial and have a running Ghost blogging Platform at the end you need to have a running Docker Swarm environment. To achieve this you should consider reading this article:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.paulsblog.dev/docker-swarm-in-a-nutshell/">https://www.paulsblog.dev/docker-swarm-in-a-nutshell/</a></div>
<p>Furthermore, you need a Traefik load balancer that is used to grant Let's encrypt SSL certificates and for forwarding your services within your Docker Swarm environment. To learn about this, you can read the first chapter from this tutorial:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.paulsblog.dev/services-you-want-to-have-in-a-swarm-environment/">https://www.paulsblog.dev/services-you-want-to-have-in-a-swarm-environment/</a></div>
<p>Additionally, I will provide files to run the Ghost blogging platform on any server running Docker with a Traefik load balancer. Please read this article to understand how Traefik is installed in a simple Docker environment:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://www.paulsblog.dev/how-to-setup-traefik-with-automatic-letsencrypt-certificate-resolver/">https://www.paulsblog.dev/how-to-setup-traefik-with-automatic-letsencrypt-certificate-resolver/</a></div>
<h2 id="heading-install-ghost">Install Ghost</h2>
<h3 id="heading-docker-swarm">Docker Swarm</h3>
<p>Ghost will be installed with Docker Compose. The Compose file contains the service name, settings for Traefik to have a unique URL, and an SSL certificate. Also, there are two middlewares that are used to forward requests to the main site.</p>
<p>To install Ghost within your Docker Swarm you can paste the following code into your docker-compose.yml which will be explained afterward.</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">"3.4"</span>
<span class="hljs-attr">services:</span>
  <span class="hljs-attr">ghost:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">ghost:4</span>
    <span class="hljs-attr">environment:</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">https://www.${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}</span>
      <span class="hljs-attr">mail__transport:</span> <span class="hljs-string">SMTP</span>
      <span class="hljs-attr">mail__options__host:</span> <span class="hljs-string">${MAIL_HOST?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}</span>
      <span class="hljs-attr">mail__options__port:</span> <span class="hljs-string">${MAIL_PORT?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}</span>
      <span class="hljs-attr">mail__options__auth__user:</span> <span class="hljs-string">${MAIL_USER?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}</span>
      <span class="hljs-attr">mail__options__auth__pass:</span> <span class="hljs-string">${MAIL_PASS?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}</span>
      <span class="hljs-attr">mail__from:</span> <span class="hljs-string">${MAIL_FROM?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">content:/var/lib/ghost/content</span>
    <span class="hljs-attr">networks:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">traefik-public</span>
    <span class="hljs-attr">deploy:</span>
      <span class="hljs-attr">placement:</span>
        <span class="hljs-attr">constraints:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">node.labels.blogs.knulst</span> <span class="hljs-string">==</span> <span class="hljs-literal">true</span>
      <span class="hljs-attr">labels:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.enable=true</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.docker.network=traefik-public</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.constraint-label=traefik-public</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-http.rule=Host(`www.${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}`)</span> <span class="hljs-string">||</span> <span class="hljs-string">Host(`${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}`)</span> <span class="hljs-string">||</span> <span class="hljs-string">Host(`blog.${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}`)</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-http.entrypoints=http</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-http.middlewares=https-redirect</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-https.rule=Host(`www.${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}`)</span> <span class="hljs-string">||</span> <span class="hljs-string">Host(`${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}`)</span> <span class="hljs-string">||</span> <span class="hljs-string">Host(`blog.${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}`)</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-https.entrypoints=https</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-https.tls=true</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-https.tls.certresolver=le</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.services.blogs-knulst.loadbalancer.server.port=2368</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-blog.redirectregex.regex=^https://blog.${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}/(.*)</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-blog.redirectregex.replacement=https://www.${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}/$${1}</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-blog.redirectregex.permanent=true</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-nosub.redirectregex.regex=^https://${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}/(.*)</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-nosub.redirectregex.replacement=https://www.${DOMAIN?Variable</span> <span class="hljs-string">not</span> <span class="hljs-string">set}/$${1}</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.redirect-nosub.redirectregex.permanent=true</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.blogs-knulst-https.middlewares=redirect-blog,</span> <span class="hljs-string">redirect-nosub</span>
<span class="hljs-attr">volumes:</span>
  <span class="hljs-attr">content:</span>

<span class="hljs-attr">networks:</span>
  <span class="hljs-attr">traefik-public:</span>
    <span class="hljs-attr">external:</span> <span class="hljs-literal">true</span>
</code></pre>
<p><strong>Line 4:</strong> Up to date version of Ghost v4 will be used during installation</p>
<p><strong>Line 6:</strong> The main blog URL will be defined as an environment variable within the docker service</p>
<p><strong>Line 7 - 12:</strong> This section can be used to set mail configuration for sending registration emails, invitations, password resets, or member login links. This email cannot be used for newsletter emails. For this feature, you have to set up a service like Mailgun. Read more about it <a target="_blank" href="https://ghost.org/docs/config/#mail">within the Ghost documentation</a>.</p>
<p><strong>Line 13 - 14:</strong> The content of the website will be saved as a persistent volume within your Docker environment</p>
<p><strong>Line 15 - 16:</strong> The main traefik network will be used here. This is important because otherwise, Traefik cannot forward requests to the service</p>
<p><strong>Line 18 - 20:</strong> The service will only be deployed to a Docker Swarm node if the label <code>blogs.knulst</code> is true. This can be achieved by executing the following command before deploying the docker-compose.yml to the stack:</p>
<pre><code class="lang-bash">docker node update --label-add blogs.knulst=<span class="hljs-literal">true</span> ID_OF_NODE_TO_USE
</code></pre>
<p>Replace  ID_OF_NODE_TO_USE with the correct ID of any worker/manager node of your Docker Swarm where the service should run.</p>
<p><strong>Line 21 - 31:</strong> Set up a standard configuration for a service deployed in a Docker Swarm with Traefik and Let's Encrypt certificates. In Line 19 and 22 three URLs are registered for this service: www.${DOMAIN}, ${DOMAIN}, and blog.${DOMAIN}.</p>
<p><strong>Line 32:</strong> Port used by Ghost blogging Docker container. Needed for Traefik.</p>
<p><strong>Line 33 - 35:</strong> Creates a permanent Traefik middleware that forwards every request from blog.${DOMAIN} to www.${DOMAIN}</p>
<p><strong>Line 36 - 38:</strong> Creates a permanent Traefik middleware that forwards every request from ${DOMAIN} to www.${DOMAIN}</p>
<p><strong>Line 39:</strong> Activates the earlier created middleware for this service. This is done because I only want to have one primary website for my blog but multiple URLs to reach it.</p>
<hr />
<p>Before deploying (or redeploying) multiple environment variables should be set with (adjust them to your needs):</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> DOMAIN=knulst.de
<span class="hljs-built_in">export</span> MAIL_HOST=smtp.your-domain.de
<span class="hljs-built_in">export</span> MAIL_PORT=587
<span class="hljs-built_in">export</span> MAIL_USER=blog@knulst.de
<span class="hljs-built_in">export</span> MAIL_PASS=unbelievablehowsecurethisis
<span class="hljs-built_in">export</span> MAIL_FROM=blog@knulst.de
</code></pre>
<p>Then you can deploy the Docker Swarm stack by executing:</p>
<pre><code class="lang-bash">docker stack deploy -c docker-compose.yml blog
</code></pre>
<h3 id="heading-docker">Docker</h3>
<p>If you do not have a running Docker Swarm you can download <a target="_blank" href="https://ftp.f1nalboss.de/data/docker-compose.blog.yml">this Compose file</a>.</p>
<p>Within this file, there are only two differences from the Docker Swarm Compose file. The first is in <strong>Line 17</strong> where a new setting is used: <code>restart: always</code>. This configuration is used to automatically restart the Docker service if it is aborted. The other change is that <strong>labels are removed from the deploy - keyword</strong> and put to a higher order within the Compose file. This is done because deploy is only used in a Docker Swarm environment but labels can also be used in a simple Docker environment.</p>
<p>Keep in mind that this will only work if you have a <a target="_blank" href="https://www.paulsblog.dev/how-to-setup-traefik-with-automatic-letsencrypt-certificate-resolver/">running Traefik load balancer</a>.</p>
<p>Before starting the docker service multiple environment variables should be set with (adjust them to your needs):</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> DOMAIN=knulst.de
<span class="hljs-built_in">export</span> MAIL_HOST=smtp.your-domain.de
<span class="hljs-built_in">export</span> MAIL_PORT=587
<span class="hljs-built_in">export</span> MAIL_USER=blog@knulst.de
<span class="hljs-built_in">export</span> MAIL_PASS=unbelievablehowsecurethisis
<span class="hljs-built_in">export</span> MAIL_FROM=blog@knulst.de
</code></pre>
<p>Then you can start the Docker service by executing</p>
<pre><code class="lang-bash">docker-compose up -d
</code></pre>
<h2 id="heading-configure-ghost">Configure Ghost</h2>
<p>If you reached this step Ghost blog is already installed on your URL with the default Casper theme and will look like this:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://www.paulsblog.dev/content/images/2022/09/image--31-.webp" alt="Ghost Blogging Platform default interface after installed in Docker Swarm" /></td></tr>
</thead>
<tbody>
<tr>
<td><center><em>Screenshot of a fresh installed Ghost blog</em></center></td></tr>
</tbody>
</table>
</div><p>Now you have to configure your Ghost blog instance by opening your website and appending the ghost path to open the Admin menu (should be https://your-domain.de<strong>/ghost</strong>).</p>
<p>If you open the admin menu you have to set the title, your name, your email address, and a secure password:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://www.paulsblog.dev/content/images/2022/09/image--32-.webp" alt="Installed Ghost Blogging Platform admin menu registration form" /></td></tr>
</thead>
<tbody>
<tr>
<td><center><em>Registration Form after installation</em></center></td></tr>
</tbody>
</table>
</div><p>After you create the account you should receive an email from your blog. If you do not receive any mail you can check the docker logs about email problems.</p>
<p>The last step will be adjusting the personal settings of your newly created blog. For this, you can open the Ghost blog settings (https://your-domain.de<strong>/ghost/#/settings</strong>) which should look like this:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://www.paulsblog.dev/content/images/2022/09/image--33-.webp" alt="Ghost Blogging Platform dashboard running on Docker Swarm" /></td></tr>
</thead>
<tbody>
<tr>
<td><center><em>Settings Dashboard</em></center></td></tr>
</tbody>
</table>
</div><p>Select <strong>General</strong> and adjust everything to your needs. Afterward, you should open <strong>Design</strong> and install a theme that suits you. I can recommend <a target="_blank" href="https://github.com/eddiesigner/liebling">Liebling</a> by <a target="_blank" href="https://github.com/eddiesigner">Eduardo Gómez</a>. It has already inbuilt translations and looks very good.</p>
<p>Furthermore, I would suggest selecting <strong>Membership</strong>, connecting your <strong>Stripe</strong> account, and enabling paid registration as an additional Membership alternative. Within my blog, I have done this too but I do not provide extra content only to paying members. It is only activated if someone wants to sponsor me regardless of the content that is provided.</p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>Congratulations you have just installed your own Ghost blog. If you want to improve the user experience or do more with your blog you can:</p>
<ul>
<li>Sign up on Grammarly and install the web extension to optimize articles</li>
<li>Customize the design with pictures or a theme</li>
<li><a target="_blank" href="https://ghost.org/integrations/google/">Set up analytics with Google analytics</a></li>
<li><a target="_blank" href="https://ghost.org/docs/tutorials/code-syntax-highlighting/">Add code syntax highlighting</a></li>
<li><a target="_blank" href="https://ghost.org/docs/tutorials/adding-comments/">Add comments to the posts</a></li>
</ul>
<p>This is the end of this tutorial. Once you finish setting up your personal blog, share it with me on <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>. I will love to see that!</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/how-to-self-host-ghost-blogging-platform-on-docker-docker-swarm/">https://www.paulsblog.dev/how-to-self-host-ghost-blogging-platform-on-docker-docker-swarm/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my blog</a>, <a target="_blank" href="https://medium.knulst.de">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, and <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>.</p>
<hr />
<h2 id="heading-support-this-content">🙌 Support this content</h2>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@zal3wa?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Damian Zaleski</a> / <a target="_blank" href="https://unsplash.com/@zal3wa?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[How To Start With Python Programming]]></title><description><![CDATA[Paul Knulst  in  Programming • Feb 1, 2022 • 11 min read

According to the Tiobe Index, Python is the most used programming language in the world. Also, it is in high demand since it is used by all of the world’s leading firms. As of today, there is ...]]></description><link>https://hashnode.knulst.de/how-to-start-with-python-programming</link><guid isPermaLink="true">https://hashnode.knulst.de/how-to-start-with-python-programming</guid><category><![CDATA[Python]]></category><category><![CDATA[python beginner]]></category><category><![CDATA[Python 3]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[learning]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Fri, 02 Dec 2022 10:00:56 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1669975119150/OgiWb9DFk.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/programming/">Programming</a> • Feb 1, 2022 • 11 min read</p>
<hr />
<p>According to the <a target="_blank" href="https://www.tiobe.com/tiobe-index/">Tiobe Index</a>, Python is the most used programming language in the world. Also, it is in high demand since it is used by all of the world’s leading firms. As of today, there is no better time to learn Python and this can be done with YouTube, books, or websites.</p>
<p>Within this article, I have compiled a list of the best channels that I follow, interesting books that I earn, and websites that I have experience with.</p>
<h2 id="heading-youtube-channels">YouTube Channels:</h2>
<p>If you want to learn Python, you will have a vast collection of resources to choose from. On YouTube, you will find plenty of channels with a simple search.</p>
<p>To help you find good resources to start with I come up with the following seven channels.</p>
<h3 id="heading-1-freecodecamporghttpswwwyoutubecomcfreecodecamp"><a target="_blank" href="https://www.youtube.com/c/Freecodecamp">1. freeCodeCamp.org</a></h3>
<ul>
<li>Created: 17 Dec 2014</li>
<li>Creator: Quincy Larson</li>
<li>Subscribers: 5.18M</li>
<li>Channel Link: <a target="_blank" href="https://www.youtube.com/c/Freecodecamp">https://www.youtube.com/c/Freecodecamp</a></li>
</ul>
<p>freeCodeCamp.org is a well-known resource among programmers. It was created by Quincy Larson back in 2014 with the aim of making programming easier and accessible for anyone without paying a fee.</p>
<p>This YouTube channel is one of the best resources for learning nearly every programming language including Python. In addition to tutorials for absolute beginners, they also offer Python-related video tutorials for professionals. They also cover Deep Learning or Data Analysis.</p>
<p>Furthermore, the freeCodeCamp website offers certification programs for a number of domains, including Python (and many more).</p>
<h3 id="heading-2-programming-with-moshhttpswwwyoutubecomchannelucwv7vmbmwh4-v0zxdmdppba"><a target="_blank" href="https://www.youtube.com/channel/UCWv7vMbMWH4-V0ZXdmDpPBA">2. Programming with Mosh</a></h3>
<ul>
<li>Created: 07 Oct 2014</li>
<li>Creator: Mosh Hamedani</li>
<li>Subscribers: 2.34M</li>
<li>Channel Link: <a target="_blank" href="https://www.youtube.com/channel/UCWv7vMbMWH4-V0ZXdmDpPBA">https://www.youtube.com/channel/UCWv7vMbMWH4-V0ZXdmDpPBA</a></li>
</ul>
<p>Programming with Mosh was created in 2014 by Mosh Hamedani and provides tutorials for upcoming Python developers. He has a clear and concise way of presenting topics. His videos usually are long but they are well-structured with plenty of timestamps, making navigation through the videos painless.</p>
<h3 id="heading-3-cs-dojohttpswwwyoutubecomcsdojo"><a target="_blank" href="https://www.youtube.com/csdojo">3. CS Dojo</a></h3>
<ul>
<li><strong>Created:</strong> 26 Feb 2016</li>
<li><strong>Creator:</strong> YK Sugishita</li>
<li><strong>Subscribers:</strong> 1.79M</li>
<li><strong>Channel Link:</strong> <a target="_blank" href="https://www.youtube.com/csdojo">https://www.youtube.com/csdojo</a></li>
</ul>
<p>CS Dojo was created by YK Sugishita, who was a former Microsoft intern and an ex-Googler. Within his video tutorials, he follows a code-along approach to enable his viewers to practice writing code side-by-side with him while solving practice exercises.</p>
<p>Additionally, he describes the topics in a completely transparent and unclouded way to his viewers which makes the videos easy to follow.</p>
<h3 id="heading-4-sentdexhttpswwwyoutubecomusersentdex"><a target="_blank" href="https://www.youtube.com/user/sentdex">4. Sentdex</a></h3>
<ul>
<li><strong>Created:</strong> 17 Dec 2012</li>
<li><strong>Creator:</strong> Harrison Kinsley</li>
<li><strong>Subscribers:</strong> 1.11M</li>
<li><strong>Channel Link:</strong> <a target="_blank" href="https://www.youtube.com/user/sentdex">https://www.youtube.com/user/sentdex</a></li>
</ul>
<p>Sentdex is a YouTube channel that is beginner-friendly and suitable for new developers as well as intermediate ones. It was created in 2012 by Harrison Kinsley and mainly covers topics related to Python.</p>
<p>In his video tutorials, Harrison follows a practical approach to the topics rather than going with the theoretical approach.</p>
<h3 id="heading-5-corey-schaferhttpswwwyoutubecomchanneluccezigc97pvuur4gbfus5g"><a target="_blank" href="https://www.youtube.com/channel/UCCezIgC97PvUuR4_gbFUs5g">5. Corey Schafer</a></h3>
<ul>
<li><strong>Created:</strong> 01 Jun 2006</li>
<li><strong>Creator:</strong> Corey Schafer</li>
<li><strong>Subscribers:</strong> 898k</li>
<li><strong>Channel Link:</strong> <a target="_blank" href="https://www.youtube.com/channel/UCCezIgC97PvUuR4_gbFUs5g">https://www.youtube.com/channel/UCCezIgC97PvUuR4_gbFUs5g</a></li>
</ul>
<p>Corey Schafer created his primarily Python-focused YouTube channel in 2006 and provides convenient tutorials and walkthroughs for programmers of every skill level.</p>
<p>It contains everything from setting up your development environment to tutorials about advanced concepts in Python.</p>
<p>In his tutorials, Corey is focused that a viewer will get a solid understanding of the foundational concepts in Python that then could be used in several follow-up tutorials on his channel</p>
<h3 id="heading-6-teluskohttpswwwyoutubecomctelusko"><a target="_blank" href="https://www.youtube.com/c/Telusko">6. Telusko</a></h3>
<ul>
<li><strong>Created:</strong> 29 Apr 2014</li>
<li><strong>Creator:</strong> Navin Reddy</li>
<li><strong>Subscribers:</strong> 1.64M</li>
<li><strong>Channel Link:</strong> <a target="_blank" href="https://www.youtube.com/c/Telusko">https://www.youtube.com/c/Telusko</a></li>
</ul>
<p>Telusko is a YouTube channel that makes learning as fun and interesting as possible. Navin Reddy created the channel in 2014 and covers nearly every topic in Python in a playful and clear way.</p>
<p>This channel is a good starting point for developers who are new to programming in general and want to take simple steps in the world of Python programming.</p>
<h3 id="heading-7-real-pythonhttpswwwyoutubecomcrealpython"><a target="_blank" href="https://www.youtube.com/c/realpython">7. Real Python</a></h3>
<ul>
<li><strong>Created:</strong> 22 May 2013</li>
<li><strong>Creator:</strong> Dan Bader</li>
<li><strong>Subscribers:</strong> 146k</li>
<li><strong>Channel Link:</strong> <a target="_blank" href="https://www.youtube.com/c/realpython">https://www.youtube.com/c/realpython</a></li>
</ul>
<p>Real Python’s YouTube channel was created in 2013 by Dan Bader and is one of the most reputed learning resource providers for people interested in learning Python. It is your one-stop shop for learning a huge amount of topics that are beyond the foundational stuff.</p>
<p>It is designed for people that are already familiar with Python and are looking to strengthen their skills in different advanced topics. Also, the YouTube channel offers everything from beginner-friendly tutorials, to weekly podcasts, and coverage of Python events.</p>
<h2 id="heading-books">Books</h2>
<h3 id="heading-1-python-crash-course-a-hands-on-project-based-introduction-to-programminghttpsamznto3uz4aoa"><a target="_blank" href="https://amzn.to/3Uz4Aoa">1. Python Crash Course, A Hands-On, Project-Based Introduction to Programming</a>*</h3>
<p>Python Crash Course by Eric Matthews is a fast-paced and comprehensive introduction to Python language. It is written for beginners who want to learn Python programming to write useful programs.</p>
<p>This book focuses on speed to enable the reader to write real programs in no time at all. It is designed for programmers that have a basic understanding of Python and want to test their skills before creating real-life applications with Python.</p>
<p>You will learn libraries and tools such as Pygame, Matplotlib, Plotly, and Django and will use data to create interactive visualizations.</p>
<p>The book consists of two parts. Within the first part, some Python basics and concepts will be explained. You will be taught to write clean and readable code to create interactive Python programs. The second part follows a practical approach and tests your knowledge by presenting three different projects: space invaders inspired arcade game, a set of data visualizations with Python’s handy libraries, and a simple web app that you can deploy online.</p>
<p><a target="_blank" href="https://amzn.to/3Uz4Aoa">You can buy the book here</a>*</p>
<h3 id="heading-2-automate-the-boring-stuff-with-python-2nd-edition-practical-programming-for-total-beginnershttpsamznto3ha0czw"><a target="_blank" href="https://amzn.to/3Ha0cZW">2. Automate the Boring Stuff with Python, 2nd Edition: Practical Programming for Total Beginners</a>*</h3>
<p>Automate the Boring Stuff with Python by Al Sweigart is a book that teaches Python3 to everyone, including technically inclined beginners, juniors, seniors, and other geeks around the world. It covers step-by-step instructions and explains each program in detail to enable you to write programs quickly and efficiently in Python.</p>
<p>Within the first part of the book, you will learn Python’s basics and explore several libraries to perform tasks like data scraping of websites, reading PDF or word documents, and automating typing and clicking. The second part includes input validation and tutorials on automating Gmail and Google Sheets. Also, it describes how you can automatically update CSV files. Additionally, you will learn how to automate several tasks with Python including searching for text in multiple files, creating/updating/moving/renaming files or folders, searching the web and downloading content, filling out forms on websites, and many more.</p>
<p><a target="_blank" href="https://amzn.to/3Ha0cZW">You can buy the book here</a>*</p>
<h3 id="heading-3-learning-python-powerful-object-oriented-programminghttpsamznto3ufvebv"><a target="_blank" href="https://amzn.to/3uFveBv">3. Learning Python: Powerful Object-Oriented Programming</a>*</h3>
<p>Learning Python is a comprehensive, in-depth introduction to the core Python language by Mark Lutz and is based on his popular training course. The latest version of the book helps you to quickly write efficient, high-quality code. The book is designed for everyone who wants to learn Python including new developers, intermediates as well as professional ones.</p>
<p>You will explore Python’s major built-in object types such as numbers, lists, and dictionaries and create/process objects with Python statements to learn the general syntax model. Additionally, it will teach you to avoid code redundancy, organize statements/functions, and how package code for reuse.</p>
<p>Furthermore, you will learn about OOP concepts, the exception-handling model, and development tools. The book also covers advanced Python tools like decorators, descriptors, metaclasses, and Unicode processing.</p>
<p><a target="_blank" href="https://amzn.to/3uFveBv">You can buy the book here</a>*</p>
<h3 id="heading-4-head-first-python-a-brain-friendly-guidehttpsamznto3vr1cgu"><a target="_blank" href="https://amzn.to/3VR1cGu">4. Head First Python: A Brain-Friendly Guide</a>*</h3>
<p>Head-First Python by Paul Barry is a book to learn Python without wrapping through counterproductive tutorials and books. The book will help you to gain a quick grasp of Python programming fundamentals and work with built-in data structures and functions. Afterward, you will create your own web application, exercise exception handling or data wrangling, explore database management, and other concepts.</p>
<p>This book is different than others because it uses a visually rich format to engage your mind, rather than a text-heavy approach that will put you to sleep. Additionally, it creates a multi-sensory learning experience and is designed for the way your brain really works.</p>
<p><a target="_blank" href="https://amzn.to/3VR1cGu">You can buy the book here</a></p>
<h3 id="heading-5-elements-of-programming-interviews-in-python-the-insiders-guidehttpsamznto3xydh4d"><a target="_blank" href="https://amzn.to/3XYdH4D">5. Elements of Programming Interviews in Python: The Insiders’ Guide</a>*</h3>
<p>Elements of Programming is a book that contains a collection of 250 problems with detailed solutions based on interview questions at leading software companies. The problems are illustrated with 200 figures, 300 tested programs, and 150 additional variants. It provides several data structures, algorithms, and problem-solving patterns.</p>
<p>The book is separated into a series of chapters on basic and advanced data structures, searching, sorting, algorithm design principle, and concurrency. You will find a brief introduction, a case study, top tips, and a review of the most important library method in every chapter of the book. Then you will have a broad and thought-provoking set of problems.</p>
<p>Furthermore, the book summarizes many nontechnical aspects of interviewing, such as interview strategies, perspectives from the other side of the table, common interview mistakes, and tips on negotiating the best offer.</p>
<p><a target="_blank" href="https://amzn.to/3XYdH4D">You can buy the book here</a>*</p>
<h2 id="heading-websites">Websites</h2>
<h3 id="heading-1-the-official-python-tutorialhttpsdocspythonorg3tutorial"><a target="_blank" href="https://docs.python.org/3/tutorial/">1. The Official Python Tutorial</a></h3>
<p>One of the best places to start your Python developer journey is the official Python website. The creator of the language made a Python tutorial that teaches you the basics of the language. It is designed rather for beginners than seniors who want to learn Python. It has a slow pace with clear imagery and plenty of time to explain nearly every topic.</p>
<p>If you already have heard about Python and are not completely new you could find the official Python tutorial too slow and should use another source for further research.</p>
<p>You should consider using this website if you want to learn Python tips and tricks from Python creators on a site that is created for complete beginners. Also, it is for you if you like a slow pace of learning to make sure every concept is discussed in detail before moving to the next chapter</p>
<p><a target="_blank" href="https://docs.python.org/3/tutorial/">Here is a link to the website</a></p>
<h3 id="heading-2-codewarshttpswwwcodewarscomlanguagepython"><a target="_blank" href="https://www.codewars.com/?language=python">2. CodeWars</a></h3>
<p>If you are no new Python developer or the official Python tutorial is too slow for you, CodeWars will be a good place to start. CodeWars is a set of puzzles that helps you to test your Python knowledge and learn about your strength and weaknesses while you learn several algorithms.</p>
<p>CodeWars is some kind of Gamification model for Python and contains many puzzles that are called katas. These <strong><strong>catas</strong></strong> are categorized according to their difficulty levels. After you finished a puzzle you can check solutions from other developers and learn different strategies.</p>
<p>You should consider using this website if you want to use a Gamification concept, love to solve coding puzzles to test your knowledge, and want to compare with other developers and see or learn from their solutions.</p>
<p><a target="_blank" href="https://www.codewars.com/?language=python">Here is a link to the website</a></p>
<h3 id="heading-3-a-byte-of-pythonhttpspythonswaroopchcom"><a target="_blank" href="https://python.swaroopch.com">3. A Byte of Python</a></h3>
<p>A byte of Python is a free online e-book about Python programming that is not suited for beginners. It requires that you have at least knowledge about the basics of Python programming otherwise you will find yourself lost during reading.</p>
<p>Although it is an excellent site to learn Python if you want to have a book that jumps quickly into all the topics without caring about beginners. If you are a fast learner who can adapt easily or you already know the language this book is perfect for you to strengthen your Python programming skills.</p>
<p><a target="_blank" href="https://python.swaroopch.com/">Here is a link to the website</a></p>
<h3 id="heading-4-real-python-tutorialshttpsrealpythoncom"><a target="_blank" href="https://realpython.com/">4. Real Python Tutorials</a></h3>
<p>Real Python Tutorials is a website that provides interactive tutorials to you. Also, it's more structured like a blog about Python programming, where you can learn every topic by reading different blog posts one by one.</p>
<p>After you have read about new skills in a blog post you can practice them or jump to another topic with the search function. If you mark blog posts as completed you will be get informed about related topics that will enhance your Python knowledge based on the already completed posts.</p>
<p>You should consider using this website as a resource on its own. It is suitable for beginners and advanced programmers because it covers the basics and provides practical exercises to help you with Python programming. It can be used as a resource to find missing information for problems you will encounter as a Python developer.</p>
<p><a target="_blank" href="https://realpython.com/">Here is the link to the website</a></p>
<h3 id="heading-5-freecodecamporghttpswwwfreecodecamporg"><a target="_blank" href="https://www.freecodecamp.org/">5. FreeCodeCamp.org</a></h3>
<p>FreeCodeCamp.org is a website that contains several blog entries about Python programming (and many other programming languages). Also, it has a well-structured curriculum teaching nearly every aspect of Python programming. You are able to join several courses online that are free and allow you to earn certificates.</p>
<p>FreeCodeCamp is a good learning tool for beginners and seniors. You do not need any requirements to join the courses because the platform provides everything that is necessary to finish the course and receive the certification.</p>
<p><a target="_blank" href="https://www.freecodecamp.org/">Here is a link to the website</a></p>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>I hope this compilation might be useful for you and help you grow in your development career as a Python developer. If you are new to coding, the best way to learn Python is to just start with it and see how things go. Python is one of the easiest programming languages for beginners (only the basics!), so you won’t have to spend much time troubleshooting your code or learning how the syntax works. You can see results fast and start building your first small programs sooner than you may think.</p>
<p>If I missed your favorite Python video tutorial, the book you loved, or a website you cannot live without please leave me a comment!</p>
<p>I would love to hear your ideas and thoughts about these sources.</p>
<p>If you have any questions, please jot them down below. I try to answer them if possible.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/how-to-start-with-python-programming/">https://www.paulsblog.dev/how-to-start-with-python-programming/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://medium.knulst.de">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, and <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>.</p>
<hr />
<h2 id="heading-support-this-content">🙌 Support this content</h2>
<p>If you like this content, please consider <a target="_blank" href="https://www.paulsblog.dev/support/">supporting me</a>. You can share it on social media or <a target="_blank" href="https://buymeacoffee.com/paulknulst">buy me a coffee</a>! Any support helps!</p>
<p>Furthermore, you can <a target="_blank" href="https://www.paulsblog.dev/newsletter/">sign up for my newsletter</a> to show your contribution to my content. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<p>Thanks! 🥰</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@hiteshchoudhary?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Hitesh Choudhary</a> / <a target="_blank" href="https://unsplash.com/?utm_source=ghost&amp;utm_medium=referral&amp;utm_campaign=api-credit">Unsplash</a></p>
]]></content:encoded></item><item><title><![CDATA[Harden Your Website With Traefik And Security Headers]]></title><description><![CDATA[Paul Knulst  in  Security • 5 min read

Everyone knows it’s really important to have a good security score on several websites. Within this tutorial, I will explain how I used traefik to get one.
Important: I moved the website in the screenshots from...]]></description><link>https://hashnode.knulst.de/harden-your-website-with-traefik-and-security-headers</link><guid isPermaLink="true">https://hashnode.knulst.de/harden-your-website-with-traefik-and-security-headers</guid><category><![CDATA[Security]]></category><category><![CDATA[Programming Blogs]]></category><category><![CDATA[software development]]></category><category><![CDATA[Docker]]></category><category><![CDATA[Traefik]]></category><dc:creator><![CDATA[Paul Knulst]]></dc:creator><pubDate>Wed, 23 Nov 2022 09:15:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1669109017330/_fmxFGWcr.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a target="_blank" href="https://www.paulsblog.dev/">Paul Knulst</a>  in  <a target="_blank" href="https://www.paulsblog.dev/tag/security/">Security</a> • 5 min read</p>
<hr />
<p>Everyone knows it’s really important to have a good security score on several websites. Within this tutorial, <strong>I will explain how I used traefik to get one</strong>.</p>
<p><strong>Important:</strong> <strong>I moved the website in the screenshots from</strong> <a target="_blank" href="https://www.f1nalboss.de/"><strong>https://www.f1nalboss.de</strong></a> <strong>to</strong> <a target="_blank" href="https://ftp.f1nalboss.de/"><strong>https://ftp.f1nalboss.de</strong></a> <strong>after I wrote this article.</strong></p>
<h2 id="heading-precondition">Precondition</h2>
<p>If you want to apply the content from this tutorial you need to have a Docker environment in Docker Swarm Mode. If you want to know how to set up one for yourself try <a target="_blank" href="https://www.paulsblog.dev/docker-swarm-in-a-nutshell/">following this tutorial that I wrote</a>.</p>
<p>Also, you need to have traefik installed as a load balancer because it is used for adding security headers to your website requests. <a target="_blank" href="https://www.paulsblog.dev/services-you-want-to-have-in-a-swarm-environment/">I have written another tutorial</a> that shows how to integrate traefik in your Docker Swarm environment.</p>
<hr />
<p><strong>If you did not have a Docker Swarm</strong> and you don't want to set up one you can try to install traefik as a load balancer on a single machine. <a target="_blank" href="https://www.paulsblog.dev/how-to-setup-traefik-with-automatic-letsencrypt-certificate-resolver/">I explain how this is done in this tutorial</a>. But keep in mind that for non-Docker Swarm environments you have to adjust the docker-compose files. If you have questions about that just ask in the comments and I will answer them if possible.</p>
<h2 id="heading-how-i-added-security-headers">How I Added Security Headers</h2>
<p>There is a very important website published by Mozilla: <a target="_blank" href="https://observatory.mozilla.org/">The Mozilla Observatory</a></p>
<blockquote>
<p><strong>The Mozilla Observatory has helped over 240,000 websites by teaching developers, system administrators, and security professionals how to configure their sites safely and securely.</strong></p>
</blockquote>
<p>I ran a check for my site and got the following result:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://www.paulsblog.dev/content/images/2022/09/image--19-.webp" alt="Harden Your Website With Traefik And Security Headers" /></td></tr>
</thead>
<tbody>
<tr>
<td><center> <em>First test on Mozilla Observatory with the result: F</em></center></td></tr>
</tbody>
</table>
</div><p>After quick research at <a target="_blank" href="https://doc.traefik.io/traefik/v2.0/middlewares/headers/">traefik documentation</a>, I came up with these important headers which I want to implement in my environment:</p>
<pre><code class="lang-bash">accesscontrolallowmethods=GET, OPTIONS, PUT, POST, PATCH
accesscontrolmaxage=100
addvaryheader-true
hostsproxyheaders=X-Forwarded-Host
sslredirect=<span class="hljs-literal">true</span>
X-Forwarded-Proto=https
stsseconds=63072000
stsincludesubdomains=<span class="hljs-literal">true</span>
stspreload=<span class="hljs-literal">true</span>
forcestsheader=<span class="hljs-literal">true</span>
framedeny=<span class="hljs-literal">true</span>
contenttypenosniff=<span class="hljs-literal">true</span>
browserxssfilter=<span class="hljs-literal">true</span>
referrerpolicy=same-origin
featurepolicy=camera <span class="hljs-string">'none'</span>; geolocation <span class="hljs-string">'none'</span>; microphone <span class="hljs-string">'none'</span>; payment <span class="hljs-string">'none'</span>; usb <span class="hljs-string">'none'</span>; vr <span class="hljs-string">'none'</span>;
customresponseheaders.X-Robots-Tag=none,noarchive,nosnippet,notranslate,noimageindex,
</code></pre>
<p>To add these headers in my traefik installation I added a new middleware to my traefik docker-compose.yml:</p>
<pre><code class="lang-yaml">    [<span class="hljs-string">...</span>]
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.accesscontrolallowmethods=GET,</span> <span class="hljs-string">OPTIONS,</span> <span class="hljs-string">PUT</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.accesscontrolmaxage=100</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.addvaryheader=true</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.hostsproxyheaders=X-Forwarded-Host</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.sslredirect=true</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.sslproxyheaders.X-Forwarded-Proto=https</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.stsseconds=63072000</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.stsincludesubdomains=true</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.stspreload=true</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.forcestsheader=true</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.framedeny=true</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.contenttypenosniff=true</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.browserxssfilter=true</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.referrerpolicy=same-origin</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.featurepolicy=camera</span> <span class="hljs-string">'none'</span><span class="hljs-string">;</span> <span class="hljs-string">geolocation</span> <span class="hljs-string">'none'</span><span class="hljs-string">;</span> <span class="hljs-string">microphone</span> <span class="hljs-string">'none'</span><span class="hljs-string">;</span> <span class="hljs-string">payment</span> <span class="hljs-string">'none'</span><span class="hljs-string">;</span> <span class="hljs-string">usb</span> <span class="hljs-string">'none'</span><span class="hljs-string">;</span> <span class="hljs-string">vr</span> <span class="hljs-string">'none'</span><span class="hljs-string">;</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.security-headers.headers.customresponseheaders.X-Robots-Tag=none,noarchive,nosnippet,notranslate,noimageindex</span>
    [<span class="hljs-string">...</span>]
</code></pre>
<p>After adding this middleware I updated my traefik service that runs within a Docker Swarm (cf. <a target="_blank" href="https://www.paulsblog.dev/services-you-want-to-have-in-a-swarm-environment/">this article</a>)</p>
<pre><code class="lang-bash">docker stack deploy -c docker-compose.traefik.yml traefik
</code></pre>
<p>This addition made it possible to use this middleware within every service deployed to my swarm and managed by traefik. To do this I had to add the following line to the <strong><strong>labels section</strong></strong> within the docker-compose.yml:</p>
<pre><code class="lang-yaml">    [<span class="hljs-string">...</span>]
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.simpleweb-https.middlewares=security-headers</span>
    [<span class="hljs-string">...</span>]
</code></pre>
<p>After restarting the simpleweb service I ran another test and got a B score because of this error:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://www.paulsblog.dev/content/images/2022/09/image--20-.webp" alt="Harden Your Website With Traefik And Security Headers" /></td></tr>
</thead>
<tbody>
<tr>
<td><center> <em>Content Security Policy Error on Mozilla Observatory</em></center></td></tr>
</tbody>
</table>
</div><p>I researched and found the correct traefik header for CSP. Additionally, I created a very strict CSP directive and added it to the header value:</p>
<pre><code class="lang-yaml">    <span class="hljs-string">contentsecuritypolicy=default-src</span> <span class="hljs-string">'none'</span><span class="hljs-string">;</span> <span class="hljs-string">img-src</span> <span class="hljs-string">'self'</span> <span class="hljs-string">https://i.postimg.cc;</span> <span class="hljs-string">script-src</span> <span class="hljs-string">'self'</span><span class="hljs-string">;</span> <span class="hljs-string">style-src</span> <span class="hljs-string">'self'</span><span class="hljs-string">"</span>
</code></pre>
<p>I exclusively allow access to <a target="_blank" href="https://i.postimg.cc/">https://i.postimg.cc</a> for img-src because the only picture I use at <a target="_blank" href="https://ftp.f1nalboss.de/">https://ftp.f1nalboss.de</a> is hosted there.</p>
<p>Because this header is very strict I did <strong><strong>not create a global middleware</strong></strong> for it. I upgraded the service in which I want to use this CSP directive by adding this line to the simpleweb service and adjusting the middleware:</p>
<pre><code class="lang-yaml">    [<span class="hljs-string">...</span>]
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.simpleweb-https.middlewares=security-headers,</span> <span class="hljs-string">simpleweb-csp</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.middlewares.simpleweb-csp.headers.contentsecuritypolicy=default-src</span> <span class="hljs-string">'none'</span><span class="hljs-string">;</span> <span class="hljs-string">img-src</span> <span class="hljs-string">'self'</span> <span class="hljs-string">https://i.postimg.cc;</span> <span class="hljs-string">script-src</span> <span class="hljs-string">'self'</span><span class="hljs-string">;</span> <span class="hljs-string">style-src</span> <span class="hljs-string">'self'</span>
    [<span class="hljs-string">...</span>]
</code></pre>
<p>I restarted the service and ran another check to finally achieve an A+!</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><img src="https://www.paulsblog.dev/content/images/2022/09/image--21-.webp" alt="Harden Your Website With Traefik And Security Headers" /></td></tr>
</thead>
<tbody>
<tr>
<td><center><em>The final result on Mozilla Observatory after using security headers in Traefik; result: A+</em></center></td></tr>
</tbody>
</table>
</div><h2 id="heading-how-i-harden-my-website">How I Harden My Website</h2>
<p>Because I also want to <strong>harden</strong> my server I ran a check at <a target="_blank" href="https://www.hardenize.com/">hardenize.com</a> and got non-satisfying results for <strong>TLS</strong> and <strong>HSTS</strong>.</p>
<p>Due to already having adjusted the traefik headers for HSTS beforehand, the only thing left to do was submit my domain name at <a target="_blank" href="https://hstspreload.org/">hstspreload</a> to fix the <strong><strong>HSTS</strong></strong> issue.</p>
<p><strong>THIS PART IS VERY IMPORTANT:</strong> If you want to submit your website to <strong>hstspreload.org</strong> think about it very carefully. There can be problems if you cannot fulfill everything: <a target="_blank" href="https://hstspreload.org/#opt-in">read here for information</a></p>
<p>Solving the <strong>TLS</strong> issue was more involved. I had to adjust the traefik configuration so that the minimum version of TLS is 1.2 and <strong>NOT</strong> 1.0, as is the default. To set a minimum <strong>TLS</strong> version I added a file provider to my traefik installation within the command section of my traefik docker-compose.yml:</p>
<pre><code class="lang-yaml">   <span class="hljs-attr">command:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--providers.docker</span>
      [<span class="hljs-string">...</span>]
      <span class="hljs-bullet">-</span> <span class="hljs-string">--providers.file.directory=/configuration/</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">--providers.file.watch=true</span>
</code></pre>
<p>Then I created a new configuration file (called tls.toml) which contains an entry <strong><strong>[tls-options]</strong></strong> and should be used to set the minimum TLS version to 1.2. Additionally, I added excellent cipher suites (<a target="_blank" href="https://www.thesslstore.com/blog/cipher-suites-algorithms-security-settings/">read this to know about cipher suites</a>).</p>
<pre><code class="lang-yaml">[<span class="hljs-string">tls.options</span>]
  [<span class="hljs-string">tls.options.mintls12</span>]
    <span class="hljs-string">minVersion</span> <span class="hljs-string">=</span> <span class="hljs-string">"VersionTLS12"</span>

    <span class="hljs-string">cipherSuites</span> <span class="hljs-string">=</span> [
        <span class="hljs-string">"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"</span>,
        <span class="hljs-string">"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"</span>,
        <span class="hljs-string">"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"</span>,
        <span class="hljs-string">"TLS_AES_128_GCM_SHA256"</span>,
        <span class="hljs-string">"TLS_AES_256_GCM_SHA384"</span>,
        <span class="hljs-string">"TLS_CHACHA20_POLY1305_SHA256"</span>
    ]

    <span class="hljs-string">sniStrict</span> <span class="hljs-string">=</span> <span class="hljs-literal">true</span>
</code></pre>
<p>This configuration guarantees that traefik will use at least TLS 1.2 with the provided cipher suites. I chose these six cipher suites because I want to have three for TLS 1.2 and three for TLS 1.3 which are declared safe!</p>
<p>I saved the file within the ./configuration/ folder and updated the volume section within the traefik docker-compose.yml:</p>
<pre><code class="lang-yaml">    <span class="hljs-attr">volumes:</span>
      [<span class="hljs-string">...</span>]
      <span class="hljs-bullet">-</span> <span class="hljs-string">./configuration:/configuration</span>
</code></pre>
<p>After that, I restarted the traefik instance.</p>
<p>The last step was activating the min TLS version within the simpleweb service by adding a new label:</p>
<pre><code class="lang-yaml">    [<span class="hljs-string">...</span>]
    <span class="hljs-bullet">-</span> <span class="hljs-string">traefik.http.routers.simpleweb-https.tls.options=mintls12@file</span>
    [<span class="hljs-string">...</span>]
</code></pre>
<p>After a restart of the simpleweb service I retried the test on <strong>Hardenize</strong> and <strong>Mozilla</strong> and got the wanted results:</p>
<ul>
<li><a target="_blank" href="https://www.hardenize.com/report/f1nalboss.de/1628027450">https://www.hardenize.com/report/f1nalboss.de/1628027450</a></li>
<li><a target="_blank" href="https://observatory.mozilla.org/analyze/ftp.f1nalboss.de">https://observatory.mozilla.org/analyze/ftp.f1nalboss.de</a></li>
</ul>
<p>There is also an informative test at <a target="_blank" href="https://www.ssllabs.com/ssltest">ssl-labs</a> which checks certificates. With the new configuration, I also got an <strong>A+</strong></p>
<ul>
<li><a target="_blank" href="https://www.ssllabs.com/ssltest/analyze.html?d=ftp.f1nalboss.de">https://www.ssllabs.com/ssltest/analyze.html?d=ftp.f1nalboss.de</a></li>
</ul>
<h2 id="heading-closing-notes">Closing Notes</h2>
<p>To simplify everything you can use <a target="_blank" href="https://ftp.f1nalboss.de/data/docker-compose.simpleweb.example.yml">this docker-compose.yml</a> and run it within any Docker Swarm as www-stack service:</p>
<pre><code class="lang-bash">docker stack deploy -c docker-compose-simpleweb.example.yml www-stack
</code></pre>
<p>I hope you find this tutorial helpful and are now able to secure your website and increase trust by adding security headers and optimizing SSL usage.</p>
<p>I would love to hear your ideas and thoughts. If you already run a traefik installation and use different headers/middleware or you have other cipher suites please comment here and explain. Also, if you have any questions, please jot them down below. I try to answer them if possible.</p>
<p>This article was originally published on my blog at <a target="_blank" href="https://www.paulsblog.dev/harden-your-website-with-traefik-and-security-headers/">https://www.paulsblog.dev/harden-your-website-with-traefik-and-security-headers/</a></p>
<p>Feel free to connect with me on <a target="_blank" href="https://www.paulsblog.dev">my personal blog</a>, <a target="_blank" href="https://medium.knulst.de">Medium</a>, <a target="_blank" href="https://www.linkedin.com/in/paulknulst/">LinkedIn</a>, <a target="_blank" href="https://twitter.com/paulknulst">Twitter</a>, and <a target="_blank" href="https://github.com/paulknulst">GitHub</a>.</p>
<hr />
<p>Did you find this article valuable? Want to support the author? (... and support development of current and future tutorials!). You can sponsor me on <a target="_blank" href="https://buymeacoffee.com/paulknulst">Buy Me a Coffee</a> or <a target="_blank" href="https://ko-fi.com/paulknulst">Ko-Fi</a>. Furthermore, you can become a free or paid member by <a target="_blank" href="https://www.paulsblog.dev/#/portal">signing up to my website</a>. See the <a target="_blank" href="https://www.paulsblog.dev/contribute/">contribute page</a> for all (free or paid) ways to say thank you!</p>
<hr />
<p>Photo by <a target="_blank" href="https://unsplash.com/@pconrad?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Peter Conrad</a> / <a target="_blank" href="https://unsplash.com/@pconrad?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></p>
]]></content:encoded></item></channel></rss>