<?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[Andreas Karabetian | A dev’s perspective]]></title><description><![CDATA[Andreas Karabetian | A dev’s perspective]]></description><link>https://blog.karabetian.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 21 Apr 2026 10:40:35 GMT</lastBuildDate><atom:link href="https://blog.karabetian.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Getting started with GitLab CI/CD]]></title><description><![CDATA[In this tutorial, we will set an automated pipeline to build a Next.js Docker image and then push it to GitLab container registry. We will start by creating our ci file and then register a runner to execute our pipelines.
As always, here is the offic...]]></description><link>https://blog.karabetian.dev/getting-started-with-gitlab-cicd</link><guid isPermaLink="true">https://blog.karabetian.dev/getting-started-with-gitlab-cicd</guid><category><![CDATA[GitLab]]></category><category><![CDATA[Docker]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[docker images]]></category><dc:creator><![CDATA[Andreas Karabetian]]></dc:creator><pubDate>Sat, 03 Feb 2024 12:12:08 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1706863104441/e5b4cb30-f0ba-4f2f-a11d-da55ab9b4f95.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this tutorial, we will set an automated pipeline to build a Next.js Docker image and then push it to GitLab container registry. We will start by creating our <code>ci</code> file and then register a runner to execute our pipelines.</p>
<p>As always, here is the <a target="_blank" href="https://docs.gitlab.com/ee/tutorials/create_register_first_runner/">official documentation</a> link.</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>The only thing required is a GitLab project containing our <strong>source code</strong> and <strong>Dockerfile</strong>.</p>
<h1 id="heading-gitlab-runners">GitLab Runners</h1>
<h3 id="heading-overview">Overview</h3>
<p>A GitLab Runner is an open source application that executes the jobs of a pipeline. We can install this program in four different ways:</p>
<ul>
<li><p>Local server</p>
</li>
<li><p>Docker container</p>
</li>
<li><p>VM</p>
</li>
<li><p>Cloud Provider</p>
</li>
</ul>
<p>For this example we will use a <strong>Docker container</strong>, as it simplifies the whole process.</p>
<p>Runners can also be categorized as:</p>
<ol>
<li><p><strong>Shared:</strong> All project in a GitLab instance can use this Runner</p>
</li>
<li><p><strong>Group:</strong> All projects in a specific group can use this Runner</p>
</li>
<li><p><strong>Project:</strong> Only a specific project can use this Runner</p>
</li>
</ol>
<p>For this example we will create a Runner that will be assigned to a specific <strong>Project</strong>.</p>
<h3 id="heading-executors">Executors</h3>
<p>Now that we know what Runners are, let's dive a bit deeper on how they work. An executor is the environment on which our pipeline will run. These also come in different flavors, so some examples include:</p>
<ul>
<li><p>Virtual Box</p>
</li>
<li><p>Shell</p>
</li>
<li><p>Docker</p>
</li>
<li><p>Kubernetes</p>
</li>
<li><p>...</p>
</li>
</ul>
<p>So for example, a <strong>Shell</strong> executor will run the pipeline directly on the host shell, a <strong>Docker</strong> executor will create a container to execute the pipeline and so on.</p>
<p>Yes, we will be using a <strong>Docker executor</strong>. Good guess!</p>
<h1 id="heading-creating-our-cicd">Creating our CI/CD</h1>
<p>Time to start implementing our pipeline. In order to do that we need to take a few setup steps. These include:</p>
<ol>
<li><p>Create a ci <code>.yml</code> file</p>
</li>
<li><p>Create a Runner container</p>
</li>
<li><p>Register the Runner to the GitLab project</p>
</li>
</ol>
<h3 id="heading-1-starting-with-the-ci-file">1. Starting with the ci file</h3>
<p>In order to run a pipeline on a GitLab project we need to create a <code>.gitlab-ci.yml</code> file on the root of our project's source code.</p>
<p>Let's see an example:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">variables:</span> 
  <span class="hljs-attr">DOCKER_TLS_CERTDIR:</span> <span class="hljs-string">"/certs"</span>

<span class="hljs-attr">docker-build:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">docker:latest</span>
    <span class="hljs-attr">stage:</span> <span class="hljs-string">build</span>
    <span class="hljs-attr">services:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">docker:dind</span>
    <span class="hljs-attr">script:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">echo</span> <span class="hljs-string">$CI_REGISTRY_PASSWORD</span> <span class="hljs-string">|</span> <span class="hljs-string">docker</span> <span class="hljs-string">login</span> <span class="hljs-string">-u</span> <span class="hljs-string">$CI_REGISTRY_USER</span> <span class="hljs-string">$CI_REGISTRY</span> <span class="hljs-string">--password-stdin</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">build</span> <span class="hljs-string">-t</span> <span class="hljs-string">$CI_REGISTRY_IMAGE:latest</span> <span class="hljs-string">.</span>
        <span class="hljs-bullet">-</span> <span class="hljs-string">docker</span> <span class="hljs-string">push</span> <span class="hljs-string">$CI_REGISTRY_IMAGE:latest</span>
</code></pre>
<p>Here's what's going on:</p>
<ul>
<li><p>The <code>docker-build</code> job uses the latest image of Docker as base image</p>
</li>
<li><p>We name this stage <code>build</code></p>
</li>
<li><p><code>dind</code> (“Docker-in-Docker”) means running Docker within a Docker container. Thus, a new Docker engine is spawned within a container, providing an isolated environment for managing containers.</p>
</li>
<li><p>After that, we begin our script commands:</p>
<ol>
<li><p>We login to our repository's registry using the <code>docker login</code> command. Notice how we can directly pull the <code>PASSWORD</code> , <code>USER</code> and <code>REGISTRY</code> variables from the platform.</p>
</li>
<li><p>We build our application image</p>
</li>
<li><p>We push the image to the repository registry</p>
</li>
</ol>
</li>
</ul>
<p>You can find more information in the official documentation:</p>
<ul>
<li><p><a target="_blank" href="https://docs.gitlab.com/ee/ci/docker/using_docker_images.html">Run your CI/CD jobs in Docker containers</a></p>
</li>
<li><p><a target="_blank" href="https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-docker">Use Docker to build Docker images</a></p>
</li>
</ul>
<h3 id="heading-2-creating-our-runner-container">2. Creating our Runner container</h3>
<p>As we previously said, we need to create a docker container that has the Runner software listening for requests. Using a <code>docker-compose.yml</code> file we can describe the service like so:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">version:</span> <span class="hljs-string">'3.8'</span>

<span class="hljs-attr">services:</span>
  <span class="hljs-attr">gitlab-runner:</span>
    <span class="hljs-attr">image:</span> <span class="hljs-string">gitlab/gitlab-runner:alpine</span>
    <span class="hljs-attr">container_name:</span> <span class="hljs-string">gitlab-runner</span>
    <span class="hljs-attr">volumes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">/var/run/docker.sock:/var/run/docker.sock</span>
</code></pre>
<p>The only thing to notice here is how we mount the host <code>docker.sock</code> inside the container, in order to be utilized by the Runner service.</p>
<p>Then we simply <code>docker compose up -d</code> !</p>
<h3 id="heading-3-register-the-runner">3. Register the Runner</h3>
<p>And the final part is to register our Runner on the GitLab repository. Here's what we need to do:</p>
<p>Firs navigate to your GitLab project <strong>Settings &gt; CI/CD</strong> and expand the <strong>Runners</strong> tab. Here we need to create a new project Runner configuration. Click on the <strong>New project runner</strong> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706959999601/ffa39654-90ff-4182-ad1e-fb8dad983fa2.png" alt class="image--center mx-auto" /></p>
<p>The configuration needed for this is:</p>
<ol>
<li><p><strong>Select Linux</strong> as operating system</p>
</li>
<li><p>Provide a <strong>Runner description</strong></p>
</li>
<li><p>Check the <strong>Run untagged jobs</strong>, as we won't be using tags</p>
</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706960200901/9e469a7b-a9db-4d40-b27f-81f5287fe4ff.png" alt class="image--center mx-auto" /></p>
<p>After clicking the submit button, our Runner configuration is ready. The important thing to keep here is the <code>--url</code> parameter provided by GitLab, as well as the <code>--token</code></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1706960417524/11af3027-0122-47b2-9a07-4cd90d6cde92.png" alt class="image--center mx-auto" /></p>
<p>Great, now we just need to register this configuration to our Runner container!</p>
<p>To do that we need to enter the container shell so that we can execute the register command. To do that type:</p>
<pre><code class="lang-bash">docker <span class="hljs-built_in">exec</span> -it &lt;container-name&gt; bash
</code></pre>
<p>Now, inside the Runner container, we execute the following command:</p>
<pre><code class="lang-bash">gitlab-runner register -n \
  --url <span class="hljs-string">"URL"</span> \
  --registration-token REGISTRATION_TOKEN \
  --executor docker \
  --description <span class="hljs-string">"My Docker Runner"</span> \
  --docker-image <span class="hljs-string">"docker:24.0.5"</span> \
  --docker-privileged \
  --docker-volumes <span class="hljs-string">"/certs/client"</span>
</code></pre>
<h1 id="heading-running-the-pipeline">Running the pipeline</h1>
<p>That was it! 🎉</p>
<p>The Runner is registered and all that's left to do is run our pipeline. We can do that from the project <strong>CI/CD &gt; Pipelines</strong> tab.</p>
<p>After the image gets built, it will be automatically to our GitLab container registry with the tag <code>:latest</code>.</p>
<p>Till next time! ✌️</p>
]]></content:encoded></item><item><title><![CDATA[How to build and push a Docker image using GitHub actions 🤖]]></title><description><![CDATA[In this tutorial, we will use an automated pipeline to build Docker images and then push them to a remote registry. For this example, we will use Docker Hub.
As always, here is the official documentation link for GitHub actions.
Prerequisites
Before ...]]></description><link>https://blog.karabetian.dev/how-to-build-and-push-a-docker-image-using-github-actions</link><guid isPermaLink="true">https://blog.karabetian.dev/how-to-build-and-push-a-docker-image-using-github-actions</guid><category><![CDATA[GitHub]]></category><category><![CDATA[Docker]]></category><category><![CDATA[ci-cd]]></category><category><![CDATA[docker images]]></category><category><![CDATA[github-actions]]></category><dc:creator><![CDATA[Andreas Karabetian]]></dc:creator><pubDate>Thu, 08 Sep 2022 13:45:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662712462242/ueXq3309p.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this tutorial, we will use an automated pipeline to build Docker images and then push them to a remote registry. For this example, we will use <a target="_blank" href="https://hub.docker.com/">Docker Hub</a>.</p>
<p>As always, here is the <a target="_blank" href="https://docs.github.com/en/actions">official documentation</a> link for GitHub actions.</p>
<h1 id="heading-prerequisites">Prerequisites</h1>
<p>Before we start implementing our pipeline, we need to set up our container registry and GitHub repository.</p>
<h3 id="heading-docker-hub">Docker Hub</h3>
<p>Our action will require access to a Docker Hub repository. The preferred method of authentication is the use of an access token. Here's what you need to do:</p>
<ol>
<li>Create an account on Docker Hub</li>
<li>Create a repository with the desired name of your image</li>
<li>Click on <strong>Account settings</strong> -&gt; <strong>Privacy</strong>, and generate a new access token.</li>
</ol>
<blockquote>
<p>Note: Make sure to save the token's value because it will not appear again after a page refresh. </p>
</blockquote>
<h3 id="heading-github">GitHub</h3>
<p>Select a GitHub repository where you want to implement the automated action and follow these steps:</p>
<ol>
<li>Click on <strong>Settings</strong> -&gt; <strong>Secrets</strong> -&gt; <strong>Actions</strong> and select <strong>New repository secret</strong>.</li>
<li>Create a secret named <strong>DOCKERHUB_USERNAME</strong> and add your Docker Hub username as a value.</li>
<li>Create another one named <strong>DOCKERHUB_TOKEN</strong> and add your Docker Hub access token that you generated earlier as a value.</li>
</ol>
<h1 id="heading-creating-our-action">Creating our action</h1>
<p>Once we have the container registry and GitHub repo set up, we can create the workflow file. To do that:</p>
<ol>
<li>Go to your GitHub repository and click on the tab <strong>Actions</strong>.</li>
<li>There you will find some suggested pre-built workflows, and an option to <strong>set up a workflow yourself</strong>. Click on that.</li>
</ol>
<h3 id="heading-writting-the-file">Writting the file</h3>
<p>Each workflow is represented by a <code>.yml</code> document. </p>
<p>Below is our <strong>Docker image build and push</strong> .yml file.</p>
<pre><code class="lang-yml"><span class="hljs-attr">name:</span> <span class="hljs-string">Docker</span> <span class="hljs-string">image</span> <span class="hljs-string">build</span> <span class="hljs-string">and</span> <span class="hljs-string">push</span>

<span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span> [ <span class="hljs-string">"master"</span> ]
  <span class="hljs-attr">pull-request:</span>

<span class="hljs-attr">jobs:</span>

  <span class="hljs-attr">build-push:</span>

    <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
    <span class="hljs-attr">steps:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v3</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Login</span> <span class="hljs-string">to</span> <span class="hljs-string">DockerHub</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">docker/login-action@v2</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">username:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.DOCKERHUB_USERNAME</span> <span class="hljs-string">}}</span>
          <span class="hljs-attr">password:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.DOCKERHUB_TOKEN</span> <span class="hljs-string">}}</span>

      <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">and</span> <span class="hljs-string">push</span>
        <span class="hljs-attr">uses:</span> <span class="hljs-string">docker/build-push-action@v3</span>
        <span class="hljs-attr">with:</span>
          <span class="hljs-attr">context:</span> <span class="hljs-string">.</span>
          <span class="hljs-attr">push:</span> <span class="hljs-literal">true</span>
          <span class="hljs-attr">tags:</span> <span class="hljs-string">&lt;username&gt;/&lt;repository&gt;:latest</span>
</code></pre>
<p>Here's what's going on:</p>
<ol>
<li><code>name:</code> The name of your GitHub action.</li>
<li><code>on:</code> Controls when your action will run.</li>
<li><code>push:</code> and <code>pull-request:</code> Triggers the workflow on every push request at the master branch, and every pull-request.</li>
<li><code>jobs:</code> A workflow consists of one or many jobs that can run sequentially or in parallel. For this example, we only use one job named <strong>build-push</strong></li>
<li><code>runs-on:</code> The OS you want for the runner environment that executes the workflow. For this example, we will use <strong>Ubuntu </strong> as it has Docker pre-configured. Other popular options are <strong>windows-latest</strong> or <strong>macos-latest</strong>.</li>
<li><p><code>steps:</code> Each step runs in its own process in the runner environment. Steps consist of:</p>
<ul>
<li><code>name:</code> Describes each step.</li>
<li><p><code>uses:</code> Selects a pre-built action to run as part of a step in your job.</p>
<p>For example here we are using the <strong>actions/checkout@v3</strong>, <strong>docker/login-action@v2</strong> and <strong>docker/build-push-action@v3</strong>, as part of our workflow.</p>
<p> You can also use any custom action from the <a target="_blank" href="https://github.com/marketplace?type=actions">GitHub actions marketplace</a>.</p>
</li>
<li><p><code>with:</code> Input parameters for this step's pre-built action.</p>
<p>For example, in the second step, we pass as parameters a <code>username:</code> and <code>password:</code>. Notice how the values are drawn from the <strong>repository secrets</strong> that we set up earlier.</p>
<p>In the third step, we have a <code>context:</code> parameter that indicates the <strong>location of our Dockerfile</strong>, a <code>push:</code> parameter set to <strong>true</strong> and a <code>tags:</code> parameter where we can define what tags we want for our Docker Hub repository.</p>
</li>
</ul>
</li>
</ol>
<p>More on GitHub actions syntax <a target="_blank" href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions">here</a>.</p>
<h3 id="heading-deploying-the-workflow">Deploying the workflow</h3>
<p>That was it! 🎉 </p>
<p>All that's left to do is deploy our action for the first time, and we can do that by <strong>committing</strong> this file to our repository. This will save it under <code>.github/workflows/docker-image.yml</code>, and trigger the workflow. </p>
<p>After the image gets built, it will be uploaded to your Docker Hub repository with the tag <code>:latest</code>.</p>
<p>You can find the overview of each workflow under the <strong>Actions</strong> tab on your GitHub repository, as well as a detailed log of each job step.</p>
<p>Till next time! ✌️</p>
]]></content:encoded></item><item><title><![CDATA[How to use routers with Express and split your API logic 🗃️]]></title><description><![CDATA[Is your app.js API file becoming a big pile of code nonsense? Do you have trouble distinguishing one endpoint from another? Well then, this tutorial is definitely for you!
In this article, we will take a look at how you can split your endpoints logic...]]></description><link>https://blog.karabetian.dev/how-to-use-routers-with-express-and-split-your-api-logic</link><guid isPermaLink="true">https://blog.karabetian.dev/how-to-use-routers-with-express-and-split-your-api-logic</guid><category><![CDATA[Node.js]]></category><category><![CDATA[Express.js]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Andreas Karabetian]]></dc:creator><pubDate>Mon, 29 Aug 2022 22:28:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662712607512/UZQrMRh9a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Is your <code>app.js</code> API file becoming a big pile of code nonsense? Do you have trouble distinguishing one endpoint from another? Well then, this tutorial is definitely for you!</p>
<p>In this article, we will take a look at how you can split your endpoints logic into different files by utilizing the  <code>express.Router</code> class.</p>
<p>As always, here is the <a target="_blank" href="https://expressjs.com/en/guide/routing.html">official documentation</a> link for a more detailed look at Express routing.</p>
<h1 id="heading-the-basic-setup">The basic setup</h1>
<p>Let’s suppose that our API looks like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'hello world'</span>);
})

app.post(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
  <span class="hljs-comment">// User login code</span>
  <span class="hljs-comment">// …</span>
})
</code></pre>
<p>This is the most basic setup, and it’s quite ok if you don’t plan on using too many endpoints. </p>
<p>But let’s suppose we want to <strong>add another path</strong> to our API. Our code would look like this:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

<span class="hljs-comment">// Home route - - - - - - -</span>
app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'hello world'</span>);
})

app.post(<span class="hljs-string">"/"</span>, <span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
  <span class="hljs-comment">// User login code</span>
  <span class="hljs-comment">// …</span>
})

<span class="hljs-comment">// Register route - - - - - - - - - -</span>
app.get(<span class="hljs-string">"/register"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.send(<span class="hljs-string">'Register an account'</span>);
})

app.post(<span class="hljs-string">"/register"</span>, <span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> user = req.body.username;
  <span class="hljs-keyword">const</span> password = req.body.password; <span class="hljs-comment">// Always encrypt passwords</span>
})
</code></pre>
<p>Now it starts to get a bit difficult to read, hence the comments to section out the different routes.</p>
<h1 id="heading-a-better-technique">A better technique</h1>
<p>Luckily, we can create <strong>chainable route handlers</strong> for each path, and reduce some of the repetitive code. </p>
<p>We can do that by using the <code>.route()</code> built-in method. Here’s how that works:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

<span class="hljs-comment">// Home route - - -</span>
app.route(<span class="hljs-string">"/"</span>)
  .get(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    res.send(<span class="hljs-string">'hello world'</span>);
  })
  .post(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-comment">// User login code</span>
  })

<span class="hljs-comment">// Register route - - -</span>
app.route(<span class="hljs-string">"/register"</span>)
  .get(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    res.send(<span class="hljs-string">'Register an account'</span>);
  })
  .post(<span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> user = req.body.username;
    <span class="hljs-keyword">const</span> password = req.body.password; <span class="hljs-comment">// Always encrypt passwords</span>
  })
</code></pre>
<p>Yea, that looks much better. But still, it’s not practical for a large number of endpoints, since all of our code is on the same file. You can imagine how messy that can get if we just keep adding more.</p>
<h1 id="heading-the-routers-solution">The routers solution</h1>
<p>Let’s take a step back and look at our project’s directory tree. </p>
<p>At this point it’s very simple, just our <code>app.js</code> file:</p>
<pre><code class="lang-bash">/project-root-folder
  app.js
</code></pre>
<p>But in order to start using routers, it is best practice if we make a separate directory to store them. So let’s make one:</p>
<pre><code class="lang-bash">/project-root-folder
  app.js
  /routers
</code></pre>
<p>Great, now we can start creating our router files. Let’s make one for our <code>"/"</code> endpoint named <strong>homeRoute.js</strong>:</p>
<pre><code class="lang-bash">/project-root-folder
  app.js
  /routers
    homeRoute.js
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> router = express.Router();

router.route(<span class="hljs-string">"/"</span>)
  .get(<span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
    res.send(<span class="hljs-string">'hello world'</span>);
  })
  .post(<span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
    <span class="hljs-comment">// User login code</span>
  })

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<p>Okay, so what is going on here? </p>
<p>A router file is a JavaScript <strong>module</strong>, a piece of code that can be imported when needed. More on modules <a target="_blank" href="https://nodejs.org/api/modules.html">here</a>. For it to work, you need to <strong>require express</strong> and use the <code>module.exports</code> command at the end of the file.</p>
<p>Right, so how on earth do we use that in our <code>app.js</code> file? Here’s how:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

<span class="hljs-comment">// Import the homeRoute.js file</span>
<span class="hljs-keyword">const</span> homeRoute = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/homeRoute'</span>);

<span class="hljs-comment">// Use it as an endpoint</span>
app.use(homeRoute);
</code></pre>
<p>Now that’s what I’m talking about!</p>
<p>Let’s implement the <code>"/register"</code> route in the same fashion and call it a day:</p>
<pre><code class="lang-bash">/project-root-folder
  app.js
  /routers
    homeRoute.js
    registerRoute.js
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> router = express.Router();

router.route(<span class="hljs-string">"/register"</span>)
  .get(<span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
    res.send(<span class="hljs-string">'Register an account'</span>);
  })
  .post(<span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> user = req.body.username;
    <span class="hljs-keyword">const</span> password = req.body.password; <span class="hljs-comment">// Always encrypt passwords</span>
  })

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<p>And in <code>app.js</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> app = express();

<span class="hljs-keyword">const</span> homeRoute = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/homeRoute'</span>);
<span class="hljs-keyword">const</span> registerRoute = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/registerRoute'</span>);

app.use(homeRoute);
app.use(registerRoute);
</code></pre>
<h1 id="heading-conclusions">Conclusions</h1>
<p>And that was it! 🎉 </p>
<p>I hope this helped you organize your Express API as it grows bigger with more endpoints. In a future blog post, we will explore the concept of <strong>controllers</strong>, a more advanced technique for splitting your JavaScript logic.</p>
<p>Till next time! ✌️</p>
]]></content:encoded></item><item><title><![CDATA[How to add GitHub OAuth to your Node.js application 🔐]]></title><description><![CDATA[Okay, so you've got your Node.js project up and running, with your sign-in / up routes and all. But you want to add this fancy Sign in using GitHub button that you see on every major tech website. Well, this article is for you then!
For this tutorial...]]></description><link>https://blog.karabetian.dev/how-to-add-github-oauth-to-your-nodejs-application</link><guid isPermaLink="true">https://blog.karabetian.dev/how-to-add-github-oauth-to-your-nodejs-application</guid><category><![CDATA[Node.js]]></category><category><![CDATA[GitHub]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[iwritecode]]></category><dc:creator><![CDATA[Andreas Karabetian]]></dc:creator><pubDate>Sat, 27 Aug 2022 16:00:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1662712532978/sDrxxbPYS.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Okay, so you've got your Node.js project up and running, with your sign-in / up routes and all. But you want to add this fancy <strong>Sign in using GitHub</strong> button that you see on every major tech website. Well, this article is for you then!</p>
<p>For this tutorial, we will use vanilla Node.js and <a target="_blank" href="https://expressjs.com/">Express</a>, as there really is no need for any extra middleware like <a target="_blank" href="https://www.passportjs.org/">Passport.js</a>. It's a really simple process and it only requires you to follow a few basic steps.</p>
<p>Also, make sure to take a look at the official <a target="_blank" href="https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps">documentation page</a> for a more detailed walkthrough.</p>
<h1 id="heading-the-oauth-flow">The OAuth flow</h1>
<p>Let's take a quick look at how GitHub OAuth works under the hood before we start to implement anything.</p>
<ol>
<li>The user clicks on the sign-in button and gets redirected to GitHub, where he can authorize your application.</li>
<li>GitHub then redirects back to your application, providing us with a temporary <strong>code</strong> as a URL parameter (query string).</li>
<li>Your application can then exchange this temp code for an <strong>access token</strong>. This token can be used to retrieve user data.</li>
</ol>
<h1 id="heading-creating-an-oauth-app">Creating an OAuth app</h1>
<p>First of all, we need to create a GitHub OAuth app. </p>
<p>Head over to your GitHub account settings, and then navigate to <a target="_blank" href="https://github.com/settings/developers">Developer settings</a> at the very bottom. Select the <strong>OAuth Apps</strong> option, and then click <strong>New OAuth App</strong>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661591999393/cSkJS2sZK.jpg" alt="new_oauth_app.jpg" /></p>
<p>Now you need to fill in some information, regarding your application. This will be:</p>
<ol>
<li><strong>Application name</strong>: Give your OAuth application a name.</li>
<li><strong>Homepage URL</strong>: This is your application home page URL including the port that it is running at.</li>
<li><strong>Application description</strong>: Pretty self-explanatory, not required for this demo.</li>
<li><strong>Authorization callback URL</strong>: This is the most important field. This is your application server endpoint that will receive a request with the <strong>temporary code</strong>.</li>
</ol>
<blockquote>
<p>Sidenote: You can change all of this later, don't worry if you mistyped something.</p>
</blockquote>
<p>Click the <strong>Register application</strong> button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661592943141/e0RJXIjD_.jpg" alt="register_oauth_application.jpg" /></p>
<p>The last thing we need to do is to generate a client secret. Once you generate that, make sure to take note of both <strong>client secret</strong> and the <strong>client id</strong>.</p>
<blockquote>
<p>Important note: Write down the client secret at the moment of generation because you will not be able to see it again after a page refresh. (You are also prompted to do so by GitHub as well). </p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661594997364/HVnvqU_kN.jpg" alt="our_application.jpg" /></p>
<h1 id="heading-client-side">Client side</h1>
<p>Fire up your VS Code champ, <a target="_blank" href="https://www.youtube.com/watch?v=OPaCXU4mwR8">it's coding time</a>!</p>
<p>Let's set up our <strong>Sign in with GitHub</strong> button on our client side. The script below will trigger a redirect to the GitHub authorization page, where the user will give your application access. </p>
<p>As you can see this process requires our <strong>client id</strong> and <strong>redirect URL</strong> that we set earlier.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"githubAuth"</span>&gt;</span>Sign in with GitHub<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">

    <span class="hljs-keyword">const</span> clientId = <span class="hljs-string">"10705503950effe4aa1e"</span>; <span class="hljs-comment">//Your client id here</span>
    <span class="hljs-keyword">const</span> redirectURL = <span class="hljs-string">'http://localhost:3000/api/auth/github/'</span>;
    <span class="hljs-keyword">const</span> url = <span class="hljs-string">"https://github.com/login/oauth/authorize?client_id="</span>+clientId+<span class="hljs-string">"&amp;redirect_uri="</span>+redirectURL;

    $(<span class="hljs-string">"#githubAuth"</span>).click(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">window</span>.location.href = url;
    });

</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Click the button and you will see the following screen:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661602394247/BmUgBE91p.jpg" alt="authorise_page.jpg" /></p>
<p>By clicking the green <strong>Authorize "username"</strong> button you will be redirected to the URL that we set previously, in this case: <code>http://localhost:3000/api/auth/github/</code>, with a query string <code>code</code>. </p>
<p>We then need to extract this information in order to proceed.</p>
<h1 id="heading-server-side">Server side</h1>
<p>Time we build our API so that we can execute some HTTP requests.</p>
<p>As mentioned, a request will be sent on the <code>/api/auth/github/</code> endpoint. This will also require our <strong>client id</strong> as well as our <strong>client secret</strong>. </p>
<p>It's good practice to store these values in a <code>.env</code> file, but for this example we will simply use local variables. </p>
<p>So let's build that:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);

<span class="hljs-keyword">const</span> app = express();

<span class="hljs-keyword">const</span> clientId = <span class="hljs-string">'10705503950effe4aa1e'</span>; <span class="hljs-comment">// Your client id</span>
<span class="hljs-keyword">const</span> clientSecret = <span class="hljs-string">'SECRET'</span>; <span class="hljs-comment">// Your client secret</span>

app.route(<span class="hljs-string">"/api/auth/github/"</span>)
  .get(<span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
      <span class="hljs-comment">//Get the temp code from the request</span>
      <span class="hljs-keyword">const</span> code = req.query.code;
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"[INFO] - Got the temp code: "</span> + code);
  });

app.listen(<span class="hljs-number">3000</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'App listening on port 3000'</span>);
</code></pre>
<p>Great, now that we have our <strong>temp code</strong>, we can make a <code>POST</code> request to <code>https://github.com/login/oauth/access_token</code> in order to get our <strong>access token</strong>.</p>
<p>For this request, we will also need our <strong>client id</strong> and <strong>client secret</strong>. To make the <code>POST</code> request, I'm using the <code>node-fetch</code> library, but it's totally up to preference. </p>
<p>Let's create this functionality:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Building the request URL</span>
<span class="hljs-keyword">const</span> url = <span class="hljs-string">"https://github.com/login/oauth/access_token?client_id="</span>+clientId+<span class="hljs-string">"&amp;client_secret="</span>+clientSecret+<span class="hljs-string">"&amp;code="</span>+code;

<span class="hljs-comment">//Get the access token from GitHub</span>
fetch(url, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
    <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Accept'</span>: <span class="hljs-string">'application/json'</span> }
})
.then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
.then(<span class="hljs-function"><span class="hljs-params">json</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> token = json.access_token;
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"[INFO] - Got the access token: "</span> + token);
});
</code></pre>
<p>Cool, now let's use this token to receive user information. </p>
<p>All we have to do is another <code>POST</code> request to this URL: <code>https://api.github.com/user</code>. We also need to include the access token we just acquired as a header parameter. Here's the code block:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//Get the user data from GitHub</span>
fetch(<span class="hljs-string">"https://api.github.com/user"</span>, {
    <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Authorization'</span>: <span class="hljs-string">'token '</span> + token }
})
.then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
.then(<span class="hljs-function"><span class="hljs-params">json</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> user = json.login;
    <span class="hljs-keyword">const</span> organization = json.company;
    <span class="hljs-keyword">const</span> email = json.email;
    <span class="hljs-keyword">const</span> image = json.avatar_url;

    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"[INFO] - Got github data for user: "</span> + json.login);
});
</code></pre>
<p>And that was it! 🎉 We have successfully acquired the user information we need for our application. </p>
<p>So to put it all together now, here's the finalized express application:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);
<span class="hljs-keyword">const</span> fetch = <span class="hljs-built_in">require</span>(<span class="hljs-string">'node-fetch'</span>);

<span class="hljs-keyword">const</span> app = express();

<span class="hljs-keyword">const</span> clientId = <span class="hljs-string">'10705503950effe4aa1e'</span>; <span class="hljs-comment">// Your client id</span>
<span class="hljs-keyword">const</span> clientSecret = <span class="hljs-string">'SECRET'</span>; <span class="hljs-comment">// Your client secret</span>

app.route(<span class="hljs-string">"/api/auth/github/"</span>)
  .get(<span class="hljs-function">(<span class="hljs-params">req,res</span>) =&gt;</span> {
      <span class="hljs-comment">//Get the temp code from the request</span>
      <span class="hljs-keyword">const</span> code = req.query.code;
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"[INFO] - Got the temp code: "</span> + code);

      <span class="hljs-comment">// Building the request URL</span>
      <span class="hljs-keyword">const</span> url = <span class="hljs-string">"https://github.com/login/oauth/access_token?client_id="</span>+clientId+<span class="hljs-string">"&amp;client_secret="</span>+clientSecret+<span class="hljs-string">"&amp;code="</span>+code;

      <span class="hljs-comment">//Get the access token from GitHub</span>
      fetch(url, {
          <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
          <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Accept'</span>: <span class="hljs-string">'application/json'</span> }
      }).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
        .then(<span class="hljs-function"><span class="hljs-params">json</span> =&gt;</span> {
          <span class="hljs-keyword">const</span> token = json.access_token;
          <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"[INFO] - Got the access token: "</span> + token);

          <span class="hljs-comment">//Get the user data from GitHub</span>
          fetch(<span class="hljs-string">"https://api.github.com/user"</span>, {
              <span class="hljs-attr">headers</span>: { <span class="hljs-string">'Authorization'</span>: <span class="hljs-string">'token '</span> + token }
          })
          .then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> res.json())
          .then(<span class="hljs-function"><span class="hljs-params">json</span> =&gt;</span> {
              <span class="hljs-keyword">const</span> user = json.login;
              <span class="hljs-keyword">const</span> organization = json.company;
              <span class="hljs-keyword">const</span> email = json.email;
              <span class="hljs-keyword">const</span> image = json.avatar_url;

              <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"[INFO] - Got github data for user: "</span> + json.login);
        });
      });
  });

app.listen(<span class="hljs-number">3000</span>);
<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'App listening on port 3000'</span>);
</code></pre>
<p>Note: GitHub OAuth App utilizes the <strong>scope</strong> concept, something that this tutorial <strong>does not</strong> cover. Scopes determine what permissions your OAuth App has. By default, your OAuth App has access to some basic public information about the requested user. </p>
<p>You can find more information on scopes at the official <a target="_blank" href="https://docs.github.com/en/developers/apps/building-oauth-apps/scopes-for-oauth-apps">documentation page</a>.</p>
<h1 id="heading-conclusions">Conclusions</h1>
<p>Now you can use GitHub as a method to manage user data for your application. There are a lot more uses to the GitHub OAuth Apps that you can utilize in your future applications, but that's a story for another post ;)</p>
<p>Till next time! ✌️</p>
]]></content:encoded></item><item><title><![CDATA[First time attending a tech conference 👨🏻‍💻!]]></title><description><![CDATA[Hello everyone 👋! 
In this article, i will talk about my experience during the WordCamp Athens 2022 conference, organized by the WordPress community.
Some background
This was actually the first time i ever attended a tech conference, something i alw...]]></description><link>https://blog.karabetian.dev/first-time-attending-a-tech-conference</link><guid isPermaLink="true">https://blog.karabetian.dev/first-time-attending-a-tech-conference</guid><category><![CDATA[Developer]]></category><category><![CDATA[WordPress]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Web Design]]></category><category><![CDATA[conference]]></category><dc:creator><![CDATA[Andreas Karabetian]]></dc:creator><pubDate>Thu, 25 Aug 2022 22:33:42 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1661448040387/BoU5tEljm.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Hello everyone 👋! 
In this article, i will talk about my experience during the WordCamp Athens 2022 conference, organized by the WordPress community.</p>
<h1 id="heading-some-background">Some background</h1>
<p>This was actually the first time i ever attended a tech conference, something i always wanted to do since i study computer science. But many of these big-name conferences were never held in Greece, or at least i wasn’t aware of any. Not to mention that these major events had very expensive tickets. So going abroad was <strong>a big no</strong> for me. But then i started digging a little deeper, i joined some local community slack groups and there it was. The <code>events</code> channel. This is where it all started.</p>
<p>At the time i was an undergraduate student, working for a research group at my university department (but that’s a story for another time). But i also had experience in building websites using the <a target="_blank" href="https://wordpress.com/">WordPress</a> CMS. I’ve been doing this job since my early high school years, helping out my dad as a graphic designer who got into website design. So yea, i was very familiar with the WordPress platform and terms like <strong>HTML</strong>, <strong>SEO</strong>, <strong>plugins</strong>, <strong>domain names</strong> and <strong>hosting</strong>.</p>
<h1 id="heading-finding-the-event">Finding the event</h1>
<p>So back to our story. Scrolling through the <code>events</code> channel of this slack group, i found a post about a <a target="_blank" href="https://central.wordcamp.org/">WordCamp</a> conference. At first i didn’t know what it was about. But after reading the description i realized that it was a community-organized event, dedicated to the WordPress platform and web technologies in general. Also held in Athens, great news! </p>
<p>I immediately rushed to see the ticket price and, in my surprise, it was very cheap compared to the ones organized abroad (✨including the student discount✨). That was it, my chance to attend one of these cool conferences. But i didn’t want to go alone. See now, the problem was that none of my friends were into programming / tech stuff, thus they would be terribly bored to go with me. But then i thought of my two research colleagues (the group that i mentioned earlier), and so i texted them. They were really excited about it, so i booked the tickets and we were ready to go!</p>
<h1 id="heading-the-big-day">The big day</h1>
<h3 id="heading-entering-the-conference">Entering the conference</h3>
<p>On the 9th of April, my friend and I are walking towards the event location. Unfortunately, one of the guys couldn’t make it due to being sick a few days before, and was still recovering. Nonetheless, we managed to get him a bag of goodies and swag, as well as his personal name tag. Stonks!</p>

<img class="image-margin" src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661457789257/Kw-sUOGjq.jpeg" />
<b><em>Bags full of loot!</em></b>


<p>The conference took place at the <a target="_blank" href="https://www.megaron.gr/en/">Megaron Athens Concert Hall</a>, a beautiful building that i had never visited before. There was a big hall where all the sponsors had set their banners and filled their tables with even more swag! Notebooks, pens, stickers and t-shirts, even stress balls! We knew we were in for a treat.</p>

<img class="image-margin" src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661460167031/MRNuhmqR2.jpeg" />



<img class="image-margin" src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661460180599/2_SrEyQlF.jpeg" />
<b><em>GoDaddy stand - they had a cool social media contest</em></b>


<h3 id="heading-attending-the-speeches">Attending the speeches</h3>
<p>Of course there were two rooms for all the speeches and presentations that took place during the event. These were split in two tracks, <strong>A</strong> and <strong>B</strong>. Both tracks were executed in parallel, so we had to choose which speech to attend each time.</p>
<p>Overall every presentation was very constructive and had a lot to offer, not necessarily about WordPress, but rather any subject related to website-building or managing data on the web. As a junior-level developer, i had no problem following any of the subjects as they were well-organized by every speaker and not heavily detailed with technical information.</p>

<img class="image-margin" src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661462026616/whAjyN93n.jpeg" />
<b><em>Room A- the big amphitheater</em></b>



<img class="image-margin" src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661463519308/-51LrYb7h.jpeg" />
<b><em>Room B- a smaller and rather flat room</em></b>


<h3 id="heading-eat-oclock">Eat o’clock</h3>
<p>There also was a lunch break with provided food 😋, which we really appreciated and enjoyed in a nearby park under the sun. We were each given a bag with a sandwich of choice (meat or fish), balsamic vinegar salad, chocolate cake for dessert, and of course some soda drinks.</p>
<p>As i mentioned there is a <a target="_blank" href="https://goo.gl/maps/bTUP3C5NkGBvcRX37">really cool park</a> next to the Megaro building, perfect for a picnic or a quick walk. If you ever happen to be nearby, make sure to pay a visit!</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661468295455/qikYOi_rt.jpeg" alt="5730D0EF-D9F4-4C05-B615-42812343DCF2.jpeg" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>I had so much fun attending WordCamp this year and i am really looking forward to doing so next year as well. The WordPress community is very passionate and well organized, kudos to all of the volunteers that made it all possible! 👏</p>
<p>Finally, here’s a collage of me and my buddy <a target="_blank" href="https://www.linkedin.com/in/karamolegkos/">Panos</a> having a blast and enjoying our first tech conference! (check out <a target="_blank" href="https://karamolegkos.dev">karamolegkos.dev</a> for some epic <em>no-css</em> stuff!)</p>
<p>Till next time! ✌️</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1661465613362/1ZeG9blLf.jpeg" alt="67ED20D7-D73C-43B0-91E9-18B66B5B0910.jpeg" /></p>
<p><em>All photos were taken by the official <a target="_blank" href="https://athens.wordcamp.org/2022/">WordCamp Athens</a> event website</em></p>
]]></content:encoded></item></channel></rss>