<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Webserver &#8211; Digitaldocblog</title>
	<atom:link href="https://digitaldocblog.com/category/webserver/feed/" rel="self" type="application/rss+xml" />
	<link>https://digitaldocblog.com</link>
	<description>Various digital documentation</description>
	<lastBuildDate>Thu, 01 Jan 2026 08:02:37 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</generator>

<image>
	<url>https://digitaldocblog.com/wp-content/uploads/2022/08/cropped-website-icon-star-500-x-452-transparent-32x32.png</url>
	<title>Webserver &#8211; Digitaldocblog</title>
	<link>https://digitaldocblog.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Run Vaultwarden and Caddy on your Linux Server with docker-compose</title>
		<link>https://digitaldocblog.com/webserver/run-vaultwarden-and-caddy-on-your-linux-server-with-docker-compose-2/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Fri, 05 Sep 2025 05:45:33 +0000</pubDate>
				<category><![CDATA[Server]]></category>
		<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Security]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=252</guid>

					<description><![CDATA[Vaultwarden is a very light, easy to use and very well documented alternative implementation of the Bitwarden Client API. It is perfect if you struggle with the complex Bitwarden installation&#8230;]]></description>
										<content:encoded><![CDATA[
<p><a href="https://github.com/dani-garcia/vaultwarden" title="Vaultwarden on GitHub">Vaultwarden</a> is a very light, easy to use and very well documented alternative implementation of the <a href="https://bitwarden.com/help/self-host-bitwarden/" title="Bitwarden Self-Hosted">Bitwarden Client API</a>. It is perfect if you struggle with the complex Bitwarden installation but want to self-host your own password management server and connect your Bitwarden Clients which are installed on your computer or mobile device. In this documentation I describe the steps to configure and run Vaultwarden on a Ubuntu Linux 22.04 using docker-compose services. In parallel you should read the <a href="https://github.com/dani-garcia/vaultwarden/wiki" title="Vaultwarden Wiki">Vaultwarden Wiki</a> to understand the complete background.</p>




<h3 class="wp-block-heading">Prepare your Server</h3>



<p>Before we start we need to prepare the server. In this step we create the environment to manage the vaultwarden instance. Login to your server using your standard User (not root).</p>




<h4 class="wp-block-heading">Basic requirements</h4>



<p>Login to your server with SSH and authenticate with keys. Never use simple password authentication. You must create a private and a public SSH key-pair on your Host Machine and copy only the public SSH key to your Remote Server. Keep your private key safe on your Host machine. Then copy your public key to your Remote Server and configure your ssh-deamon on your Remote Server. Disable password authentication and root login in your configuration on your Remote Server. How all this works is very good explained on <a href="https://linuxize.com/post/how-to-set-up-ssh-keys-on-ubuntu-1804/" title="Linuxize.com">Linuxize.com</a>.</p>




<p>You must ensure that SSL certificates for your server are installed. I use free <a href="https://letsencrypt.org/" title="Letsencrypt - Encryption for Everybody">LetsEncrypt</a> certificates and <em>certbot</em> to install and renew my certificates on the system. Therefore pls. read on my <a href="https://digitaldocblog.com/" title="Digitaldocblog - Patrick Rottländer nurdy Ressources">Digitaldocblog</a> in the Article <a href="https://digitaldocblog.com/webserver/ssl-certificates-with-lets-encrypt-and-certbot-on-a-linux-server/" title="SSL Certificates with Lets Encrypt and certbot on a Linux Server"> SSL Certificates with Lets Encrypt and certbot on a Linux Server </a>. Here you find a very detailed description of how you can do this .</p>




<p>You must ensure that docker and docker-compose is installed on your system. Therefore pls. read on my <a href="https://digitaldocblog.com/" title="Digitaldocblog - Patrick Rottländer nurdy Ressources">Digitaldocblog</a> a very detailed description of how you can do this in the Article <a href="https://digitaldocblog.com/webserver/containerize-a-nodejs-app-and-nginx-with-docker-on-ubuntu-2204/" title="Containerize a nodejs app with nginx">Containerize a nodejs app with nginx</a>. You should read the following chapters:</p>




<ul class="wp-block-list">
	<li>Prepare the System for secure Downloads from Docker</li>
	<li>Install Docker from Docker Resources</li>
	<li>Install standalone docker-compose from Docker Resources<br></li>
</ul>



<p>Make sure that your server is running behind a firewall. In my case I have a virtual server and I am responsible for server security. Therefore I install and configure a firewall on my system. </p>




<p>Before you configure the firewall be sure that your ssh service is running and on which port your ssh service is running. This is important to know because you don’t want to lock yourself out. First you check if the ssh service is running with <em> systemctl </em> and then on which port ssh is running using <em>netstat</em>. If <em>netstat</em> is not installed on your system you can do this with <em>apt</em>. </p>




<pre class="wp-block-code"><code>#control ssh status
sudo systemctl status ssh

#check ssh port
sudo netstat -tulnp | grep ssh

#check if net-tools (include netstat) are installed
which netstat

#install net-tools only in case not installed
sudo apt install net-tools
</code></pre>



<p>I install <em>ufw</em> (uncomplicated firewall) on my server and configure it to provide only SSH and HTTPS to the outside world. </p>




<pre class="wp-block-code"><code># check if ufw is installed
ufw version
which ufw

#install ufw if not installed
sudo apt install ufw

#open SSH and HTTPS
sudo ufw allow OpenSSH
sudo ufw allow 443
sudo ufw allow 80/tcp

#Default rules
sudo ufw default deny incoming
sudo ufw default allow outgoing

#Start the firewall
sudo ufw enable

#Check the firewall status
sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere                  
443                        ALLOW IN    Anywhere                  
80/tcp                     ALLOW IN    Anywhere                  
22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)             
443 (v6)                   ALLOW IN    Anywhere (v6)             
80/tcp (v6)                ALLOW IN    Anywhere (v6)

</code></pre>



<h4 class="wp-block-heading">Port 443 must run TCP and UDP</h4>



<p>In the Docker environment, we will later configure Caddy as a reverse proxy for the vaultwarden service. Caddy requires both TCP and UDP on port 443. This is due to a modern protocol called HTTP/3 (QUIC).</p>




<p>Traditionally, the web (HTTP/1.1 and HTTP/2) ran exclusively over the TCP protocol. TCP is very reliable, but sometimes a bit slow when establishing a connection. Google and others subsequently developed QUIC, on which the new standard HTTP/3 is based. QUIC uses UDP instead of TCP.</p>




<p>HTTP/3 QUIC is significantly faster when establishing a connection (handshake). It handles packet loss better, which is especially important for mobile connections on smartphones, for example, when using the Bitwarden app.</p>




<p>Caddy is a very modern reverse proxy that has HTTP/3 enabled by default. The &#8222;normal&#8220; HTTPS connection (HTTP/1.1 or HTTP/2) is established over TCP port 443. Caddy offers clients (Browsers or the Bitwarden app) the option to switch to HTTP/3 via UDP port 443.</p>




<p>With vaultwarden, users often access their vaults via smartphones using LTE or Wi-Fi connections. When UDP port 443 is open, the app uses HTTP/3. This results in a more stable connection and improved vault synchronization due to reduced latency.</p>




<h4 class="wp-block-heading">Why must Port 80 be open</h4>



<p>When you follow the instructions in the Article <a href="https://digitaldocblog.com/webserver/ssl-certificates-with-lets-encrypt-and-certbot-on-a-linux-server/" title="SSL Certificates with Lets Encrypt and certbot on a Linux Server"> SSL Certificates with Lets Encrypt and certbot on a Linux Server </a> then you are using <em>certbot</em> and install your <a href="https://letsencrypt.org/" title="Letsencrypt - Encryption for Everybody">LetsEncrypt</a> certificates on your local server with the following <em>certbot</em> command :</p>




<pre class="wp-block-code"><code>sudo certbot certonly --standalone -d &lt;yourDomain&gt;
</code></pre>



<p>Here you use the method <em>standalone</em>. Whenever you renew your certificates  <em>certbot</em> starts a temporary web server to prove ownership of your domain to <a href="https://letsencrypt.org/" title="Letsencrypt - Encryption for Everybody">LetsEncrypt</a>.</p>




<p>When renewing your certificates <em>certbot</em> attempts to start a temporary web server on port 80. Therefore, port 80 must be open on your system via your firewall rules.</p>




<p>Important: If another web server (such as Nginx or Apache) is already running permanently on port 80, this step will fail unless the service is stopped. So, if a web server is running on port 80, this service must be permanently stopped.</p>




<p>After Certbot successfully starts the web server on port 80, the following happens:</p>




<ul class="wp-block-list">
	<li><strong>ACME Challenge:</strong> Certbot contacts the Let&#8217;s Encrypt servers. These provide Certbot with a random string (token).</li>
	<li><strong>Deployment:</strong> Certbot creates a small file on the spot at the URL <em>http://yourDomain/.well-known/acme-challenge/token</em></li>
	<li><strong>Verification:</strong> The Let&#8217;s Encrypt servers now attempt to access this exact URL via the public internet. If they find the file (and the correct token), it proves that you own the server under this domain.<br></li>
</ul>



<p>Once the verification is successful, the temporary standalone web server is immediately shut down and port 80 is opened. Certbot generates a new private key (if configured) and signs the new certificate. The new certificate files are stored in the directory <em>/etc/letsencrypt/archive/ </em> and the symbolic links in the directory <em>/etc/letsencrypt/live/</em> are updated.</p>




<h4 class="wp-block-heading">Create new user vaultwarden</h4>



<p>You are logged in with your standarduser. From the home directory of the standarduser you create the user vaultwarden and create the hidden directory /home/vaultwarden/.ssh. Then copy the  authorized_keys file from your .ssh directory into the new created .ssh directory of the new user vaultwarden and set the owner and permissions.</p>




<p>The new user vaultwarden should be in sudo group to perform commands under root using sudo. And the user vaultwarden should be in docker group to perform docker commands without using sudo. </p>




<pre class="wp-block-code"><code>#logged-in with standard user and create new user 
sudo adduser vaultwarden

#create hidden .ssh directory in new users home directory
sudo mkdir /home/vaultwarden/.ssh

#copy authorized_keys file to enable ssh key login for new user
cd /home/standarduser/.ssh
sudo cp authorized_keys /home/vaultwarden/.ssh

#set the owner vaultwarden and permissions
sudo chown -R vaultwarden:vaultwarden /home/vaultwarden/.ssh
sudo chmod 700 /home/vaultwarden/.ssh
sudo chmod 600 /home/vaultwarden/.ssh/authorized_keys

#check permissions
ls -al /home/vaultwarden
drwx-- vaultwarden vaultwarden 4096 May 11 14:20 .ssh

ls -l /home/vaultwarden/.ssh
-rw-- vaultwarden vaultwarden 400 May 11 14:20 authorized_keys

#add user vaultwarden to sudo- and docker group
sudo usermod -aG sudo vaultwarden
sudo usermod -aG docker vaultwarden

#check vaultwarden groups (3 groups: vaultwarden sudo docker)
sudo groups vaultwarden
vaultwarden : vaultwarden sudo docker

</code></pre>



<h4 class="wp-block-heading">Create /opt/vaultwarden directory</h4>



<p>Login with the new user vaultwarden. Stay logged in as vaultwarden for the next steps. Do not perform the next steps or the installation of the vaultwarden server with root or any other user. </p>




<p>After you are logged in with vaultwarden you create a new directory /opt/vaultwarden. This is the runtime directory of your vaultwarden application and the place from where the docker containers will be started.</p>




<pre class="wp-block-code"><code>sudo mkdir /opt/vaultwarden
sudo chmod -R 700 /opt/vaultwarden

ls -l /opt/vaultwarden
drwx--vaultwarden vaultwarden 4096 Mai 16 07:06 vaultwarden
 
</code></pre>



<p>Then change into /opt/vaultwarden. You create the /opt/vaultwarden/vw-data directory which is the host directory for the docker containers. One of these containers will be started under the container name vaultwarden. This container vaultwarden run with root privileges and write into this host directory /opt/vaultwarden/vw-data.</p>




<pre class="wp-block-code"><code>mkdir /opt/vaultwarden/vw-data

ls -l /opt/vaultwarden
drwxrwxr-x 6 vaultwarden vaultwarden 4096 Mai 15 15:54 vw-data
`
</code></pre>



<h4 class="wp-block-heading">Create /opt/vaultwarden/certs and copy your SSL certificates</h4>



<p>The container vaultwarden is the web application where you can log-in and manage your passwords. As we will se below the container will be started under the user vaultwarden in /opt/vaultwarden and run with root privileges behind a reverse proxy server. As reverse proxy I will use <a href="https://caddyserver.com" title="Caddy Server Platform">Caddy</a> which is a powerful platform. Caddy manages the requests from the outside world and forward requests via an internal docker network to the vaultwarden server. </p>




<p>Caddy must accept only HTTPS connections and use the standard directory for certificates /opt/vaultwarden/certs. Therefore we create this directory.</p>




<p>I use Letsencrypt certificates which are managed automatically by certbot and stored in the directory /etc/letsencrypt on my host server. The keys are set up the following structure: </p>




<ul class="wp-block-list">
	<li>In /etc/letsencrypt/live/&lt;domain&gt; there are only symlinks that point </li>
	<li>to the real files in /etc/letsencrypt/archive/&lt;domain&gt;. <br></li>
</ul>



<p>Copy the fullchain.pem and privkey.pem files from /etc/letsencrypt/live/&lt;domain&gt; to /opt/vaultwarden/certs and set the permissions accordingly. </p>




<pre class="wp-block-code"><code>#create certs directory
mkdir /opt/vaultwarden/certs

#change into certs directory
cd /opt/vaultwarden/certs

#copy the files
cp /etc/letsencrypt/live/&lt;domain&gt;/fullchain.pem fullchain.pem
cp /etc/letsencrypt/live/&lt;domain&gt;/privkey.pem privkey.pem

#set owner and group vaultwarden
chown vaultwarden:vaultwarden fullchain.pem
chown vaultwarden:vaultwarden privkey.pem

#set access (read write only vaultwarden)
chmod 600 privkey.pem
chmod 600 fullchain.pem

</code></pre>



<p><strong>note:</strong> The cp command will place the actual files (not the symlinks) into the directory /opt/vaultwarden/certs unless you specify a -P or -d option. So the cp command follows the symlinks and grab the actual files behind. This is important when the certificates have been renewed and new certificate files are stored behind the symlinks. </p>




<h3 class="wp-block-heading">Run Vaultwarden and Caddy as docker containers</h3>



<p>The following setup creates a secure, email-enabled Vaultwarden instance behind a Caddy reverse proxy with HTTPS and admin access, running entirely via Docker.</p>




<h4 class="wp-block-heading">Create docker-compose.yml</h4>



<p>This docker-compose.yml file sets up two services Vaultwarden and Caddy to host a self-hosted password manager with HTTPS support.</p>




<pre class="wp-block-code"><code>#docker-compose.yml

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: always
    environment:
      DOMAIN: "&lt;yourDomain&gt;"
      SIGNUPS_ALLOWED: "false"
      SMTP_HOST: "&lt;yourSmtpServer&gt;"
      SMTP_FROM: "&lt;yourEmail&gt;"
      SMTP_FROM_NAME: "&lt;yourName&gt;"
      SMTP_USERNAME: "&lt;yourEmail&gt;"
      SMTP_PASSWORD: "&lt;yourSmtpPasswd&gt;"
      SMTP_SECURITY: "force_tls"
      SMTP_PORT: "465"
      ADMIN_TOKEN: '&lt;yourAdminToken&gt;'
    volumes:
      - ./vw-data:/data

  caddy:
    image: caddy:2
    container_name: caddy
    restart: always
    ports:
      - 443:443
      - 443:443/udp 
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-config:/config
      - ./caddy-data:/data
      - ./certs:/certs:ro
    environment:
      DOMAIN: "&lt;yourDomain&gt;"
      LOG_FILE: "/data/access.log"
</code></pre>



<p><strong>vaultwarden service:</strong></p>




<p>Runs the Vaultwarden server (a lightweight Bitwarden-compatible backend).</p>




<ul class="wp-block-list">
	<li>Disables user signups (SIGNUPS_ALLOWED: &#8222;false&#8220;).</li>
	<li>Configures SMTP settings for sending emails (e.g. for password resets).</li>
	<li>Sets a secure admin token (using Argon2 hash) to access the /admin interface.</li>
	<li>Persists Vaultwarden data to ./vw-data on the host.<br></li>
</ul>



<p><strong>Enable admin page access:</strong></p>




<p>With the ADMIN_TOKEN set we enable login to the admin site via &lt;yourDomain&gt;/admin. First create a secure admin password. The &lt;yourAdminToken&gt; value can be created with your admin password piped into argon2. The result is a hash value that must be a little modified and then inserted as &lt;yourAdminToken&gt;. It is important that you use single quotes in docker-compose.yml when you insert &lt;yourAdminToken&gt;. </p>




<pre class="wp-block-code"><code>sudo apt install -y argon2
echo -n '&lt;yourAdminPassword&gt;' | argon2 somesalt -e

#This is the result of the argon2 hashing
$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$D...
</code></pre>



<p>Then you modify the hash by adding a $ sign in front of each $ sign in the hash. In this case we add 5 $ signs.</p>




<pre class="wp-block-code"><code>#original value
$argon2i$v=19$m=4096,t=3,p=1$c29thbHQ$D...

#modified value
$$argon2i$$v=19$$m=4096,t=3,p=1$$c29thbHQ$$D...
</code></pre>



<p>Then you put the modified value in single quotes into docker-compose.yml.</p>




<pre class="wp-block-code"><code>#docker-compose.yml

.....

ADMIN_TOKEN: '$$argon2i$$v=19$$m=4096,t=3,p=1$$c29thbHQ$$D...'

</code></pre>



<p>Now you can access the admin page with &lt;yourDomain&gt;/admin and login with your admin password. </p>




<p><strong>caddy service:</strong></p>




<p>Uses Caddy web server to reverse proxy to Vaultwarden.</p>




<ul class="wp-block-list">
	<li>Handles HTTPS using custom certificates from ./certs.</li>
	<li>Binds to port 443 for secure access.</li>
	<li>Reads its configuration from ./Caddyfile.</li>
	<li>Logs access to /data/access.log (mapped from ./caddy-data on the host).<br></li>
</ul>



<h4 class="wp-block-heading">Create Caddyfile</h4>



<p>Caddy is a modern, powerful web server that automatically handles HTTPS, reverse proxying, and more. Caddy acts as a secure HTTPS reverse proxy, forwarding external requests to the Vaultwarden Docker container running on internal port 80.</p>




<p>This Caddyfile defines how Caddy should serve and protect your vaultwarden instance over HTTPS.</p>




<pre class="wp-block-code"><code>#Caddyfile
https://&lt;domain&gt; {
  log {
    level INFO
    output file /data/access.log {
      roll_size 10MB
      roll_keep 10
    }
  }

  # Use custom certificate and key
  tls /certs/fullchain.pem /certs/privkey.pem

  # This setting may have compatibility issues with some browsers
  # (e.g., attachment downloading on Firefox). Try disabling this
  # if you encounter issues.
  encode zstd gzip

  # Admin path matcher
  @adminPath path /admin*
  
  # Basic Auth for admin access
  handle @adminPath {
    # If admin path require basic auth
    basicauth {
      superadmin &lt;passwdhash&gt;
    }

    reverse_proxy vaultwarden:80 {
      header_up X-Real-IP {remote_host}
    }
  }

  # Everything else
  reverse_proxy vaultwarden:80 {
    header_up X-Real-IP {remote_host}
  }
}
</code></pre>



<p><strong>Domain</strong></p>




<pre class="wp-block-code"><code>https://&lt;domain&gt;
</code></pre>



<p>This defines the domain name Caddy listens on (e.g. https://yourinstance.example.com).</p>




<p><strong>Logging</strong></p>




<pre class="wp-block-code"><code>log {
  level INFO
  output file /data/access.log {
    roll_size 10MB
    roll_keep 10
  }
}
</code></pre>



<p>Logs all access to a file inside the container (/data/access.log), with log rotation.</p>




<p><strong>TLS with Custom Certificates</strong></p>




<pre class="wp-block-code"><code>tls /certs/fullchain.pem /certs/privkey.pem
</code></pre>



<p>Use your own Let&#8217;s Encrypt certificates from mounted files rather than auto-generating them.</p>




<p><strong>Compression</strong></p>




<pre class="wp-block-code"><code>encode zstd gzip
</code></pre>



<p>Enables modern compression methods to improve performance, though may cause issues with attachments on some browsers.</p>




<p><strong>Admin Area Protection</strong></p>




<pre class="wp-block-code"><code>@adminPath path /admin*
</code></pre>



<p>Matches all requests to /admin paths.</p>




<pre class="wp-block-code"><code>handle @adminPath {
  basicauth {
    superadmin &lt;passwdhash&gt;
  }

  reverse_proxy vaultwarden:80 {
    header_up X-Real-IP {remote_host}
  }
}
</code></pre>



<ul class="wp-block-list">
	<li>Requires HTTP Basic Auth for access to /admin.</li>
	<li>Proxies/Forwards authenticated admin requests to the Vaultwarden container.</li>
	<li>Ensures the backend sees the original client IP address.<br></li>
</ul>



<p><strong>All Other Requests</strong></p>




<pre class="wp-block-code"><code>reverse_proxy vaultwarden:80 {
  header_up X-Real-IP {remote_host}
}
</code></pre>



<ul class="wp-block-list">
	<li>Proxies/Forwards all non-/admin traffic directly to Vaultwarden container.</li>
	<li>Ensures the backend sees the original client IP address.<br></li>
</ul>



<p><strong>Protect your admin page</strong></p>




<p>To protect your admin page we can use a HTTP Basic Auth. This mean whenever you access &lt;yourDomain&gt;/admin a login window will pop up in your browser and ask you to provide a user name and a password. We use htpasswd which is part of the apache2-utils. </p>




<pre class="wp-block-code"><code>#install apache2-utils if not available
sudo apt install apache2-utils

#Create a hash for the user admin (you can use any user-name) 
htpasswd -nB admin

New password: 
Re-type new password: 
admin:$2y$05$HZukVJWhWMrT7qMO2n65bm/5JYlt5tO...

</code></pre>



<figure class="wp-block-table">
<table>
	<thead>
		<tr>
			<th>
				Option
			</th>
			<th>
				Description
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>-n</code>
			</td>
			<td>
				Displays the result only on the console instead of writing it to a file.
			</td>
		</tr>
		<tr>
			<td>
				<code>-B</code>
			</td>
			<td>
				Uses the bcrypt hash algorithm, which is supported by Caddy and is very secure.
			</td>
		</tr>
		<tr>
			<td>
				<code>admin</code>
			</td>
			<td>
				The username for basic auth access (for example admin).
			</td>
		</tr>
	</tbody>
</table>
<figcaption>htpasswd options</figcaption>
</figure>



<p>Then you insert only the hash (without admin: ….) into the Caddyfile.</p>




<pre class="wp-block-code"><code>#Caddyfile

.....

handle @adminPath 
bash
  basicauth {
    admin $2y$05$HZukVJWhWMrT7qMO2n65bm/5JYlt5tO...
  }

</code></pre>



<h4 class="wp-block-heading">Create ssl certificates with Letsencrypt and certbot</h4>



<p>You can follow the instructions in my post on <a href="https://digitaldocblog.com/webserver/ssl-certificates-with-lets-encrypt-and-certbot-on-a-linux-server/" title="SSL Certificates with Letsencrypt">digitaldocblog.com</a>. When you followed these instructions your ssl certificates are installed on your server in standalone mode. Whenever you renew your certificates certbot initiates the domain validation challenge and a temporary server will be started trying to listen on port 80. Because we run a web server this port is blocked and the web server must be stoped before we can initiate the renewal process.</p>




<p>To avoid this we should change the certbot renewal from standalone mode to webroot. Webroot is a method that leverages your existing web server to handle the domain validation challenge process without the need to stop the web server.  Certbot places a temporary file with a unique token in a specific directory within your web servers  public &#8222;webroot&#8220; directory (e.g., /var/www/html/.well-known/acme-challenge/). Let&#8217;s Encrypt then sends a request to your domain to retrieve this file. Since your web server is already running, it can serve the file without any interruption to your website&#8217;s availability.</p>




<p>In our <em>docker.compose.yml</em> we defined the backend service <em>vaultwarden</em>. This service is the backend service listening to port 80. We also defined a web server service <em>caddy</em> listening to port 443.   </p>




<p>In our <em>Caddyfile</em> we specified only a web server only for <em>https://&lt;Domain&gt;</em> working as a reverse proxy. Any request for <em>https://&lt;Domain&gt;</em> will be forwarded to the backend service <em>vaultwarden</em> listening on port 80 <em>vaultwarden:80</em>. </p>




<p>To enable webroot for certbot certificate renewal we must change the  <em>docker.compose.yml</em> file. For the <em>caddy</em> service and in the ports section we must allow port 80 and expose a webroot directory via port 80 only for serving the domain challenge validation file. Therefore we create on the local host directory <em>/opt/vaultwarden/caddy<em>acme</em></em>. In the volumes section of the caddy service we map this local directory 1:1 into the caddy container. Note: the notation with the dot like <em>./caddy<em>acme:/caddy</em>acme</em> requires that the actual <em>Caddyfile</em> is in the same directory <em>/opt/vaultwarden/Caddyfile</em> than <em>/opt/vaultwarden/caddy<em>acme</em></em>. </p>




<pre class="wp-block-code"><code>#docker-compose.yml

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: always
    environment:
      DOMAIN: "https://bitwarden.rottlaender.eu"
      SIGNUPS_ALLOWED: "false"
      SMTP_HOST: "smtp.strato.de"
      SMTP_FROM: "bw@bitwarden.rottlaender.eu"
      SMTP_FROM_NAME: "Vaultwarden"
      SMTP_USERNAME: "bw@bitwarden.rottlaender.eu"
      SMTP_PASSWORD: "ZZqrmQEvydkxY3E2u8Y.KEbKNwgkTV"
      SMTP_SECURITY: "force_tls"
      SMTP_PORT: "465"
      ADMIN_TOKEN: '$$argon2i$$v=19$$m=4096,t=3,p=1$$c29tZXNhbHQ$$D/yu7vPhcpPz8Kk7G/R34YSO+NgtLzai0wVGSGL0RDE'
    volumes:
      - ./vw-data:/data

  caddy:
    image: caddy:2
    container_name: caddy
    restart: always
    ports:
      - 80:80
      - 80:80/udp
      - 443:443
      - 443:443/udp
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy-config:/config
      - ./caddy-data:/data
      - ./certs:/certs:ro
      - ./caddy_acme:/caddy_acme
    environment:
      DOMAIN: "https://bitwarden.rottlaender.eu"
      LOG_FILE: "/data/access.log"
</code></pre>



<p>With this configuration we configure <em>caddy</em> to listen on port 80 and on port 443. In the current ufo firewall configuration we only allow the ports 22 and 443.</p>




<pre class="wp-block-code"><code>#Check the firewall status
sudo ufw status verbose

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere                  
443                        ALLOW IN    Anywhere                  
22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)             
443 (v6)                   ALLOW IN    Anywhere (v6)
</code></pre>



<p>We must open port 80 to enable the certbot webroot certificate renewal.</p>




<pre class="wp-block-code"><code># open port 80
sudo ufw allow 80/tcp

# Check the firewall status
sudo ufw status verbose

Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp (OpenSSH)           ALLOW IN    Anywhere                  
443                        ALLOW IN    Anywhere                  
80/tcp                     ALLOW IN    Anywhere                  
22/tcp (OpenSSH (v6))      ALLOW IN    Anywhere (v6)             
443 (v6)                   ALLOW IN    Anywhere (v6)             
80/tcp (v6)                ALLOW IN    Anywhere (v6)
</code></pre>



<p>Then we must also change the <em>Caddyfile</em>. We must insert a <em>http://..</em> block to enable <em>caddy</em> to serve the ACME challenge.</p>




<pre class="wp-block-code"><code>#Caddyfile

http://bitwarden.rottlaender.eu {
    # Serve ACME Challenge at Port 80
    handle_path /.well-known/acme-challenge/* {
        root * /caddy_acme
        file_server
    }

    # Any other request redirect to HTTPS
    @notACME not path /.well-known/acme-challenge/*
    handle @notACME {
        redir https://bitwarden.rottlaender.eu{uri} permanent
    }
}

https://bitwarden.rottlaender.eu {
  log {
    level INFO
    output file /data/access.log {
      roll_size 10MB
      roll_keep 10
    }
  }

  # Use custom certificate and key
  tls /certs/fullchain.pem /certs/privkey.pem

  # ACME Challenge for Certbot Webroot
  handle_path /.well-known/acme-challenge/* {
    root * /caddy_acme
    file_server
  }

  # This setting may have compatibility issues with some browsers (e.g., attachment downloading on Firefox). Try disabling this if you encounter issues.
  encode zstd gzip

  # Admin path matcher
  @adminPath path /admin*
  
  # Basic Auth for admin access
  handle @adminPath {
    # If admin path require basic auth
    basicauth {
      superadmin $2y$05$HZukVJWhWMrT7qMOIenLkuf2n65bm/6n260TPXKb4Wn825JYlt5tO  # Password Hash
    }

    reverse_proxy vaultwarden:80 {
      header_up X-Real-IP {remote_host}
    }
  }

  # Everything else
  reverse_proxy vaultwarden:80 {
    header_up X-Real-IP {remote_host}
  }
}
</code></pre>



<p>Then we must also change the <em>/etc/letsencrypt/renewal/bitwarden.rottlaender.eu.conf</em> to configure certbot to use webroot instead of standalone. Here change the <em>authenticator</em> directive to <em>webroot</em> and we add the <em>webroot<em>path</em></em>.  Everything else keep the same. </p>




<pre class="wp-block-code"><code># renew_before_expiry = 30 days
version = 1.21.0
archive_dir = /etc/letsencrypt/archive/bitwarden.rottlaender.eu
cert = /etc/letsencrypt/live/bitwarden.rottlaender.eu/cert.pem
privkey = /etc/letsencrypt/live/bitwarden.rottlaender.eu/privkey.pem
chain = /etc/letsencrypt/live/bitwarden.rottlaender.eu/chain.pem
fullchain = /etc/letsencrypt/live/bitwarden.rottlaender.eu/fullchain.pem

# Options used in the renewal process
[renewalparams]
account = 7844ed1ad487ff139e3adaa80aa7bbab
authenticator = webroot
webroot_path = /opt/vaultwarden/caddy_acme
server = https://acme-v02.api.letsencrypt.org/directory
renew_hook = /usr/local/bin/sync-certs.sh

</code></pre>



<p>And finally we must change the entry in the <em>crontab</em>. Before the <em>depoly-hook</em> we change the code to <em> &#8211;webroot -w /opt/vaultwarden/caddy<em>acme</em></em>. This renewal will be executed daily at 03:30h. In case the certificates are still valid (no renewal required) then the <em>deploy-hook</em> will be skipped. Only in case a renewal must be executed the script <em> /usr/local/bin/sync-certs.sh</em> will be executed.</p>




<pre class="wp-block-code"><code>#crontab

30 3 * * * sh -c '/usr/bin/certbot renew --webroot -w /opt/vaultwarden/caddy_acme --deploy-hook /usr/local/bin/sync-certs.sh &gt;&gt; /var/log/certbot-renew.log 2&gt;&amp;1'
</code></pre>



<h4 class="wp-block-heading">Create sync-certs.sh script and root crontab</h4>



<p>This script copies renewed Let&#8217;s Encrypt certificates from the standard location to a custom destination /opt/vaultwarden/certs, sets strict permissions, assigns correct ownership, and restarts the Caddy container to apply the new certificates. At the end it reloads the Caddy container (via docker-compose restart) to apply new certs.</p>




<pre class="wp-block-code"><code>#!/bin/bash

# Variables
DOMAIN="&lt;Domain&gt;"
SRC="/etc/letsencrypt/live/$DOMAIN"
DEST="/opt/vaultwarden/certs"

# Check Source Directory
if [ ! -d "$SRC" ]; then
    echo "Certificate Path $SRC not found"
    exit 1
fi

# Check Destination Directory
if [ ! -d "$DEST" ]; then
    mkdir -p "$DEST"
    chown vaultwarden:vaultwarden "$DEST"
    chmod 700 "$DEST"
    echo "Target Path $DEST created"
fi

# Copy files (overwrite)
cp "$SRC/fullchain.pem" "$DEST/fullchain.pem"
cp "$SRC/privkey.pem" "$DEST/privkey.pem"

# set owner:group vaultwarden
chown vaultwarden:vaultwarden "$DEST/fullchain.pem"
chown vaultwarden:vaultwarden "$DEST/privkey.pem"

# set access (read write only vaultwarden)
chmod 600 "$DEST/privkey.pem"
chmod 600 "$DEST/fullchain.pem"

echo "[sync-certs] Certificates for $DOMAIN synced"

# successful sync of certificates – caddy re-start
echo "[sync-certs] re-start caddy ..."
cd /opt/vaultwarden
/usr/local/bin/docker-compose restart caddy

echo "[sync-certs] caddy reloaded new certificates"
</code></pre>



<p><strong>Check Source Directory</strong></p>




<pre class="wp-block-code"><code>if [ ! -d "$SRC" ]; then
    echo "Certificate Path $SRC not found"
    exit 1
fi
</code></pre>



<ul class="wp-block-list">
	<li>What it checks: Whether the Let&#8217;s Encrypt certificate source directory for the domain exists.</li>
	<li>Why it&#8217;s needed:<br><ul>
			<li>Let’s Encrypt stores certificates as symlinks in /etc/letsencrypt/live/&lt;domain&gt;.</li>
			<li>If this folder doesn&#8217;t exist, the script stops immediately to avoid copying from a missing or invalid source.</li>
		</ul></li>
	<li>Fail-safe: Prevents copying non-existent files, which would cause later commands to fail.<br></li>
</ul>



<p><strong>Check and Create Destination Directory</strong></p>




<pre class="wp-block-code"><code>if [ ! -d "$DEST" ]; then
    mkdir -p "$DEST"
    chown vaultwarden:vaultwarden "$DEST"
    chmod 700 "$DEST"
    echo "Target Path $DEST created"
fi
</code></pre>



<ul class="wp-block-list">
	<li>What it checks: Whether the destination directory for the copied certificates exists.</li>
	<li>If not:<br><ul>
			<li>It creates the directory (mkdir -p ensures parent paths are created if missing).</li>
			<li>Sets secure permissions:<br><ul>
					<li>Owner: vaultwarden</li>
					<li>Permissions: 700 – only the vaultwarden user can access the directory.</li>
				</ul></li>
		</ul></li>
	<li>Why this matters:<br><ul>
			<li>The vaultwarden container or process needs access to the certificates.</li>
			<li>These permissions ensure only vaultwarden can read the certs, improving security.<br></li>
		</ul></li>
</ul>



<h4 class="wp-block-heading">Start, Stop and Check containers</h4>



<p>Here are the most important docker-compose commands. Run these commands when you are in the directory where the docker-compose.yml file is and be sure that the logged in user is in the docker group (otherwise these commands work with sudo).  </p>




<figure class="wp-block-table">
<table>
	<thead>
		<tr>
			<th>
				Command
			</th>
			<th>
				Description
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>docker-compose up -d</code>
			</td>
			<td>
				Start all services defined in <code>docker-compose.yml</code> in detached mode (background).
			</td>
		</tr>
		<tr>
			<td>
				<code>docker-compose down</code>
			</td>
			<td>
				Stop and remove all services and associated networks/volumes (defined in the file).
			</td>
		</tr>
		<tr>
			<td>
				<code>docker-compose restart</code>
			</td>
			<td>
				Restart all services.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker-compose stop</code>
			</td>
			<td>
				Stop all running services (without removing them).
			</td>
		</tr>
		<tr>
			<td>
				<code>docker-compose start</code>
			</td>
			<td>
				Start services that were previously stopped.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker-compose logs</code>
			</td>
			<td>
				Show logs from all services.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker-compose logs -f</code>
			</td>
			<td>
				Tail (follow) logs in real time.
			</td>
		</tr>
	</tbody>
</table>
<figcaption>Docker-compose commands to start, stop and check</figcaption>
</figure>



<p>Here art the most important docker commands to check the status of containers. </p>




<figure class="wp-block-table">
<table>
	<thead>
		<tr>
			<th>
				Command
			</th>
			<th>
				Description
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>docker ps</code>
			</td>
			<td>
				List <strong>running</strong> containers.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker ps -a</code>
			</td>
			<td>
				List <strong>all</strong> containers (running + stopped).
			</td>
		</tr>
		<tr>
			<td>
				<code>docker logs &lt;container-name&gt;</code>
			</td>
			<td>
				Show logs of a specific container.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker logs -f &lt;container-name&gt;</code>
			</td>
			<td>
				Tail logs in real time.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker inspect &lt;container-name&gt;</code>
			</td>
			<td>
				Show detailed info about a container.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker top &lt;container-name&gt;</code>
			</td>
			<td>
				Show running processes inside the container.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker exec -it &lt;container-name&gt; /bin/sh</code>
			</td>
			<td>
				Start a shell session in the container.
			</td>
		</tr>
		<tr>
			<td>
				<code>docker stats</code>
			</td>
			<td>
				Live resource usage (CPU, RAM, etc.) of containers.
			</td>
		</tr>
	</tbody>
</table>
<figcaption>Docker commands to check containers</figcaption>
</figure>



<h4 class="wp-block-heading">Backup the vaultwarden data</h4>



<p>To backup your database you must login to your remote server and navigate to the directory <em>/opt/vaulwarden/vw-data</em>. </p>




<pre class="wp-block-code"><code>cd /opt/vaultwarden/vw-data
ls -l /opt/vaultwarden/vw-data

drwxr-xr-x 2 root root    4096 Mai 14 07:29 attachments
-rw-r--r-- 1 root root 1437696 Mai 29 10:03 db_20250529_080308.sqlite3
-rw-r--r-- 1 root root 1437696 Mai 29 10:04 db_20250529_080448.sqlite3
-rw-r--r-- 1 root root 1445888 Aug  9 08:31 db_20250809_063113.sqlite3
-rw-r--r-- 1 root root 1552384 Aug 10 07:26 db.sqlite3
-rw-r--r-- 1 root root   32768 Sep  3 08:27 db.sqlite3-shm
-rw-r--r-- 1 root root  135992 Sep  3 08:27 db.sqlite3-wal
drwxr-xr-x 2 root root   16384 Sep  3 07:57 icon_cache
-rw-r--r-- 1 root root    1675 Mai 14 07:29 rsa_key.pem
drwxr-xr-x 2 root root    4096 Mai 14 07:29 sends
drwxr-xr-x 2 root root    4096 Mai 14 07:29 tmp

</code></pre>



<p>Before we run the backup here are some background infos.</p>




<p><strong>Note:</strong> Pls. Be aware that you are currently navigating within your <em>remote server</em> not within the container vaultwarden. Under the service <em>vaultwarden</em> and <em>volumes</em> we defined in <em>docker-compose.yml</em> that the directory <em>./vw-data</em> should be mounted into the container under the directory of <em>/data</em>.  This mean that these directories are synchronized and you can check this by navigating within the container as follows.</p>




<p>To navigate within the container <em>vaultwarden</em> you can run the following commands. </p>




<pre class="wp-block-code"><code>#list your containers home directory
docker exec vaultwarden ls -l /

#list the data directory within your container
docker exec vaultwarden ls -l /data
</code></pre>



<p>With <em> docker exec vaultwarden ls -l /</em> you list your container root directory. Here you find among others the file <em>vaultwarden</em> which is basically the main program.  With <em>docker exec vaultwarden ls -l /data</em> you list the files in <em>/data</em> on container side. You see exact the same files than in <em>./vw-data</em> on your remote server.</p>




<p>All data of your vaultwarden service are stored in <em>/data</em> on container side. Here you have the database <em> db.sqlite3</em> and various directories and other files. </p>




<p>To backup these data you run the program <em>vaultwarden</em> with the option <em>backup</em>. </p>




<pre class="wp-block-code"><code>#interactive mode
docker exec -it vaultwarden /vaultwarden backup

#standard mode
docker exec vaultwarden /vaultwarden backup
Backup to 'data/db_20250905_045937.sqlite3' was successful

</code></pre>



<p>The option <em>exec -it</em> is not necessary. It runs the command in interactive mode within the terminal. </p>




<p>This script:</p>




<ol class="wp-block-list">
	<li>Reads the database location and configurations on container side in <em>/data</em>.</li>
	<li>Creates a backup of the SQLite database db.sqlite3, attachments, icons, etc.</li>
	<li>Compresses everything as a .tar.gz archive.</li>
	<li>Stores the archive in the <em>/data</em> directory on container side. <br></li>
</ol>



<p>After executing this command the backup will be also be present in <em>./vw-data</em> on your remote server. </p>




<p>Then you can download the backup from your remote server to your local computer. <strong>Note:</strong> Your local computer is in my case a Mac where I sit in front of and from where I connect to my remote server via <em>ssh</em>.</p>




<p>To download the backup you navigate on your local computer to the directory where you want to store your backup files. Then you run the <em>scp</em> command with the <em>.</em> at the end and download the backup files from your remote server into the current directory which should be your backup directory on your local computer. </p>




<pre class="wp-block-code"><code>#on your local computer
cd /Users/patrick/Software/docker/vaultwarden/backup

scp user@remote-host:/opt/vaultwarden/vw-data/&lt;backupfile&gt; .

</code></pre>



]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>SSL Certificates with Lets Encrypt and certbot on a Linux Server</title>
		<link>https://digitaldocblog.com/webserver/ssl-certificates-with-lets-encrypt-and-certbot-on-a-linux-server/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Fri, 23 May 2025 12:29:11 +0000</pubDate>
				<category><![CDATA[Server]]></category>
		<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=241</guid>

					<description><![CDATA[To install our certificates for SSL we use a tool called certbot. We install certbot with apt on our Linux machine. Then we run certbot. The Letsencrypt certificates are automatically&#8230;]]></description>
										<content:encoded><![CDATA[
<p>To install our certificates for SSL we use a tool called <em>certbot</em>. We install <em>certbot</em> with apt on our Linux machine.</p>




<pre class="wp-block-code"><code>sudo apt update
sudo apt install certbot
</code></pre>



<p>Then we run certbot. </p>




<pre class="wp-block-code"><code>sudo certbot certonly --standalone -d &lt;yourDomain&gt;

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): &lt;yourEmail&gt;

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Account registered.
Requesting a certificate for &lt;yourDomain&gt;

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/&lt;yourDomain&gt;/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/&lt;yourDomain&gt;/privkey.pem
This certificate expires on 2025-07-25.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
</code></pre>



<p>The Letsencrypt certificates are automatically stored by certbot in the directory /etc/letsencrypt. Pls. Check the permissions accordingly. </p>




<pre class="wp-block-code"><code>#700 for letsencrypt and owned by root
sudo ls -l /etc
drwx------ 9 root  root    4096 Mai 22 04:49 letsencrypt

#750 for archive and live and owned by root 
ls -l /etc/letsencrypt
drwx------ 3 root root 4096 Apr 26 10:16 accounts
drwxr-x--- 3 root root 4096 Apr 26 10:18 archive
-rw-r--r-- 1 root root  207 Nov 12  2021 cli.ini
drwx------ 2 root root 4096 Mai 16 07:15 csr
drwx------ 2 root root 4096 Mai 16 07:15 keys
drwxr-x--- 3 root root 4096 Apr 26 10:18 live
drwxr-x--- 2 root root 4096 Mai 16 07:15 renewal
drwxr-xr-x 5 root root 4096 Apr 26 10:16 renewal-hooks

#700 for the directory with the real certificates 
sudo ls -l /etc/letsencrypt/archive
drwx------ 2 root root 4096 Mai 16 07:15 &lt;domain&gt;

#750 for the directory with the symlinks
sudo ls -l /etc/letsencrypt/live
drwxr-x--- 2 root root 4096 Mai 16 07:15 &lt;domain&gt;
</code></pre>



<p><strong>note:</strong> Permissions in Linux are set using the following scheme. </p>




<figure class="wp-block-table">
<table>
	<thead>
		<tr>
			<th>
				Owner
			</th>
			<th>
				Group
			</th>
			<th>
				Others
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				Read (r) = 4 &#8211; Write (w) = 2 &#8211; execute (x) = 1
			</td>
			<td>
				Read (r) = 4 &#8211; Write (w) = 2 &#8211; execute (x) = 1
			</td>
			<td>
				Read (r) = 4 &#8211; Write (w) = 2 &#8211; execute (x) = 1
			</td>
		</tr>
		<tr>
			<td>
				<strong>Example:</strong>
			</td>
			<td>
				
			</td>
			<td>
				
			</td>
		</tr>
		<tr>
			<td>
				7 = r(4)+w(2)+x(1)
			</td>
			<td>
				5 = r(4)+x(1)
			</td>
			<td>
				0
			</td>
		</tr>
	</tbody>
</table>
<figcaption>Permissions in Linux</figcaption>
</figure>



<p>The following permissions are recommended:</p>




<figure class="wp-block-table">
<table>
	<thead>
		<tr>
			<th>
				Directory
			</th>
			<th>
				Permissions
			</th>
			<th>
				Recommendation
			</th>
			<th>
				Comment
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>accounts/</code>
			</td>
			<td>
				<code>drwx------</code>
			</td>
			<td>
				750. Ok
			</td>
			<td>
				Contains Let&#8217;s Encrypt account information – <strong>only root is allowed access</strong>
			</td>
		</tr>
		<tr>
			<td>
				<code>archive/</code>
			</td>
			<td>
				<code>drwxr-x---</code>
			</td>
			<td>
				750 or 700
			</td>
			<td>
				Contains <strong>real keys/certificates</strong> – <strong>do not make publicly readable</strong>
			</td>
		</tr>
		<tr>
			<td>
				<code>cli.ini</code>
			</td>
			<td>
				<code>-rw-r--r--</code>
			</td>
			<td>
				644 or 600 if sensitive content
			</td>
			<td>
				Not critical unless cli.ini contains API keys or email addresses
			</td>
		</tr>
		<tr>
			<td>
				<code>csr/</code>
			</td>
			<td>
				<code>drwx------</code>
			</td>
			<td>
				700. Ok
			</td>
			<td>
				CSR files (may contain sensitive information) – not readable by others
			</td>
		</tr>
		<tr>
			<td>
				<code>keys/</code>
			</td>
			<td>
				<code>drwx------</code>
			</td>
			<td>
				700. Ok
			</td>
			<td>
				Contains private keys &#8211; <strong>only root is allowed access</strong>
			</td>
		</tr>
		<tr>
			<td>
				<code>live/</code>
			</td>
			<td>
				<code>drwxr-x---</code>
			</td>
			<td>
				750. Ok
			</td>
			<td>
				Only root + webserver group should have access to symlinks
			</td>
		</tr>
		<tr>
			<td>
				<code>renewal/</code>
			</td>
			<td>
				<code>drwxr-x---</code>
			</td>
			<td>
				750 or 700
			</td>
			<td>
				Contains configs with paths to key files – do not leave open
			</td>
		</tr>
		<tr>
			<td>
				<code>renewal-hooks/</code>
			</td>
			<td>
				<code>drwxr-xr-x</code>
			</td>
			<td>
				755. Ok
			</td>
			<td>
				Contains mostly harmless scripts – leaving it readable is okay unless you have sensitive hooks
			</td>
		</tr>
	</tbody>
</table>
<figcaption>Lets Encrypt Permissions</figcaption>
</figure>



<p>The keys are set up the following structure: </p>




<ul class="wp-block-list">
	<li>In /etc/letsencrypt/live/&lt;domain&gt; there are only symlinks that point to the real key files</li>
	<li>the real files are stored in /etc/letsencrypt/archive/&lt;domain&gt;<br></li>
</ul>



<p>A symlink itself in /etc/letsencrypt/live/&lt;domain&gt; has no real permissions. It effectively inherits the access rights of the file it points to. However: ls -l shows symbolic permissions which, for symlinks, are usually lrwxrwxrwx. This isn&#8217;t problematic in itself, since access is always controlled by the target files.</p>




<pre class="wp-block-code"><code>sudo ls -l /etc/letsencrypt/live/&lt;domain&gt;
total 4
lrwxrwxrwx 1 root root  48 Mai 16 07:15 cert.pem -&gt; ../../
lrwxrwxrwx 1 root root  49 Mai 16 07:15 chain.pem -&gt; ../../
lrwxrwxrwx 1 root root  53 Mai 16 07:15 fullchain.pem -&gt; ../../
lrwxrwxrwx 1 root root  51 Mai 16 07:15 privkey.pem -&gt; ../../
</code></pre>



<p>Important are the permissions of the actual certificate files in the directory /etc/letsencrypt/archive/&lt;domain&gt;. The permissions should be set as follows.</p>




<figure class="wp-block-table">
<table>
	<thead>
		<tr>
			<th>
				File
			</th>
			<th>
				Required permissions
			</th>
			<th>
				Owner
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>privkey*.pem</code>
			</td>
			<td>
				<code>600</code>
			</td>
			<td>
				<code>root:root</code>
			</td>
		</tr>
		<tr>
			<td>
				<code>cert*.pem</code>
			</td>
			<td>
				<code>644</code>
			</td>
			<td>
				<code>root:root</code>
			</td>
		</tr>
		<tr>
			<td>
				<code>chain*.pem</code>
			</td>
			<td>
				<code>644</code>
			</td>
			<td>
				<code>root:root</code>
			</td>
		</tr>
		<tr>
			<td>
				<code>fullchain*.pem</code>
			</td>
			<td>
				<code>644</code>
			</td>
			<td>
				<code>root:root</code>
			</td>
		</tr>
	</tbody>
</table>
<figcaption>Permissions on the actual certificate and key files</figcaption>
</figure>



<pre class="wp-block-code"><code>sudo ls -l /etc/letsencrypt/archive/&lt;domain&gt;
total 32
-rw-r--r-- 1 root root 1858 Apr 26 10:18 cert1.pem
-rw-r--r-- 1 root root 1801 Apr 26 10:18 chain1.pem
-rw-r--r-- 1 root root 3659 Apr 26 10:18 fullchain1.pem
-rw------- 1 root root 1704 Apr 26 10:18 privkey1.pem
</code></pre>



<p>You should pay special attention to some other files.</p>




<p><strong>/etc/letsencrypt/accounts/acme-&lt;v02.api&gt;/directory/&lt;accountID&gt;</strong></p>




<p>The files meta.json, regr.json and private_key.json in the accounts directory should be owned by root and only root should have access. These files contain your Let&#8217;s Encrypt account credentials, especially the private account key, and are therefore highly sensitive.</p>




<figure class="wp-block-table">
<table>
	<thead>
		<tr>
			<th>
				Files
			</th>
			<th>
				Recommendation
			</th>
			<th>
				Comment
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				<code>private_key.json</code>
			</td>
			<td>
				400
			</td>
			<td>
				contains private account key
			</td>
		</tr>
		<tr>
			<td>
				<code>meta.json</code>, <code>regr.json</code>
			</td>
			<td>
				600
			</td>
			<td>
				contains metadata, optionally sensitive information (email, ID)
			</td>
		</tr>
	</tbody>
</table>
<figcaption>Restricted Permissions for files in the account directory</figcaption>
</figure>



<pre class="wp-block-code"><code>sudo ls -l /etc/letsencrypt/accounts/acme-&lt;v02.api&gt;/directory/&lt;accountID&gt;
total 12
-rw------- 1 root root   85 Apr 26 10:18 meta.json
-r-------- 1 root root 1632 Apr 26 10:17 private_key.json
-rw------- 1 root root   80 Apr 26 10:17 regr.json
</code></pre>



<p><strong>private_key.json:</strong> Contains your ACME account key – this is like a password for your certificate management.</p>




<ul class="wp-block-list">
	<li>Read-only: The file must be readable by root so that Let&#8217;s Encrypt (or other tools like certbot) can communicate with this key to renew or issue certificates.</li>
	<li>Write-protected: Making the file unreadable even by root prevents it from being modified accidentally or through an unsafe operation. The private key should not be changed, as this could prevent access to your Let&#8217;s Encrypt account.</li>
</ul>



<p><strong>meta.json:</strong> Contains information such as the registered email address.</p>




<p><strong>regr.json:</strong> Let&#8217;s Encrypt registration details.</p>




<p>A leak of these files would allow attackers to:</p>




<ul class="wp-block-list">
	<li>Hijack your Let&#8217;s Encrypt account</li>
	<li>Issue certificates for any domain (if DNS access is compromised)</li>
	<li>Tamper or delete existing certificates<br></li>
</ul>



<p><strong> /etc/letsencrypt/csr and /etc/letsencrypt/keys directory</strong></p>




<p>The files in the /etc/letsencrypt/csr and /etc/letsencrypt/keys directories should have the following permissions to ensure security.</p>




<figure class="wp-block-table">
<table>
	<thead>
		<tr>
			<th>
				File Type
			</th>
			<th>
				Path
			</th>
			<th>
				Permission
			</th>
			<th>
				Why
			</th>
		</tr>
	</thead>
	<tbody>
		<tr>
			<td>
				Private Key
			</td>
			<td>
				/etc/letsencrypt/keys/<em>.pem</em>
			</td>
			<td>
				 Must be 600
			</td>
			<td>
				Contains the <strong>private key</strong> for the certificate
			</td>
		</tr>
		<tr>
			<td>
				CSR (Certificate Signing Request)
			</td>
			<td>
				/etc/letsencrypt/csr/<em>.pem</em>
			</td>
			<td>
				Recommended 600 (alternatively 644 less secure)
			</td>
			<td>
				Contains <strong>no private key</strong>, just public key + metadata
			</td>
		</tr>
	</tbody>
</table>
<figcaption>Permissions for csr and key files </figcaption>
</figure>



<p>Directory <strong>/etc/letsencrypt/csr</strong> stores Certificate Signing Requests (CSRs) generated by Certbot during certificate issuance or renewal. These are not used after issuance — they are kept for reference or debugging, not for active use.</p>




<pre class="wp-block-code"><code>sudo ls -l /etc/letsencrypt/csr
total 8
-rw------- 1 root root 936 Apr 26 10:18 0000_csr-certbot.pem
-rw-r--r-- 1 root root 936 Mai 16 07:15 0001_csr-certbot.pem
</code></pre>



<p>CSR files contain requests for certificate issuance and may contain sensitive information, such as the public key associated with a private key. Although the CSR file itself does not contain the private key, it can still pose informational risks because it represents an association with a private key. </p>




<p><strong>Security measure:</strong> Access should be restricted to root to prevent an attacker from tampering with or accessing the CSR files. </p>




<p><strong>Issue:</strong>After a renewal of the certificates on Mai 16 you can see above that the permissions to the file 0001_csr-certbot.pem has been set by certbot to 644. You can correct this with the following command. </p>




<pre class="wp-block-code"><code>sudo find /etc/letsencrypt/csr -name '*.pem' -exec chmod 600 {} +
</code></pre>



<pre class="wp-block-code"><code>sudo ls -l /etc/letsencrypt/csr
total 8
-rw------- 1 root root 936 Apr 26 10:18 0000_csr-certbot.pem
-rw------- 1 root root 936 Mai 16 07:15 0001_csr-certbot.pem
</code></pre>



<p>Directory <strong>/etc/letsencrypt/keys</strong> holds actual private keys used to sign CSRs and serve HTTPS. They must never be world-readable.</p>




<pre class="wp-block-code"><code>sudo ls -l /etc/letsencrypt/keys
total 8
-rw------- 1 root root 1704 Apr 26 10:18 0000_key-certbot.pem
-rw------- 1 root root 1704 Mai 16 07:15 0001_key-certbot.pem
</code></pre>



<p>Private keys are critical for security because they are used for authentication during certificate requests and for encrypting and decrypting data. The private key should be readable and writable only by root to prevent unauthorized access. Important: Any user with access to these key files could perform encryption operations or misuse certificates.</p>




<p><strong> /etc/letsencrypt/renewal/&lt;yourDomain&gt;.conf </strong></p>




<p>The file /etc/letsencrypt/renewal/&lt;yourDomain&gt;.conf contains the configuration for automatic certificate renewal and can contain sensitive data, such as the paths to private keys and certificates. Therefore, it is important to secure this file accordingly. The file should only be readable and writable by root, as it contains potentially sensitive information about the certificate configuration and the paths to private keys.</p>




<pre class="wp-block-code"><code>sudo ls -l /etc/letsencrypt/renewal
total 4
-rw------- 1 root root 606 Mai 16 07:15 &lt;yourDomain&gt;.conf
</code></pre>



]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Containerize a nodejs app and nginx with docker on Ubuntu 22.04</title>
		<link>https://digitaldocblog.com/webserver/containerize-a-nodejs-app-and-nginx-with-docker-on-ubuntu-2204/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Sat, 03 May 2025 15:56:04 +0000</pubDate>
				<category><![CDATA[Server]]></category>
		<category><![CDATA[Web-Development]]></category>
		<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[NginX]]></category>
		<category><![CDATA[Node.js]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=232</guid>

					<description><![CDATA[What we need: We must check if these packages are already installed on our system. Therefore we use the following commands in the terminal and see if there is any&#8230;]]></description>
										<content:encoded><![CDATA[
<p>What we need:</p>



<ol class="wp-block-list">
<li><strong>docker:</strong> Docker is a tool for building, running, and managing containers. A container is a lightweight, isolated environment that packages an application and all its dependencies (Docker application).</li>



<li><strong>docker-compose:</strong> Docker Compose is a tool for defining and running multi-container Docker applications using a single file called docker-compose.yml. </li>



<li><strong>node.js:</strong> Node.js is a JavaScript runtime that lets you run JavaScript outside the browser, typically on the server. It’s built on Chrome’s V8 engine, and it’s great for building fast, scalable network applications, like APIs or web servers.</li>



<li><strong>npm:</strong> npm stands for Node Package Manager. It&#8217;s a tool that comes with Node.js and is used to Install packages (libraries, tools, frameworks), manage project dependencies and share your own packages with others.</li>



<li><strong>curl:</strong> curl is a command-line tool used to send requests to URLs. It lets you interact with APIs or download content from the internet right from your terminal.</li>



<li><strong>gnupg:</strong> GnuPG (or GPG, short for Gnu Privacy Guard) is a tool for encryption and signing data and communications. It uses public-key cryptography to encrypt, decrypt, sign, and verify files or messages.</li>



<li> <strong>ca-certificates:</strong> A collection of trusted root certificates used to validate HTTPS connections.<br></li>
</ol>



<p>We must check if these packages are already installed on our system. Therefore we use the following commands in the terminal and see if there is any output. If there is no output the packages are not installed and we continue with the installation as described below. </p>



<pre class="wp-block-code"><code># check the docker components
docker --version
docker-compose --version

#check the node and npm components
node --version
npm --version

#check if required dependencies are already installed
curl --version
gpg --version
</code></pre>



<p>In case some of these packages are already installed on your system you need to reduce the installation scope of the packages accordingly. </p>



<p>We expect to have no output after typing the above commands and go through the installation process step-by-step. </p>



<p><strong>Step 1:</strong> Install node.js and npm from the standard Ubuntu resources. </p>



<p><strong>Step 2:</strong> </p>



<ul class="wp-block-list">
<li>Prepare the system for secure downloads from Docker resources and </li>



<li>install ca-certificates, curl and gnupg from standard Ubuntu resources</li>
</ul>



<p><strong>Step 3:</strong> Install Docker from Docker resources.</p>



<p><strong>Step 4:</strong> Install docker-compose standalone from Docker resources. </p>



<h3 class="wp-block-heading">Install Node.js and npm from Ubuntu Resources</h3>



<p>Before we start with the installation we update and upgrade all packages. Node.js is available in Ubuntu’s repositories, so you can install it with the following commands.</p>



<pre class="wp-block-code"><code>sudo apt update
sudo apt upgrade

sudo apt install -y nodejs npm
</code></pre>



<p>Verify the installation:</p>



<pre class="wp-block-code"><code>node -v
npm -v
</code></pre>



<h3 class="wp-block-heading">Prepare the System for secure Downloads from Docker</h3>



<p>To prepare our system we ensure that <em>ca-certificates, curl and gnupg</em> are available on our system.</p>



<p>To be able to install the Docker packages from the <em>external</em> or <em>not Ubuntu</em> Docker repository  <em>apt</em> must know where these resources are otherwise <em>apt</em> would install the packages from the Ubuntu repositories which would be the standard but not what we want. Therefore we must add the Docker repository to the <em>apt</em> tool. The complete process can be followed on the <a href="https://docs.docker.com/engine/install/ubuntu/" title="Install Docker Ubuntu">Docker Manual Pages</a>. </p>



<p>When we add the Docker repository, the packages from that repository are digitally signed to ensure that these packages really come from docker. <em>gnupg</em> is a tool that allows our system to check these signatures against a trusted Docker GPG key. Therefore <em>gnupg</em> must be available on our system. </p>



<p>To make sure that the GPG key is available we must download the key from the Docker site. For the download we use the <em>curl</em> tool. Therefore <em>curl</em> must be available on our system.</p>



<p>We access the Docker site via HTTPS. Here <em>ca-certificates</em> comes into play. <em>ca-certificates</em> is a collection of trusted root certificates used to validate HTTPS connections. When downloading the Docker GPG key or accessing the Docker <em>apt</em> repository via HTTPS, Ubuntu checks the site’s SSL certificate against the collection of trusted root certificates. Therefore <em> ca-certificates</em> must be available on our system.</p>



<p>To check if <em> ca-certificates</em> is already installed  we run the following command:</p>



<pre class="wp-block-code"><code>dpkg -l | grep ca-certificates
</code></pre>



<p><strong>Note:</strong> The <em>dpkg</em> command stands for Debian Package Manager and is used to manage <em>.deb</em> packages on Debian-based systems like Ubuntu. It works at a lower level than <em>apt</em>, which is a higher-level package management tool that uses <em>dpkg</em> under the hood.</p>



<p>If it’s installed, you’ll see output like this:</p>



<pre class="wp-block-code"><code>ii ca-certificates 20230311ubuntu0.22.04.1 all Common CA..
</code></pre>



<p>In this case you do not need to install  <em>ca-certificates</em>  as describes below  but it is highly recommended to update the collection of trusted root certificates before you continue.</p>



<pre class="wp-block-code"><code>sudo update-ca-certificates
</code></pre>



<p>We assume that we must install <em>ca-certificates, curl and gnupg</em>. First, we update the system package list to ensure everything is up-to-date on our system. This checks the installed packages for updates and upgrade the packages if required in one process. Then we install ca-certificates, curl and gnupg.</p>



<pre class="wp-block-code"><code>sudo apt update &amp;&amp; sudo apt upgrade -y

sudo apt install -y ca-certificates curl gnupg
</code></pre>



<p><strong>Add Docker GPG key:</strong></p>



<p>To install the GPG keys from Docker we run the following command.</p>



<pre class="wp-block-code"><code>sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.asc &gt; /dev/null
sudo chmod a+r /etc/apt/keyrings/docker.asc
</code></pre>



<p>Let&#8217;s break down the full set of commands step by step:</p>



<p><strong>sudo install -m 0755 -d /etc/apt/keyrings ….</strong></p>



<ul class="wp-block-list">
<li><em>sudo</em> runs the command with superuser (root) privileges.  </li>



<li><em>install</em> is used for copying files and setting permissions.</li>
</ul>



<p><strong>note:</strong> This command is only <code>sudo install</code> and not <code>sudo apt install</code> . If we use <em>apt install</em> then this installs software packages from Ubuntu&#8217;s package repositories. Example: <code>sudo apt install docker-ce</code>. It&#8217;s used to install applications.</p>



<p>Only <em>install</em> is a Unix command (part of coreutils) used to create directories, copy files, and set permissions in a single step. Example: <code>sudo install -m 0755 -d /etc/apt/keyrings</code>. It&#8217;s used to prepare the system, not install software. So this command creates the <em>/etc/apt/keyrings</em> folder with secure permissions, which is later used to store GPG keyring files (such as Docker’s signing key).</p>



<ul class="wp-block-list">
<li>-m 0755 sets file permissions:<br>
<ul class="wp-block-list">
<li><em>0755</em> means:</li>



<li>1st (0) is just a numerical representation</li>



<li>2nd (7) is for the Owner (root) having <em>1 x read (4) + 1 x write (2) + 1 x execute (1) = 7</em> permissions.</li>



<li>3rd (5) is for the Group (root) having <em>1 x read (4) + 0 x write (2) + 1 x execute (1) = 5</em> permissions (no write).</li>



<li>4th (5) is for the Others having <em>1 x read (4) + 0 x write (2) + 1 x execute (1) = 5</em> permissions (no write).</li>
</ul>
</li>



<li>-d tells install to create a directory</li>



<li><em>/etc/apt/keyrings</em> is the target directory where the Docker GPG key will be stored.</li>
</ul>



<p>What it does?</p>



<ul class="wp-block-list">
<li>Ensures that the /etc/apt/keyrings directory exists.</li>



<li>Sets the correct permissions (readable but not writable by non-root users).</li>



<li>This is a <em>security best practice</em> to keep GPG keys safe from tampering.<br></li>
</ul>



<p><strong> curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo tee /etc/apt/keyrings/docker.asc &gt; /dev/null …</strong></p>



<ul class="wp-block-list">
<li><em>curl</em> a command-line tool to fetch files from a URL (we have installed it before)</li>



<li>-fsSL flags to control <em>curl</em> behavior:<br>
<ul class="wp-block-list">
<li>-f (fail silently on server http errors like 404 &#8211; site or resource not found).</li>



<li>-s (silent mode, no progress output).</li>



<li>-S (shows error messages if -s is used).</li>



<li>-L (follows redirects if the URL points elsewhere).</li>
</ul>
</li>



<li><em>https://download.docker.com/linux/ubuntu/gpg</em> the URL for Docker’s GPG key file (the file name on the docker site is <em>gpg</em>).</li>



<li>| (pipe) passes the downloaded data (the <em>gpg</em> file) to another command. In this case the data will be passed to the following <em>sudo</em> command.</li>



<li><em>sudo tee /etc/apt/keyrings/docker.asc</em> writes the key to before created directory <em>/etc/apt/keyrings/docker.asc</em>:<br>
<ul class="wp-block-list">
<li><em>tee</em> writes the output to a file (here it is <em>docker.asc</em>) while also displaying it in the terminal.</li>



<li><em>sudo</em> ensures that the file is written with root permissions.</li>
</ul>
</li>



<li>> /dev/null redirects standard output to <em>/dev/null</em> to suppress unnecessary output. The <em>tee</em> command can also display and write at the same time, unless you silence it with > /dev/null.<br></li>
</ul>



<p><strong>note:</strong> <em>sudo tee…</em> runs with root permissions, so the file can be written even to protected directories such as <em>/etc/apt/keyrings/</em> (we have set the permission to 0755. See above). You can also run the <em>curl</em> command with root permissions (<em>sudo curl …</em>) and then directly pass the output into a file with the <em>-o</em> option: <em>sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc</em>. This is suggested on the <a href="https://docs.docker.com/engine/install/ubuntu/" title="Bocker on Ubuntu">Docker Manaual Page</a> but you can do it in both ways.</p>



<p>What it does?</p>



<ul class="wp-block-list">
<li>Downloads Docker&#8217;s official GPG key.</li>



<li>Saves it securely in /etc/apt/keyrings/docker.asc.</li>



<li>Ensures the key isn’t printed to the terminal.<br></li>
</ul>



<p><strong> sudo chmod a+r /etc/apt/keyrings/docker.asc</strong></p>



<ul class="wp-block-list">
<li><em>sudo</em> runs the command (in this case the <em>chmod</em> command) as root.</li>



<li><em>chmod</em> modifies file permissions.<br>
<ul class="wp-block-list">
<li><em>a+r</em> grants <em>read (r) permission</em> to <em>all users (a)</em>.</li>
</ul>
</li>



<li><em>/etc/apt/keyrings/docker.asc</em> the file whose permissions are being modified.</li>
</ul>



<p>What it does?</p>



<ul class="wp-block-list">
<li>Ensures that all users (including apt processes) can read the GPG key.</li>



<li>This is necessary so that <em>apt</em> can verify Docker package signatures when installing updates.<br></li>
</ul>



<p>Previously, GPG files were stored in <em>/etc/apt/trusted.gpg</em>. This has changed.</p>



<p>Why Is This Necessary?</p>



<ol class="wp-block-list">
<li>Security:  </li>
</ol>



<ul class="wp-block-list">
<li>Storing GPG keys in <em>/etc/apt/keyrings/</em> instead of <em>/etc/apt/trusted.gpg</em> is a best practice.</li>



<li>Prevents malicious modifications to package signatures.</li>
</ul>



<ol class="wp-block-list">
<li>Package Verification:</li>
</ol>



<ul class="wp-block-list">
<li>The GPG key allows Ubuntu’s package manager (apt) to verify that Docker packages are genuine and not tampered with.</li>
</ul>



<ol class="wp-block-list">
<li>Future-proofing:</li>
</ol>



<ul class="wp-block-list">
<li>Newer versions of Ubuntu prefer keys in <em>/etc/apt/keyrings/</em> instead of the older <em>/etc/apt/trusted.gpg</em>.<br></li>
</ul>



<p>Final Summary:</p>



<figure class="wp-block-table"><table><thead><tr><th>
				Command
			</th><th>
				Purpose
			</th></tr></thead><tbody><tr><td>
				sudo install -m 0755 -d /etc/apt/keyring
			</td><td>
				Creates a secure directory for storing package keys.
			</td></tr><tr><td>
				curl -fsSL &#8230; | sudo tee /etc/apt/keyrings/docker.asc &gt; /dev/null
			</td><td>
				Downloads and saves Docker&#8217;s GPG key.
			</td></tr><tr><td>
				sudo chmod a+r /etc/apt/keyrings/docker.asc
			</td><td>
				Ensures the key can be read by apt.
			</td></tr></tbody></table><figcaption class="wp-element-caption">Add Docker GPG key</figcaption></figure>



<p><strong>Add the Docker repository:</strong></p>



<p>To install the docker resources to the <em>apt</em> sources list we run the following command. </p>



<pre class="wp-block-code"><code>echo "deb &#91;arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list &gt; /dev/null
</code></pre>



<p>Let&#8217;s break down the command step by step:</p>



<p><strong> echo &#8222;deb….“</strong></p>



<p>The <em>echo</em> command output the text between the <em>“….“</em>. This is the APT repository entry for Docker. Let&#8217;s analyze its components:</p>



<ul class="wp-block-list">
<li>deb → Indicates that this is a Debian-based software repository.</li>



<li>arch=$(dpkg &#8211;print-architecture):<br>
<ul class="wp-block-list">
<li><em>dpkg &#8211;print-architecture</em> dynamically retrieves the system architecture from your system (e.g., amd64, arm64).</li>



<li>This ensures that the correct package version for your system&#8217;s architecture is used.</li>
</ul>
</li>



<li><em>signed-by=/etc/apt/keyrings/docker.asc</em> specifies the location of the docker GPG key <em>docker.asc</em> (we installed it before), which is used to verify the authenticity of packages downloaded from the repository.</li>



<li><em>https://download.docker.com/linux/ubuntu</em> the URL of Docker’s official repository.</li>



<li><em>$(lsb_release -cs)</em> dynamically fetches the codename of the Ubuntu version (e.g., jammy for Ubuntu 22.04).<br>
<ul class="wp-block-list">
<li>This ensures that the correct repository for the current Ubuntu version is used.</li>
</ul>
</li>



<li><em>stable</em> specifies that we are using the stable release channel of Docker.</li>
</ul>



<p><strong> | sudo tee /etc/apt/sources.list.d/docker.list</strong></p>



<ul class="wp-block-list">
<li>The | (pipe) takes the output of echo and passes it to the <em>tee</em> command.</li>



<li><em>sudo tee /etc/apt/sources.list.d/docker.list</em> does the following:<br>
<ul class="wp-block-list">
<li>tee writes the output to a file (<em>/etc/apt/sources.list.d/docker.list</em>).</li>



<li>sudo is required because writing to <em>/etc/apt/sources.list.d/</em> requires root privileges.</li>
</ul>
</li>
</ul>



<p><strong>&gt; /dev/null</strong></p>



<ul class="wp-block-list">
<li>The <em>> /dev/null</em> part discards the standard output of the tee command.<br>
<ul class="wp-block-list">
<li>This prevents unnecessary output from being displayed in the terminal.</li>



<li>Without this, tee would both write to the file and display the text on the screen.<br></li>
</ul>
</li>
</ul>



<h3 class="wp-block-heading">Install Docker from Docker Resources</h3>



<p>Now, update the package list again and install Docker.</p>



<pre class="wp-block-code"><code>sudo apt update

sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin
</code></pre>



<p>This command installs the following Docker key components using the <em>apt</em> package manager on Ubuntu (the <em>-y</em> option automatically answers &#8222;yes&#8220; to all prompts, so the install runs without asking for confirmation).</p>



<p><strong>docker-ce</strong></p>



<ul class="wp-block-list">
<li>Docker Community Edition</li>



<li>This is the core Docker Engine, the daemon that runs containers.</li>



<li>Installs the Docker server that manages images, containers, volumes, and networks.</li>
</ul>



<p><strong>docker-ce-cli</strong></p>



<ul class="wp-block-list">
<li>Docker Command-Line Interface</li>



<li>This is the docker command you use in your terminal (e.g., docker run, docker ps, etc.).</li>



<li>Separates the CLI from the engine so they can be updated independently.</li>
</ul>



<p><strong>containerd.io</strong></p>



<ul class="wp-block-list">
<li>Container runtime</li>



<li>A lightweight, powerful runtime for containers, used internally by Docker.</li>



<li>Handles the actual low-level execution of containers.</li>
</ul>



<p><strong>docker-buildx-plugin</strong></p>



<ul class="wp-block-list">
<li>BuildKit-powered Docker build plugin</li>



<li>Adds docker buildx functionality for advanced builds, multi-arch images, and caching strategies.</li>



<li>Useful when building complex container images.<br></li>
</ul>



<p><strong>Note:</strong> In some documentation you will see that the <em>sudo apt install…</em>  command will include also the <em>docker-compose-plugin</em>. The docker-compose-plugin is not required here because we are using the docker-compose stand alone packed (see below). The docker-compose-plugin is integrated into the Docker CLI and can replace the docker-compose standalone binary. But we use the standalone version because of the lightweight minimal install, the backward compatibility and the easy and independent manual version control. </p>



<p>It is highly recommended to omit the <em>docker-compose-plugin</em> from your apt install command if you plan to install the standalone version of Docker Compose binary manually as we will do later. If you have both versions installed this can cause confusion, especially if scripts assume one or the other. Also, Docker might prioritize the plugin version in newer setups which might cause conflicts in our preferred standalone Docker Compose setup. The following table illustrates the problem because the command styles differ only very little. </p>



<figure class="wp-block-table"><table><thead><tr><th>
				Type
			</th><th>
				Command Style
			</th><th>
				Notes
			</th></tr></thead><tbody><tr><td>
				Plugin version
			</td><td>
				<code>docker compose</code>
			</td><td>
				Comes as <code>docker-compose-plugin</code>, tied to Docker CLI
			</td></tr><tr><td>
				Standalone version
			</td><td>
				<code>docker-compose</code>
			</td><td>
				Installed separately, as an independent binary
			</td></tr></tbody></table><figcaption class="wp-element-caption">Docker Compose plugin versus standalone</figcaption></figure>



<p>In case the docker-compose-plugin has been installed on your system you can remove it with the following command:</p>



<pre class="wp-block-code"><code>sudo apt remove docker-compose-plugin
</code></pre>



<p>This removes the plugin version that integrates into the <em>docker compose</em> command. Later when we installed the standalone version of docker compose we use the commands instead of the <em>space</em> with a <em>dash</em> like <em>docker-compose</em>. </p>



<p>Verify that Docker is installed correctly:</p>



<pre class="wp-block-code"><code>sudo docker --version
</code></pre>



<p>Enable and start the Docker service:</p>



<pre class="wp-block-code"><code>sudo systemctl enable docker
sudo systemctl start docker
</code></pre>



<p>Test Docker by running the hello-world image.</p>



<pre class="wp-block-code"><code>sudo docker run hello-world
</code></pre>



<p>This command is a quick test to verify that Docker is installed and working correctly. </p>



<p><strong> sudo</strong></p>



<ul class="wp-block-list">
<li>Runs the command with superuser (root) privileges.</li>



<li>Required unless your user is in the docker group.</li>



<li>Docker needs elevated permissions to communicate with the Docker daemon (which runs as root).</li>
</ul>



<p><strong>docker</strong></p>



<ul class="wp-block-list">
<li>The main Docker CLI (Command-Line Interface) tool.</li>



<li>Used to interact with Docker Engine to manage containers, images, networks, volumes, etc.</li>
</ul>



<p><strong>run</strong></p>



<ul class="wp-block-list">
<li>Tells Docker to create a new container and start it based on the <em> hello-world</em> image you specify.<br>It does the following:<br>
<ul class="wp-block-list">
<li>Pulls the image (if it&#8217;s not already downloaded).</li>



<li>Creates a new container from that image.</li>



<li>Starts and runs the container.</li>



<li>Outputs the result and then exits (for short-lived containers like hello-world).</li>
</ul>
</li>
</ul>



<p><strong> hello-world</strong></p>



<ul class="wp-block-list">
<li>This is the name of the Docker image.</li>



<li>It&#8217;s an official image maintained by Docker, specifically designed to test Docker installations.<br></li>
</ul>



<h3 class="wp-block-heading">Install standalone docker-compose from Docker Resources</h3>



<p>Installing the standalone <em>docker-compose</em> is useful when you:</p>



<ul class="wp-block-list">
<li>Need compatibility with legacy tools or scripts</li>



<li>Want to control the exact version</li>



<li>Prefer a lightweight, portable binary<br></li>
</ul>



<p>The following command downloads the latest standalone <em>docker-compose</em> binary and saves it to a system-wide location.</p>



<pre class="wp-block-code"><code>sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
</code></pre>



<p> Let&#8217;s break down the command step by step:</p>



<p><strong>sudo</strong></p>



<ul class="wp-block-list">
<li>Runs the command with root privileges.<br>
<ul class="wp-block-list">
<li>Required because /usr/local/bin is a protected directory that only root can write to.</li>
</ul>
</li>
</ul>



<p><strong>curl</strong></p>



<ul class="wp-block-list">
<li>A command-line tool used to download files from the internet.</li>
</ul>



<p><strong>-L</strong></p>



<ul class="wp-block-list">
<li>Tells curl to follow redirects.</li>



<li>GitHub uses redirects for release URLs, so this flag ensures the final binary is actually downloaded.</li>
</ul>



<p><strong> &#8222;https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)“</strong></p>



<p>URL with Substitution. This ensures the correct binary is downloaded for your system. This is the dynamic download URL for the latest <em>docker-compose</em> release.</p>



<ul class="wp-block-list">
<li><em>$(uname -s)</em> returns the operating system name (e.g., Linux, Darwin).</li>



<li><em>$(uname -m)</em> returns the architecture (e.g., x86<em>64, arm64).</em></li>
</ul>



<p>Example Output: <em> https://github.com/docker/compose/releases/latest/download/docker-compose-Linux-x86<em>64</em></em></p>



<p><strong> -o /usr/local/bin/docker-compose </strong></p>



<ul class="wp-block-list">
<li>Tells curl to write (<em>-o</em> option; output) the downloaded file to <em>/usr/local/bin/docker-compose</em></li>



<li>This is a standard location for user-installed binaries that are globally available in the system PATH.<br></li>
</ul>



<p>After you run the command above you must do this. Give execution permissions. You’ll need to make the binary executable:</p>



<pre class="wp-block-code"><code>sudo chmod +x /usr/local/bin/docker-compose
</code></pre>



<p>And then check the version to confirm it worked:</p>



<pre class="wp-block-code"><code>docker-compose --version
</code></pre>



<p><strong>Note:</strong> In some cases it might be necessary to switch to the <em>docker compose plugin</em>. This mean you must remove the standalone version from your system and install the <em>docker compose plugin</em> instead. Here is how you should proceed in such a scenario:</p>



<pre class="wp-block-code"><code>#find our where docker-compose has been installed
which docker-compose
/usr/bin/docker-compose

#remove file docker-compose 
sudo rm /usr/bin/docker-compose

#intstall docker compose plugin via docker
sudo apt install docker-compose-plugin
</code></pre>



<h3 class="wp-block-heading">Final Verification</h3>



<p>Check if everything is installed correctly:</p>



<pre class="wp-block-code"><code>docker --version
docker-compose --version
node -v
npm -v
</code></pre>



<h3 class="wp-block-heading">Host-Docker-Setup for nodejs app behind nginx</h3>



<p>The code of the nodejs app is explained in detail in the article <em><a href="https://digitaldocblog.com/webdesign/sample-bootstrap-website-running-as-nodes-app/" title="Sample Bootstrap Website running as nodejs app">Bootstrap Website running as nodejs app</a></em>. </p>



<p>We have a simple website <em>funtrails</em> build in a one-page <em>index.html</em> with two sections: one section showing pictures of a Paterlini bike and one for showing pictures of a Gianni Motta bike. Each section containing an image gallery and text. The images are stored in an <em>images</em> directory. </p>



<pre class="wp-block-code"><code>  ├── .
	├── images
	├── index.html
</code></pre>



<p>Now we want to make this <em>funtrails</em> website run as a nodejs app behind a nginx reverse proxy. Both, the <em>funtrails</em> nodejs app and <em>nginx</em> should run in docker containers composed to work together. Therefore we crate the following file structure on the Host machine:</p>



<pre class="wp-block-code"><code> ├── node
	├── funtrails
        │    ├─ views    
        │       ├── images
	│	├── index.html
	├── nginx
</code></pre>



<p>We copy all our web-content into the <em>views</em> directory under <em>funtrails</em>. The nodejs app is build in the mainfile <em>app.js</em>. All Dependencies for the nodejs app are defined the file <em> package.json</em>.  </p>



<pre class="wp-block-code"><code> ├── node
	├── funtrails
        │    ├─ app.js
    	│    ├─ package.json
        │    ├─ views    
        │       ├── images
	│	├── index.html
	├── nginx
</code></pre>



<p>In the <em>Terminal</em> go into the <em>node/funtrails</em> directory. Install the dependencies.</p>



<pre class="wp-block-code"><code>npm install
</code></pre>



<p>Then we have the following structure.</p>



<pre class="wp-block-code"><code>funtrails
├── app.js
├── node_modules
├── package.json
├── package-lock.json
└── views
    ├── images
    └── index.html
</code></pre>



<p>Go to <em>node/funtrails</em>. Run a test with the following command. </p>



<pre class="wp-block-code"><code>node app.js

nodejs funtrails demo app listening on port 8080!
</code></pre>



<p>Switch to your browser and hit <em>http://localhost:8080</em> to see if all is working as expected. With <em>Ctrl c</em> in the terminal you can stop the app.</p>



<p>The <em>nginx</em> server will be started with the configuration of a <em>nginx.conf</em> file. The file <em>nginx.conf</em> will be created under <em>nginx</em>.</p>



<p><strong>Note:</strong> This file <em>nginx.conf</em> is only for testing and will be changed when we add the SSL/TLS certificates. In this configuration below the <em>nginx</em> server will listen on port 80 and pass requests received from port 80 to the <em>nodejs</em> app listening on port 8080. Processing requests from port 80 is not state of the art as these connections are not encrypted. In production we need a port 443 connection with SSL/TLS for encrypted connections.  </p>



<pre class="wp-block-code"><code>#nginx.conf

events {}

http {
	#Service node-app from docker-compose.yml
    upstream node_app {
        server node-app:8080;  
    }

    server {
        listen 80;

        location / {
            proxy_pass http://node_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}
</code></pre>



<p><strong>events {}</strong></p>



<ul class="wp-block-list">
<li>This is a required block in NGINX configuration, even if empty.</li>



<li>It handles connection-related events (like concurrent connections), but you don&#8217;t need to configure it unless you have advanced use cases.</li>
</ul>



<p><strong>http {</strong></p>



<ul class="wp-block-list">
<li>Starts the HTTP configuration block — this is where you define web servers, upstreams, headers, etc.</li>
</ul>



<p><strong>upstream</strong></p>



<ul class="wp-block-list">
<li>Defines a group of backend servers (can be one or many).</li>



<li><em>node_app</em> is just a name.</li>



<li>Inside: server <em>node-app:8080;</em> means:<br>
<ul class="wp-block-list">
<li>Forward traffic to the container with hostname <em>node-app</em></li>



<li>Use port 8080 (that&#8217;s where your <em>nodejs</em> app listens)</li>



<li><em>node-app</em> should match the docker-compose service name (<em>node-app</em> must be declared in your docker-compose.yml which will be explained below).</li>



<li>This lets you use <em>proxy_pass http://node<em>app</em></em> later, instead of hardcoding an IP or port.</li>
</ul>
</li>
</ul>



<p><strong>server</strong></p>



<ul class="wp-block-list">
<li>Defines a virtual server (a website or domain).</li>



<li>listen 80; tells NGINX to listen for HTTP (port 80) traffic.</li>
</ul>



<p><strong>location</strong></p>



<ul class="wp-block-list">
<li>Defines a rule for requests to / (the root URL of your site).</li>



<li>You could add more location blocks for <em>/api, /images, etc.</em> if needed.</li>



<li>Inside: <br>
<ul class="wp-block-list">
<li>proxy_pass:<br>
<ul class="wp-block-list">
<li><em>proxy_pass http://node_app;</em> Tells NGINX to forward requests to the backend defined in upstream <em>node<em>app</em></em></li>



<li>So: if you go to <em>http://yourdomain.com/</em> NGINX proxies that to <em>http://node-app:8080</em></li>
</ul>
</li>
</ul>
</li>



<li>proxy<em>set</em>header (see table)</li>
</ul>



<figure class="wp-block-table"><table><thead><tr><th>
				Header
			</th><th>
				Meaning
			</th></tr></thead><tbody><tr><td>
				<code>Host</code>
			</td><td>
				Preserves the original domain name from the client
			</td></tr><tr><td>
				<code>X-Real-IP</code>
			</td><td>
				The client’s real IP address
			</td></tr><tr><td>
				<code>X-Forwarded-For</code>
			</td><td>
				A list of all proxies the request passed through
			</td></tr><tr><td>
				<code>X-Forwarded-Proto</code>
			</td><td>
				Tells backend whether the request was via HTTP or HTTPS
			</td></tr></tbody></table><figcaption class="wp-element-caption">Nginx Proxy Variables</figcaption></figure>



<p>The <em>$variables</em> in <em>nginx.conf</em> are built-in variables that NGINX provides automatically. They are dynamically set based on the incoming HTTP request. So these variables come from the NGINX core HTTP module and you don’t need to define them or import anything. They are always available in the config. </p>



<p>Here&#8217;s what each one is and where it comes from:</p>



<p><strong>$host</strong></p>



<ul class="wp-block-list">
<li>The value of the Host header in the original HTTP request.</li>



<li>Example: If the user visits http://example.com, then $host is example.com.</li>



<li>Use case: Tells the backend app what domain the client used — useful for apps serving multiple domains.</li>
</ul>



<p><strong>$remote_addr</strong></p>



<ul class="wp-block-list">
<li>The IP address of the client making the request.</li>



<li>Example: If someone from IP 203.0.113.45 visits your site, this variable is set to 203.0.113.45.</li>



<li>Use case: Useful for logging, rate limiting, or geolocation in the backend app.</li>
</ul>



<p><strong>$proxy_add_x_forwarded_for</strong></p>



<ul class="wp-block-list">
<li>A composite header that appends the client&#8217;s IP to the existing X-Forwarded-For header.</li>



<li>Use case: Maintains a full list of proxy hops (useful if your request goes through multiple reverse proxies).</li>



<li>If X-Forwarded-For is already set (by another proxy), it appends $remote_addr to it; otherwise, it sets it to $remote_addr.</li>
</ul>



<p><strong>$scheme</strong></p>



<ul class="wp-block-list">
<li>The protocol used by the client to connect to NGINX — either http or https.</li>



<li>Example: If the user visits https://example.com, then $scheme is https.</li>



<li>Use case: Lets your backend know whether the original request was secure or not.<br></li>
</ul>



<p>Then we have the following structure.</p>



<pre class="wp-block-code"><code> ├── node
	├── funtrails
	│    ├─ app.js
    	│    ├─ package.json
        │    ├─ views    
        │       ├── images
	│	├── index.html
	├── nginx
            ├── nginx.conf
</code></pre>



<h3 class="wp-block-heading">Create Docker Image and Container for nodejs app </h3>



<p>This is to create the Docker Image with <em>docker build</em>. Then we run the Container from the image with <em>docker run</em> to test if everything is working as expected. If everything goes well we can go ahead with the <em>nginx</em> configuration and then with the composition of all together with <em>docker-compose</em>. </p>



<p>The <em>dockerization</em> process of the nodejs app in <em>node/funtrails</em> directory is controlled by the <em>Dockerfile</em> which will be created in <em>node/funtrails</em>. The <em>dockerization</em> process has the following steps.</p>



<ol class="wp-block-list">
<li>Image creation</li>



<li>Container creation from the image<br></li>
</ol>



<p>The Container can then be started, stopped and removed using terminal commands.</p>



<p>Go into in <em>node/funtrails</em>.</p>



<p>To get an Overview and check the Docker status of the system.</p>



<p>List all images. No images on your system.</p>



<pre class="wp-block-code"><code>sudo docker image ls

REPOSITORY   TAG       IMAGE ID   CREATED   SIZE  
</code></pre>



<p>List all Containers. As expected no Containers on the system.</p>



<pre class="wp-block-code"><code>sudo docker ps -a
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
</code></pre>



<p>List an Overview about Docker Images and Containers on your system. As expected no Containers and no Images on the system.</p>



<pre class="wp-block-code"><code>sudo docker system df

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          0         0         0B        0B
Containers      0         0         0B        0B
Local Volumes   0         0         0B        0B
Build Cache     16        0         47.39MB   47.39MB
</code></pre>



<p>Create a <em>Dockerfile</em> in <em>node/funtrails/Dockerfile</em>. This file is required to build the image and run the Container.</p>



<pre class="wp-block-code"><code>#Dockerfile

#Image Build
#Install nodejs 12.22.9 for the Container
FROM node:12.22.9

#Set workdirectory for the Container
WORKDIR /home/node/app

#Change Owner and Group for Container Workdirectory to node
RUN chown node:node /home/node/app

#Run the Container with User node
USER node

#Copy all files from HOST Dir to the Container workdirectory 
COPY --chown=node:node . .

#(After COPY) Run the command to create Image for the Container
RUN npm install

#Container Start
#Open Port 8080 when the Container starts
EXPOSE 8080

#RUN the command when the Container starts
CMD &#91; "node", "app.js" ]
 
</code></pre>



<p>Create a <em>.dockerignore</em> file in <em>node/funtrails/.dockerignore</em>. The <em>hidden dockerignore</em> file exclude only files from the Host that will be copied into the image for the Container with the command <em>COPY . .</em> </p>



<pre class="wp-block-code"><code>#.dockerignore
node_modules
</code></pre>



<p><strong>Note:</strong> In case you copy files from the Host into the image directly with I.e. <em>COPY &lt;file.1&gt; &lt;file.2&gt;</em> then <em>file.1</em> and <em>file.2</em> would be copies even if they would be listed in <em>dockerignore</em>.</p>



<p>We have the following structure on the Host machine.</p>



<pre class="wp-block-code"><code>funtrails
├─ Dockerfile
├─ .dockerignore
├── app.js
├── node_modules
├── package.json
├── package-lock.json
└── views
    ├── images
    └── index.html
</code></pre>



<p>Still be in <em>node/funtrails</em>. </p>



<p><strong>Build the Docker image</strong> from the <em>Dockerfile</em> with the image name <em>node-demo</em>. The dot (.) at the end set the current directory on the Host machine for the <em>docker</em> command. This is location where <em>docker</em> is looking for the <em>Dockerfile</em> to build the image.</p>



<pre class="wp-block-code"><code>sudo docker build -t node-demo .
</code></pre>



<p>List all Docker images. 1 images just created.</p>



<pre class="wp-block-code"><code>sudo docker image ls

REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
node-demo    latest    c353353f045e   22 seconds ago   944MB
</code></pre>



<p><strong>Run the Container</strong> from the image with the name <em>node-demo</em> and give the Container the name <em>funtrails-solo-demo</em>.</p>



<pre class="wp-block-code"><code>sudo docker run -d -p 8080:8080 --name funtrails-solo-demo node-demo
</code></pre>



<p>List all Docker Containers with the option <em>-a</em>. 1 Container with the name <em>funtrails-solo-demo</em> running from the image <em>node-demo</em>.</p>



<pre class="wp-block-code"><code>sudo docker ps -a
</code></pre>



<p>Access the running app on port 8080.</p>



<pre class="wp-block-code"><code>sudo curl http://localhost:8080
</code></pre>



<p>If everything went well you get a feedback in the terminal showing the HTML code. In this case the Test was successful.</p>



<p>Stop the running Docker Container with the name <em>funtrails-solo-demo</em>.</p>



<pre class="wp-block-code"><code>sudo docker stop funtrails-solo-demo
</code></pre>



<p>List all Containers with the option <em>-a</em>. 1 Container from the image <em>node-demo</em> with the name <em>funtrails-solo-demo</em> is <em>EXITED</em>.</p>



<pre class="wp-block-code"><code>sudo docker ps -a
</code></pre>



<p>List all images. Still 1 image available.</p>



<pre class="wp-block-code"><code>sudo docker image ls

REPOSITORY   TAG       IMAGE ID       CREATED        SIZE
node-demo    latest    c353353f045e   36 hours ago   944MB
</code></pre>



<p>List an Overview about Docker Images and Containers on the system. 1 active Image and 1 <em>not Active</em> Container. Status of the Container is <em>EXITED</em> as we have seen above.</p>



<pre class="wp-block-code"><code>sudo docker system df

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          1         1         944MB     0B (0%)
Containers      1         0         0B        0B
Local Volumes   0         0         0B        0B
Build Cache     18        0         47.39MB   47.39MB
</code></pre>



<p>To clean up your system use the following commands.</p>



<figure class="wp-block-table"><table><thead><tr><th>
				Target
			</th><th>
				Command
			</th></tr></thead><tbody><tr><td>
				Delete exited containers
			</td><td>
				<code>sudo docker container prune</code>
			</td></tr><tr><td>
				Delete unused images
			</td><td>
				<code>sudo docker image prune</code>
			</td></tr><tr><td>
				Delete unused volumes
			</td><td>
				<code>sudo docker volume prune</code>
			</td></tr><tr><td>
				Complete Housekeeping (attention!)
			</td><td>
				<code>sudo docker system prune -a</code>
			</td></tr></tbody></table><figcaption class="wp-element-caption">Docker commands for clean up</figcaption></figure>



<p>The full clean up (be careful).</p>



<pre class="wp-block-code"><code>sudo docker system prune -a

sudo docker system df

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          0         0         0B        0B
Containers      0         0         0B        0B
Local Volumes   0         0         0B        0B
Build Cache     0         0         0B        0B
</code></pre>



<h3 class="wp-block-heading">Configure docker-compose</h3>



<p>Go back to the <em>node</em> directory and create a <em> docker-compose.yml</em> file there.</p>



<p>Then we have the following structure.</p>



<pre class="wp-block-code"><code> ├── node
     ├── docker-compose.yml
     ├── funtrails
     │	   ├─ Dockerfile
     │	   ├─ .dockerignore
     │     ├─ app.js
     │     ├─ node_modules
     │	   ├─ package.json
     │	   ├─ package-lock.json
     │     ├─ views    
     │         ├── images
     │         ├── index.html
     ├── nginx
           ├── nginx.conf
</code></pre>



<p><em>docker-compose</em> is a tool that helps you define and run multi-container Docker applications using a YAML file. Instead of running multiple docker run commands, you describe everything in one file and start it all with the command <em>docker-compose up</em>.</p>



<p>Create <em> docker-compose.yml</em> with the following content.</p>



<pre class="wp-block-code"><code>#docker-compose.yml

services:
  funtrails:
    build: ./funtrails
    container_name: funtrails 
    networks:
      - funtrails-network

  nginx:
    image: nginx:latest
    container_name: nginx-proxy
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - funtrails
    networks:
      - funtrails-network

networks:
  funtrails-network:
    driver: bridge
</code></pre>



<p><strong>services</strong></p>



<p>This section defines containers that make up your app</p>



<ul class="wp-block-list">
<li>funtrails<br>
<ul class="wp-block-list">
<li>build: ./funtrails<br>Builds the image from the Dockerfile inside the ./funtrails directory.</li>



<li>container_name: funtrails<br>Names the container funtrails instead of a random name.</li>



<li>networks: funtrails-network<br>Connects the container to a custom user-defined network. </li>
</ul>
</li>



<li>nginx</li>



<li>image: nginx:latest<br>Uses the official latest NGINX image.</li>



<li>container_name: nginx-proxy<br>Container will be named nginx-proxy.</li>



<li>ports: &#8222;80:80&#8220;</li>



<li>Exposes Host port 80 to Container port 80.</li>



<li>volumes:<br>Mounts your local nginx.conf into the container, read-only (:ro).</li>



<li>depends_on: funtrails<br>Ensures funtrails is started before nginx.</li>



<li>networks: funtrails-network<br>Both services are in the same network, so they can communicate by name.</li>
</ul>



<p><strong>networks</strong></p>



<ul class="wp-block-list">
<li>Creates a custom bridge network named funtrails-network.</li>



<li>Ensures containers can resolve each other by name (funtrails, nginx).<br></li>
</ul>



<p><strong>Note:</strong> We are using the official NGINX image directly (<em>image: nginx:latest</em>). This image is prebuilt and includes everything NGINX needs to run. </p>



<p>We don&#8217;t need to write a custom Dockerfile because we don’t  want to:</p>



<ul class="wp-block-list">
<li>Add extra modules</li>



<li>Customize the image beyond just the config</li>



<li>Install additional tools</li>



<li>Include SSL certs directly, etc.</li>
</ul>



<p>Instead, we simply mount our own <em>nginx.conf</em> into the container using a volume. This tells Docker <em>Use the official NGINX image, but replace its config file with mine</em>. We would use a Dockerfile in the <em>nginx</em> directory if we need to build a custom NGINX image, for example to copy SSL certs directly into the image.</p>



<p>Example:</p>



<pre class="wp-block-code"><code>FROM nginx:latest
COPY ./nginx.conf /etc/nginx/nginx.conf
COPY ./certs /etc/nginx/certs
</code></pre>



<p>But for most use cases like reverse proxying a nodejs app, just mounting your own config file is perfectly sufficient and simpler.</p>



<p><strong>Note:</strong>We integrate SSL in the next chapter using free <em>Lets Encrypt</em> certificates.</p>



<h3 class="wp-block-heading">Integrate SSL certificates &#8211; free Lets Encrypt</h3>



<p>To integrate SSL we need to do the following steps:</p>



<ol class="wp-block-list">
<li>Prepare your Domain</li>



<li>Install <em>certbot</em></li>



<li>Create Lets Encrypt SSL certificates</li>



<li>Adapt your <code>node/docker-compose.yml</code></li>



<li>Adapt your <code>node/nginx/nginx.conf</code></li>



<li>Create a cron-Job to renew SSL certificates<br></li>
</ol>



<p><strong>prepare the domain</strong></p>



<p>You must own a domain like <em>example.com</em> and you must have access to your DNS-servers to adapt the <em>A-Record</em>. Here in my example I create a subdomain <em>funtrails.example.com</em> and create on my DNS an <em>A-Record</em> for <em>funtrails.example.com</em> that point to the servers IP-Adress.</p>



<p><strong>install certbot</strong></p>



<p>To install our certificates for SSL we use a tool called <em>certbot</em>. We install <em>certbot</em> with <code>apt</code> on our Linux machine.</p>



<pre class="wp-block-code"><code>sudo apt update
sudo apt install certbot
</code></pre>



<p><strong>create Lets Encrypt SSL certificates</strong></p>



<p>We create the SSL certificates with certbot.</p>



<pre class="wp-block-code"><code>sudo certbot certonly --standalone -d funtrails.example.com

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Enter email address (used for urgent renewal and security notices)
 (Enter 'c' to cancel): &lt;your-email&gt;@funtrails.example.com

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf. You must
agree in order to register with the ACME server. Do you agree?
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
(Y)es/(N)o: Y

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing, once your first certificate is successfully issued, to
share your email address with the Electronic Frontier Foundation, a founding
partner of the Let's Encrypt project and the non-profit organization that
develops Certbot? We'd like to send you email about our work encrypting the web,
EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: Y
Account registered.
Requesting a certificate for funtrails.example.com

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/funtrails.example.com/fullchain.pem

Key is saved at:         /etc/letsencrypt/live/funtrails.example.com/privkey.pem

This certificate expires on 2025-07-25.

These files will be updated when the certificate renews.

Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
</code></pre>



<p>To use SSL you need </p>



<ul class="wp-block-list">
<li>a server certificate (e.g. certificate.crt)</li>



<li>a private key (e.g. private.key) and </li>



<li>the CA certificate (e.g. ca.crt).</li>
</ul>



<figure class="wp-block-table"><table><thead><tr><th>
				File
			</th><th>
				Description
			</th><th>
				Comment
			</th></tr></thead><tbody><tr><td>
				<strong>private.key</strong>
			</td><td>
				Private secret key &#8211; keep this key strictly secret !
			</td><td>
				Only your server knows this key
			</td></tr><tr><td>
				<strong>certificate.crt</strong>
			</td><td>
				Your server certificate (proves your identity)
			</td><td>
				Issued by the CA (Let&#8217;s Encrypt)
			</td></tr><tr><td>
				<strong>ca.crt / chain.crt</strong>
			</td><td>
				The certificate chain up to the root CA
			</td><td>
				So that clients trust your certificate
			</td></tr></tbody></table><figcaption class="wp-element-caption">SSL standard certificates</figcaption></figure>



<p><em>certbot</em> create these file in the following directory on your Host server.</p>



<pre class="wp-block-code"><code>/etc/letsencrypt/live/funtrails.example.com
</code></pre>



<pre class="wp-block-code"><code>sudo ls -l /etc/letsencrypt/live/funtrails.example.com

cert.pem -&gt; ../../archive/funtrails.example.com/cert1.pem
chain.pem -&gt; ../../archive/funtrails.example.com/chain1.pem
fullchain.pem -&gt; ../../archive/funtrails.example.com/fullchain1.pem
privkey.pem -&gt; ../../archive/funtrails.example.com/privkey1.pem
</code></pre>



<p>The translation to the standard is as follows.</p>



<figure class="wp-block-table"><table><thead><tr><th>
				File
			</th><th>
				Description
			</th></tr></thead><tbody><tr><td>
				<code>privkey.pem</code>
			</td><td>
				Your private key (= private.key)
			</td></tr><tr><td>
				<code>cert.pem</code>
			</td><td>
				Your server certificate (= certificate.crt)
			</td></tr><tr><td>
				<code>chain.pem</code>
			</td><td>
				The CA certificates (= ca.crt)
			</td></tr><tr><td>
				<code>fullchain.pem</code>
			</td><td>
				Server certificate + CA chain together
			</td></tr></tbody></table><figcaption class="wp-element-caption">LetsEncrypt translation to standard SSL certificates</figcaption></figure>



<p><strong>adapt node/docker-compose.yml</strong></p>



<p>The <em>docker-compose.yml</em> will be adapted as follows.</p>



<pre class="wp-block-code"><code>services:
  funtrails:
    build: ./funtrails
    container_name: funtrails
    networks:
      - funtrails-network

  nginx:
    image: nginx:latest
    container_name: nginx-proxy
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - funtrails
    networks:
      - funtrails-network

networks:
  funtrails-network:
    driver: bridge
</code></pre>



<p>We create a bridge network with the name <em>funtrails-network</em> and both services are running in this network. This is important to reach all services by their container name(s). </p>



<p>The <em>funtrails</em> service will rebuild from the <em>Dockerfile</em> in <em>./funtrails</em>. For the <em>nginx</em> service the <em>nginx-image</em> will be loaded from the Docker resources in the latest version. For the <em>nginx</em> image it is defined that the Host ports 80 and 443 will be mapped into the Container ports 80 and 443. When the Container is started we mount the Host files <em>./nginx/nginx.conf</em> and the SSL certificates unter <em>/etc/letsencrypt</em> into the Container. Both will be loaded with read only!</p>



<pre class="wp-block-code"><code>...
volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
...
</code></pre>



<p>With the <em>depends<em>on</em></em> directive we declare that first the <em>funtrails</em> service must be started and then <em>nginx</em>. </p>



<p><strong>adapt node/nginx/nginx.conf</strong></p>



<p>The file will be adapted as follows.</p>



<pre class="wp-block-code"><code>events {
  worker_connections 1024; 
}

http {

    server {
      listen 80;
      server_name funtrails.example.com;

      # Redirect HTTP -&gt; HTTPS
      return 301 https://$host$request_uri;
    }

   server {
    listen 443 ssl;
    server_name funtrails.example.com;

    ssl_certificate /etc/letsencrypt/live/funtrails.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/funtrails.example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://funtrails:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
  }
}

</code></pre>



<p>The <em>events{}</em> Block is a required block in NGINX configuration, even if empty. It handles connection-related events (like concurrent connections), but you don&#8217;t need to configure it unless you have advanced use cases. Here I configured 1024 concurrent connections. </p>



<p>Within the <em>http</em> block we have 2 virtual server-blocks. The first server-block define that server <em> funtrails.example.com</em> is listening to port 80 (HTTP) but all requests to this port will immediately be redirected to port 443 (HTTPS). The second server-block define that server <em> funtrails.example.com</em> is listening also to port 443 (HTTPS) followed by the location of the SSL certificate and SSL key on our local Host and the protocol definition. </p>



<p>The location-block define a rule for requests to / (the root URL of your site). You could add more location blocks i.e. for /api, /images, etc. if needed. In this config, we are skipping the upstream block and directly writing <em>proxy<em>pass</em></em>. The <em>proxy<em>pass</em></em> tells NGINX to forward requests to port 8080 of the backend service defined in <em>docker-compose.yml</em>. This backend service in <em>docker-compose.yml</em> is defined with the <em>container_name</em> directive which is set to <em>funtrails</em>. </p>



<pre class="wp-block-code"><code>...
services:
  funtrails:
	build: ./funtrails
    container_name: funtrails
	networks:
	   - funtrails-network
... 
</code></pre>



<p>Docker Compose creates an internal Docker network <em>funtrails-network</em>, and all services can reach each other by their service name(s) as hostname(s). So nginx can resolve <em>funtrails</em> because it&#8217;s part of the same Docker network (no need for a manual upstream block).</p>



<p>These other <em>$variables</em> come from NGINX&#8217;s core HTTP module, so we don’t need to define them. They are always available in the config. </p>



<p><strong>$host</strong></p>



<ul class="wp-block-list">
<li>What it is: The value of the Host header in the original HTTP request.</li>



<li>Example: If the user visits http://example.com, then $host is example.com.</li>



<li>Use case: Tells the backend app what domain the client used — useful for apps serving multiple domains.</li>
</ul>



<p><strong>$remote<em>addr</em></strong></p>



<ul class="wp-block-list">
<li>What it is: The IP address of the client making the request.</li>



<li>Example: If someone from IP 203.0.113.45 visits your site, this variable is set to 203.0.113.45.</li>



<li>Use case: Useful for logging, rate limiting, or geolocation in the backend app.</li>
</ul>



<p><strong>$proxy<em>add</em>x<em>forwarded</em>for</strong></p>



<ul class="wp-block-list">
<li>What it is: A composite header that appends the client&#8217;s IP to the existing X-Forwarded-For header.</li>



<li>Use case: Maintains a full list of proxy hops (useful if your request goes through multiple reverse proxies).</li>



<li>How it works: If X-Forwarded-For is already set (by another proxy), it appends $remote<em>addr to it; otherwise, it sets it to $remote</em>addr.</li>
</ul>



<p><strong>$scheme</strong></p>



<ul class="wp-block-list">
<li>What it is: The protocol used by the client to connect to NGINX — either http or https.</li>



<li>Example: If the user visits https://example.com, then $scheme is https.</li>



<li>Use case: Lets your backend know whether the original request was secure or not.<br></li>
</ul>



<p><strong> Create a cron-Job to renew Lets Encrypt SSL certificates</strong></p>



<p>Lets Encrypt SSL Certificates must be renewed after 90 days. <em>certbot</em> can renew your certificates. To automize the renewal you can create a cronjob on your Host machine. </p>



<p><strong>Note:</strong> Sometimes cron doesn&#8217;t know where docker-compose is located (because the environment variables are missing). Therefore, it&#8217;s safer to use the full paths in crontab. You check the relevant paths as follows:</p>



<pre class="wp-block-code"><code>which docker-compose
/usr/bin/docker-compose

which certbot
/usr/bin/certbot
</code></pre>



<p>The create a cronjob in crontab of the user root (use sudo):</p>



<pre class="wp-block-code"><code>sudo crontab -e 

0 3 *&nbsp;* *&nbsp;	/usr/bin/certbot renew --quiet &amp;&amp; /usr/bin/docker-compose restart nginx
</code></pre>



<p>With <em>sudo crontab -e </em> you create a crontab for the user root. All commands within the root crontab will be executed with root privileges.  </p>



<p><em>certbot renew</em> checks all certificates for expiration dates and automatically renews them.</p>



<p><em>docker-compose restart nginx</em> ensures that nginx is reloaded so that it can apply the new certificates. Otherwise, nginx would still be using old certificates even though new ones are available. With the command you call <em>docker-compose restart &lt;service-name&gt;</em>. Here you specify the service name from <em>docker-compose.yml</em> not the container name.</p>



<p><strong>Note:</strong> In case you would call <em>crontab -e</em> (without <em>sudo</em>) you would edit your own user crontab. This crontab then runs under your user, not as root and the tasks in crontab run unter this normal user. When you renew SSL certificates using <em>certbot renew</em> this job must write into the directories under <em>/etc/letsencrypt/</em> on your machine. But these directories are owned by root. So you cannot write to the directories when the crontab runs under a normal user. One might then think that the commands in the crontab of a normal user should be executed with <em>sudo</em>. If a crontab job of your normal user (i.e. patrick) is running and sudo is used in the command, then sudo will attempt to prompt for the password. But there is no terminal in crontab where you can enter a password. Therefore, the command will fail (error in the log, nothing happens). Therefore it is essential here to edit the crontab of the user root with <em>sudo crontab -e</em>. </p>



<p>Finally you can check the crontab of your own or the crontab as root as follows.</p>



<pre class="wp-block-code"><code>crontab -l
no crontab for patrick

sudo crontab -l
# Edit this file to introduce tasks to be run by cron.
# 
# Each task to run has to be defined through a single line
# indicating with different fields when the task will be run
# and what command to run for the task
# 
# To define the time you can provide concrete values for
# minute (m), hour (h), day of month (dom), month (mon),
# and day of week (dow) or use '*' in these fields (for 'any').
# 
# Notice that tasks will be started based on the cron's system
# daemon's notion of time and timezones.
# 
# Output of the crontab jobs (including errors) is sent through
# email to the user the crontab file belongs to (unless redirected).
# 
# For example, you can run a backup of all your user accounts
# at 5 a.m every week with:
# 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/
# 
# For more information see the manual pages of crontab(5) and cron(8)
# 
# m h  dom mon dow   command

0 3 * * * /usr/bin/certbot renew --quiet &amp;&amp; /usr/bin/docker-compose restart nginx
 
</code></pre>



<p>You can check the logs for the renewal process using the following command.</p>



<pre class="wp-block-code"><code>sudo cat /var/log/letsencrypt/letsencrypt.log
</code></pre>



<h3 class="wp-block-heading">Start the Containers with docker-compose</h3>



<p>Navigate to the directory with <em>docker-compose.yml</em>. Then use the following commands.</p>



<pre class="wp-block-code"><code>sudo docker-compose build

sudo docker-compose up -d
</code></pre>



<p>The command <em>docker-compose build</em> reads the Dockerfile for each service defined in <em>docker-compose.yml</em> and builds the Docker image accordingly. The command <em>docker-compose up -d</em> run the container(s) and the network. This starts all services defined in the <em>docker-compose.yml</em> and links them via the defined docker network. The -d flag runs the containers in the background (detached mode).</p>



<p>Then you can check the status using the following commands.</p>



<pre class="wp-block-code"><code>sudo docker-compose ps

sudo docker ps
</code></pre>



<p>Here is an overview of the most important commands.</p>



<figure class="wp-block-table"><table><thead><tr><th>
				Command
			</th><th>
				Purpose
			</th></tr></thead><tbody><tr><td>
				<code>docker-compose build</code>
			</td><td>
				Build all images from Dockerfiles
			</td></tr><tr><td>
				<code>docker-compose up -d</code>
			</td><td>
				Start containers in the background
			</td></tr><tr><td>
				<code>docker-compose ps</code>
			</td><td>
				See status of containers
			</td></tr><tr><td>
				<code>docker-compose down</code>
			</td><td>
				Stop and remove all containers
			</td></tr><tr><td>
				<code>docker-compose logs -f</code>
			</td><td>
				Follow logs of all services
			</td></tr></tbody></table><figcaption class="wp-element-caption">Docker-compose commands</figcaption></figure>



<h3 class="wp-block-heading">How to manage Changes made to the application code</h3>



<p>When we make changes to the app code i.e. in <em>node/funtrails/app.js</em> or in <em>node/funtrails/Dockerfile</em> we need to rebuild the image for the funtrails service defined in <em>node/docker-compose.yml</em>. In such a change scenario it is not necessary to stop the containers with <em>docker-compose down</em> before you rebuild the image with <em>docker-compose build</em>.</p>



<p>You can rebuild and restart only the <em>funtrails</em> service with the following commands.</p>



<pre class="wp-block-code"><code>docker-compose build funtrails

docker-compose up -d funtrails
</code></pre>



<p>This will:</p>



<ul class="wp-block-list">
<li>Rebuild the <em>funtrails</em> image</li>



<li>Stop the old <em>funtrails</em> container (if running)</li>



<li>Start a new container using the updated image</li>



<li>Without affecting other services like <em>nginx</em><br></li>
</ul>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>nginx  reverse proxy server for node apps on Mac OS</title>
		<link>https://digitaldocblog.com/webserver/nginx-reverse-proxy-server-for-node-apps-on-mac-os/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Sat, 11 Feb 2023 08:35:13 +0000</pubDate>
				<category><![CDATA[Webserver]]></category>
		<category><![CDATA[NginX]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=162</guid>

					<description><![CDATA[nginx is an HTTP server and can also be used as TCP/UDP proxy server. I use nginx mostly as reverse proxy to serve node apps from my local host via&#8230;]]></description>
										<content:encoded><![CDATA[
<p><a href="https://nginx.org/en/" title="nginx">nginx</a> is an HTTP server and can also be used as TCP/UDP proxy server. I use nginx mostly as reverse proxy to serve node apps from my local host via the reverse proxy. The reverse proxy is listening on port 443 to requests coming from the outside and forward these requests to localhost where the app is listening on a different port.</p>



<h3 class="wp-block-heading">Installation</h3>



<p>So lets install <a href="https://formulae.brew.sh/formula/nginx#default" title="noginx package on homebrew">nginx with homebrew</a>. In case you dont know what homebrew is and need to install it on your Mac <a href="https://digitaldocblog.com/mac/homebrew-package-manager-on-mac-os/" title="Homebrew Package Manager on Mac OS">read my article on Digitaldocblog</a>. </p>



<pre class="wp-block-code"><code>brew install nginx

brew services start nginx

brew services stop nginx

</code></pre>



<ul class="wp-block-list">
<li>nginx has been installed in /usr/local/Cellar/nginx/1.17.7</li>



<li>nginx configuration file is /usr/local/etc/nginx/nginx.conf <br></li>
</ul>



<h3 class="wp-block-heading">Configuration</h3>



<p>Setup the core nginx configuration file nginx.conf in your environment:</p>



<pre class="wp-block-code"><code># /usr/local/etc/nginx/nginx.conf 

# Main Context
worker_processes  auto;
error_log  /usr/local/etc/nginx/logs/error.log;
pid /usr/local/etc/nginx/run/nginx.pid;  

# Events Context
events {
    worker_connections  1024;
}

# HTTP Context
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    access_log  /usr/local/etc/nginx/logs/access.log;

    include servers/*;
}
</code></pre>



<p>The <strong>Main Context</strong> is the most general context and is the only context that is not surrounded by curly braces. The Main Context is placed at the beginning of the core nginx configuration file. The directives of the Main Context cannot be inherited in any other context and can not be overridden.</p>



<p>The Main Context is used to configure the basic details of an application. Some common details that are configured in the Main Context are: </p>



<ul class="wp-block-list">
<li>The directive <em>worker_processes defines</em> the number of worker processes. The number depend on the number of CPU cores and the default value is 1. The value <em>auto</em> will autodetect the number of worker processes. The directive tell the virtual servers defined in the servers folder (see below) know how many workers can be created once they are bound to the IP(s) and port(s). It is common practice to run 1 worker process per core. Anything above this won’t hurt your system, but it will leave idle processes usually just lying about</li>



<li>The directive <em>error_log</em> define the default error file for the entire application</li>



<li>The directive <em>pid</em> define the file to save the main process ID.<br></li>
</ul>



<p>The <strong>Event Context</strong> as also the <strong>HTTP-Context</strong> are childs of the <strong>Main Context</strong> and therefore lower contexts. Lower Contexts handle the request, and the directives at this level and control the defined defaults for every virtual server.</p>



<p>The <strong>Events Context</strong> define global options for connection processing and is contained within the main context. There can be only one Event Context defined within Nginx configuration. Within the Events Context we define the <strong>worker_connections</strong> to tells our worker processes how many connections can simultaneously be served by Nginx. The default value is 768. Considering that every browser usually opens up at least 2 connections per server, this is why we need to adjust our worker connections to its full potential. Therefore we define 1024 worker connections. </p>



<p>The <strong>HTTP Context</strong> is used to hold the directives for handling HTTP or HTTPS traffic. </p>



<ul class="wp-block-list">
<li>with the <strong>include</strong> directive we includes another file, or files matching the specified mask, into configuration. </li>



<li>with the <strong>default_type</strong> directive we define the default MIME type of a response. When a web server sends HTTP traffic back to the client (response), it usually adds an IANA media type (formerly known as MIME types), or content type, to the packet header that shows what kind of content is in the packet. The HTTP header on the data stream contains this content type. It is added by the web server before the data is sent.</li>



<li>using the directive <strong>sendfile</strong> enables or disables the use of the function sendfile(). By default, NGINX handles file transmission itself and copies the file into the buffer before sending it. Enabling the sendfile directive eliminates the step of copying the data into the buffer and enables direct file transmission.</li>



<li>the directive <strong>keepalive_timeout</strong> sets a timeout during which a keep-alive client connection will stay open on the server side. <br></li>
</ul>



<p>Finally the <strong>include</strong> directive within the HTTP Context tell nginx to load all virtual servers from the files in /usr/local/etc/nginx/servers in the HTTP Context.</p>



<p>Then I create 2 serverfiles in /usr/local/etc/nginx/servers.</p>



<ol class="wp-block-list">
<li>A serverfile to run a virtual http server on localhost port 7445</li>



<li>A serverfile to run a virtual http reverse proxy server on localhost port 7444<br></li>
</ol>



<pre class="wp-block-code"><code>Patricks-Macbook Pro:servers patrick$ ls -l

total 24
-rw-r--r--@ 1 patrick  admin  188 15 Nov 06:12 patrick_7445
-rw-r--r--@ 1 patrick  admin  452 15 Nov 07:39 reverse_7444

Patricks-Macbook Pro:servers patrick$ 

</code></pre>



<p>The <strong>Server Context</strong> is inside these serverfiles. In general the Server Context is defined within the HTTP Context. </p>



<p>The Server Context define the virtual host settings. There can be multiple Server Context definitions inside the HTTP Context. The directives inside the Server Context handle the processing of requests for resources associated with a particular domain or IP address.</p>



<p>The directives in this Server Context can override many of the directives that may be defined in the HTTP Context, including the document root, logging, compression, etc. In addition to the directives that are taken from the HTTP Context, we can also configure files to try to respond to requests, issue redirects, and rewrites, and set arbitrary variables.</p>



<pre class="wp-block-code"><code># /usr/local/etc/nginx/servers/reverse_7444

server {

    listen 7444;
    server_name patrick.local;

    location / {
       proxy_pass http://localhost:7445;
       proxy_set_header X-Forwarded-For $remote_addr;
    }
}

</code></pre>



<p>To connect to your localhost using patrick.local the dns settings in etc/host must be changed accordingly.</p>



<pre class="wp-block-code"><code># /private/etc/hosts

192.168.178.20 servtest.rottlaender.lan

</code></pre>



<pre class="wp-block-code"><code># /usr/local/etc/nginx/servers/patrick_7445

server {
     listen       7445;
     server_name  localhost;

     location / {
         root   /Users/patrick/Sites/www/nginx/patrick_7445;
         index  index.html index.htm;
    }
}

</code></pre>



<h3 class="wp-block-heading">TLS/SSL for nginx</h3>



<p>SSL/TLS works by using the combination of a public certificate and a private key. </p>



<p>The <strong>SSL key (private key)</strong> is kept secret on the server. It is used to encrypt content sent to clients. </p>



<p>The <strong>SSL certificate</strong> is publicly shared with anyone requesting the content. It can be used to decrypt the content signed by the associated SSL key.</p>



<p><strong>create private key</strong></p>



<pre class="wp-block-code"><code>Patricks-MBP:express-security patrick$ cd /usr/local/etc/nginx

Patricks-MBP:nginx patrick$ ls -l
total 144
-rw-r--r--  1 patrick  admin  1077  8 Apr 13:18 fastcgi.conf
-rw-r--r--  1 patrick  admin  1077  8 Apr 13:18 fastcgi.conf.default
-rw-r--r--  1 patrick  admin  1007  8 Apr 13:18 fastcgi_params
-rw-r--r--  1 patrick  admin  1007  8 Apr 13:18 fastcgi_params.default
-rw-r--r--  1 patrick  admin  2837  8 Apr 13:18 koi-utf
-rw-r--r--  1 patrick  admin  2223  8 Apr 13:18 koi-win
-rw-r--r--  1 patrick  admin  5231  8 Apr 13:18 mime.types
-rw-r--r--  1 patrick  admin  5231  8 Apr 13:18 mime.types.default
-rw-r--r--  1 patrick  admin  3106 15 Mai 11:19 nginx.conf
-rw-r--r--  1 patrick  admin  2680  8 Apr 13:18 nginx.conf.default
-rw-r--r--  1 patrick  admin  3091 21 Jan 05:40 nginx.conf.working
-rw-r--r--  1 patrick  admin   636  8 Apr 13:18 scgi_params
-rw-r--r--  1 patrick  admin   636  8 Apr 13:18 scgi_params.default
drwxr-xr-x  3 patrick  admin    96 21 Jan 06:02 servers
-rw-r--r--  1 patrick  admin   664  8 Apr 13:18 uwsgi_params
-rw-r--r--  1 patrick  admin   664  8 Apr 13:18 uwsgi_params.default
-rw-r--r--  1 patrick  admin  3610  8 Apr 13:18 win-utf

Patricks-MBP:nginx patrick$ mkdir ssl

Patricks-MBP:nginx patrick$ ls -l
total 152
-rw-r--r--  1 patrick  admin  1077  8 Apr 13:18 fastcgi.conf
-rw-r--r--  1 patrick  admin  1077  8 Apr 13:18 fastcgi.conf.default
-rw-r--r--  1 patrick  admin  1007  8 Apr 13:18 fastcgi_params
-rw-r--r--  1 patrick  admin  1007  8 Apr 13:18 fastcgi_params.default
-rw-r--r--  1 patrick  admin  2837  8 Apr 13:18 koi-utf
-rw-r--r--  1 patrick  admin  2223  8 Apr 13:18 koi-win
-rw-r--r--  1 patrick  admin  5231  8 Apr 13:18 mime.types
-rw-r--r--  1 patrick  admin  5231  8 Apr 13:18 mime.types.default
-rw-r--r--@ 1 patrick  admin   373 18 Mai 13:38 nginx.conf
-rw-r--r--  1 patrick  admin  2680  8 Apr 13:18 nginx.conf.default
-rw-r--r--  1 patrick  admin  3091 21 Jan 05:40 nginx.conf.working
-rw-r--r--@ 1 patrick  admin  1390 17 Mai 09:19 nginx_old.conf
-rw-r--r--  1 patrick  admin   636  8 Apr 13:18 scgi_params
-rw-r--r--  1 patrick  admin   636  8 Apr 13:18 scgi_params.default
drwxr-xr-x  5 patrick  admin   160 18 Mai 13:20 servers
drwxr-xr-x  4 patrick  admin   128 16 Mai 07:41 ssl
-rw-r--r--  1 patrick  admin   664  8 Apr 13:18 uwsgi_params
-rw-r--r--  1 patrick  admin   664  8 Apr 13:18 uwsgi_params.default
-rw-r--r--  1 patrick  admin  3610  8 Apr 13:18 win-utf

Patricks-MBP:nginx patrick$ cd ssl

Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl genrsa -out privateKey.pem 4096

Patricks-MBP:ssl patrick$ ls -l
total 16
-rw-r--r--  1 patrick  admin  3247 16 Mai 07:22 privateKey.pem

</code></pre>



<p><strong>create certificate signing request (CSR)</strong></p>



<pre class="wp-block-code"><code>Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl req -new -key privateKey.pem -out csr.pem

Patricks-MBP:ssl patrick$ ls -l
total 16
-rw-r--r--  1 patrick  admin  1740 16 Mai 07:23 csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai 07:22 privateKey.pem

</code></pre>



<p>In case I would like to request an official certificate I must send this csr to the certificate authority. This authority would then create an <strong>authority signed certificate</strong> from the CSR and send it back to me.</p>



<p>This step is done by ourselves and this is the reason why we create a <strong>self signed certificate</strong>. This self signed certificate is not an official certificate and not trusted by any browser. It is not useful to use a self signed certificate in production because it produces error messages in the browsers. But for local development a self signed certificate is ok. </p>



<p>So create the self signed certificate. The csr file can then be removed. </p>



<p><strong>create the self signed certificate</strong></p>



<pre class="wp-block-code"><code>Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl x509 -in csr.pem -out selfsignedcertificate.pem -req -signkey privateKey.pem -days 365

Patricks-MBP:ssl patrick$ ls -l
total 24
-rw-r--r--  1 patrick  admin  1740 16 Mai 07:23 csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai 07:22 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai 07:39 selfsignedcertificate.pem

Patricks-MBP:ssl patrick$ rm csr.pem

Patricks-MBP:ssl patrick$ ls -l
total 24
-rw-r--r--  1 patrick  admin  3247 16 Mai 07:22 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai 07:39 selfsignedcertificate.pem
 
</code></pre>



<p><strong>show certificate details</strong></p>



<pre class="wp-block-code"><code>Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl x509 -in selfsignedcertificate.pem -text -noout

</code></pre>



<p><strong>renew self signed certificate</strong></p>



<p>So let&#8217;s assume 1 year has passed by and now we want to renew this certificate selfsignedcertificate.pem. Now you may either generate a new CSR or export the CSR from the existing expired self-signed certificate.</p>



<p>First let&#8217;s check the validity of the existing certificate.</p>



<pre class="wp-block-code"><code>patrick@MacBookPro-Patrick ssl % ls -l                                                          
total 32
-rw-r--r--  1 patrick  admin  6279 12 Mär 08:09 new-csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai  2020 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai  2020 selfsignedcertificate.pem

patrick@MacBookPro-Patrick ssl % openssl x509 -noout -text -in selfsignedcertificate.pem | grep -i -A2 validity
        Validity
            Not Before: May 16 05:39:28 2020 GMT
            Not After : May 16 05:39:28 2021 GMT

patrick@MacBookPro-Patrick ssl %

</code></pre>



<p>So our certificate selfsignedcertificate.pem expired on 16 Mai 2021 and today is 12 Mar 2022.</p>



<p>It is recommended to export the CSR as you don&#8217;t have to worry about giving the same Distinguished Name information. Although this information can be collected from the certificate itself. For us, we will export the CSR from the certificate itself.</p>



<pre class="wp-block-code"><code>patrick@MacBookPro-Patrick ssl % ls -l
total 16
-rw-r--r--  1 patrick  admin  3247 16 Mai  2020 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai  2020 selfsignedcertificate.pem

patrick@MacBookPro-Patrick ssl % openssl x509 -x509toreq -in selfsignedcertificate.pem  -signkey privateKey.pem -out new-csr.pem          
Getting request Private Key
Generating certificate request

patrick@MacBookPro-Patrick ssl % ls -l
total 32
-rw-r--r--  1 patrick  admin  6279 12 Mär 08:09 new-csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai  2020 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai  2020 selfsignedcertificate.pem

patrick@MacBookPro-Patrick ssl %

</code></pre>



<p>Now that we have our private key privateKey.pem and the new CSR new-csr.pem, we can renew our certificate selfsignedcertificate.pem. You should not get confused with the term &#8222;renew&#8220;, we are not extending the expiry of the certificate, instead we are just creating a new self signed certificate using the CSR new-csr.pem from the existing private key privateKey.pem. The Signature Modules are the same and our application considers the certificate as same instead of new.</p>



<pre class="wp-block-code"><code>patrick@MacBookPro-Patrick ssl % ls -l
total 32
-rw-r--r--  1 patrick  admin  6279 12 Mär 08:09 new-csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai  2020 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai  2020 selfsignedcertificate.pem

patrick@MacBookPro-Patrick ssl % openssl x509 -req -days 365 -in new-csr.pem -signkey privateKey.pem -out new-selfsignedcertificate.pem
Signature ok
subject=/C=DE/ST=Bayern/L=Munich/O=Digitaldocblog/CN=localhost/emailAddress=p.rottlaender@icloud.com
Getting Private key

patrick@MacBookPro-Patrick ssl % ls -l
total 40
-rw-r--r--  1 patrick  admin  6279 12 Mär 08:09 new-csr.pem
-rw-r--r--  1 patrick  admin  1980 12 Mär 08:30 new-selfsignedcertificate.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai  2020 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai  2020 selfsignedcertificate.pem

patrick@MacBookPro-Patrick ssl % 

</code></pre>



<p>Lets check the validity of the new certificate new-selfsignedcertificate.pem.</p>



<pre class="wp-block-code"><code>
patrick@MacBookPro-Patrick ssl % openssl x509 -noout -text -in selfsignedcertificate.pem | grep -i -A2 validity
        Validity
            Not Before: May 16 05:39:28 2020 GMT
            Not After : May 16 05:39:28 2021 GMT

patrick@MacBookPro-Patrick ssl % openssl x509 -noout -text -in new-selfsignedcertificate.pem | grep -i -A2 validity
        Validity
            Not Before: Mar 12 07:30:52 2022 GMT
            Not After : Mar 12 07:30:52 2023 GMT

patrick@MacBookPro-Patrick ssl % 

</code></pre>



<p>While our certificate selfsignedcertificate.pem expired on 16 Mai 2021 the new certificate new-selfsignedcertificate.pem expires on 12 Mar 2023. </p>



<p>The we rename the new-selfsignedcertificate.pem to selfsignedcertificate.pem to ensure </p>



<pre class="wp-block-code"><code>patrick@MacBookPro-Patrick ssl % mv selfsignedcertificate.pem old-selfsignedcertificate.pem

patrick@MacBookPro-Patrick ssl % mv new-selfsignedcertificate.pem selfsignedcertificate.pem
patrick@MacBookPro-Patrick ssl % ls -l
total 40
-rw-r--r--  1 patrick  admin  6279 12 Mär 08:09 new-csr.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai  2020 old-selfsignedcertificate.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai  2020 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 12 Mär 08:30 selfsignedcertificate.pem

patrick@MacBookPro-Patrick ssl %

</code></pre>



<p>Finally we restart nginx service.</p>



<pre class="wp-block-code"><code>patrick@MacBookPro-Patrick ssl % brew services list                                        
Name                  Status  User    File
mongodb-community@4.4 started patrick ~/Library/LaunchAgents/homebrew.mxcl.mongodb-community@4.4.plist
nginx                 started patrick ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist
patrick@MacBookPro-Patrick ssl % brew services stop nginx
Stopping `nginx`... (might take a while)
==&gt; Successfully stopped `nginx` (label: homebrew.mxcl.nginx)
patrick@MacBookPro-Patrick ssl % brew services start nginx
==&gt; Successfully started `nginx` (label: homebrew.mxcl.nginx)
patrick@MacBookPro-Patrick ssl % brew services list       
Name                  Status  User    File
mongodb-community@4.4 started patrick ~/Library/LaunchAgents/homebrew.mxcl.mongodb-community@4.4.plist
nginx                 started patrick ~/Library/LaunchAgents/homebrew.mxcl.nginx.plist
patrick@MacBookPro-Patrick ssl %

</code></pre>



<p><strong>configure nginx with SSL</strong></p>



<p>Any default configurations are done in nginx.conf. In this configuration we define a local webserver and a reverse proxy server. </p>



<p>The ip 127.0.0.1 is resolved in hosts file to localhost.</p>



<p>In the default configuration we define that any request to localhost on port 80 is redirected to https/ssl (443 ssl). This is a typical production configuration to enforce htps/ssl. </p>



<pre class="wp-block-code"><code>// /usr/local/etc/nginx.conf
// default configuration

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    
	# default server configuration
    server {
      	listen 80 default_server;
    	listen &#91;::]:80 default_server;
    	
    	# default server your.domain.com
    	server_name localhost;
    	
    	# enforce 301 redirect to ssl port on your default server
    	return 301 https://$host$request_uri;
    }
    
	# In servers directory we define the servers running on this machine
    include servers/*;
}

</code></pre>



<p>The <strong>local webserver</strong> is bound to 127.0.0.1 and this ip is resolved to localhost. localhost is listening on port 443. This local webserver is using https/ssl. Any ssl configuration is done via the ssl_* directives in the server block.</p>



<p>In the location block we define the root directory of the local webserver and the index files. Here the local webserver process https client requests and send responses back to the client. So when a client request the route / then the local webserver render index.htm or index.html and respond to the client.<br></p>



<pre class="wp-block-code"><code>// /usr/local/etc/servers/localweb

server {
     listen       443 ssl;
     # here you would define in production your.domain.com
     server_name  localhost;

         ssl_certificate      ssl/selfsignedcertificate.pem;
         ssl_certificate_key  ssl/privateKey.pem;
         ssl_session_cache    shared:SSL:1m;
         ssl_session_timeout  5m;
         ssl_ciphers  HIGH:!aNULL:!MD5;
         ssl_prefer_server_ciphers  on;

     location / {
         root   /Users/patrick/Sites/prod/digitaldocblog-V2/public;
         index  index.html index.htm;
    }
}

</code></pre>



<p>The <strong>reverse proxy server</strong> is bound to ip 192.168.178.20. The ip 192.168.178.20 is resolved to servtest.rottlaender.lan in hosts file. This reverse proxy server is using https/ssl. Any ssl configuration is done via the ssl_* directives in the server block. </p>



<p>The <strong>node express server</strong> is listening on localhost port 3300. This express server is using http. </p>



<p>Any request to 192.168.178.20 or servtest.rottlaender.lan to port 3000 will be passed to application server listening on localhost port 3300. </p>



<p>So any client https request for servtest.rottlaender.lan on route / will be directly handed over to the express server. The express server will then process the request and send the response back to servtest.rottlaender.lan which will then respond 1:1 to the client. </p>



<pre class="wp-block-code"><code>// /usr/local/etc/servers/reverse

server {
    listen      3000 ssl;
    
	# /private/etc/hosts
	# 192.168.178.20	servtest.rottlaender.lan
	
    server_name servtest.rottlaender.lan;

    ssl_certificate      ssl/selfsignedcertificate.pem;
    ssl_certificate_key  ssl/privateKey.pem;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;

    location / {
       proxy_pass http://localhost:3300;
    }
}

</code></pre>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Role Based Access Control using express-session in a node-js app</title>
		<link>https://digitaldocblog.com/webdesign/role-based-access-control-using-express-session-in-a-node-js-app/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Tue, 04 May 2021 12:00:00 +0000</pubDate>
				<category><![CDATA[Web-Development]]></category>
		<category><![CDATA[Webdesign]]></category>
		<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Express.js]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[Java Script]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[MongoDB]]></category>
		<category><![CDATA[Mongoose]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[NPM Node package manager]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=141</guid>

					<description><![CDATA[In this article I refer to an application I created a couple of months ago. It&#8217;s about a booking system with which players can book ice-hockey trainings in different locations,&#8230;]]></description>
										<content:encoded><![CDATA[
<p>In this article I refer to an application I created a couple of months ago. It&#8217;s about a booking system with which players can book ice-hockey trainings in different locations, the coach can confirm participation in a training session  and a club manager can organize training sessions and bill the players for booked trainings. You can see the code on my <a href="https://github.com/prottlaender/bookingsystem" title="Node-Js Booking-System Code on GitHub Account of Patrick Rottlaender">GitHub Account</a> and read a detailed application description in the style of a user manual on my blog <a href="https://digitaldocblog.com/singleblog?article=9" title="Booking-System Application Description on Digitaldocblog Website owned by Patrick Rottlaender">Digitaldocblog</a>.</p>



<p>In my booking system I give users different roles in my app and depending on their role, the users have different authorizations. An <em>admin</em> for example is able to access more sensitive data and functionalities than a normal <em>player</em> or a <em>coach</em>. So my app must know the role of a user to assign different authorizations to the particular user.</p>



<p>Clients, usually browsers send requests the app. The app responds to requests and is solely responsible for ensuring that the client only has access to the data that are intended for it. This request and response game is based on the HTTP protocol. HTTP is a stateless network protocol and requests cannot be related to each other. Each request is isolated and unrelated to previous requests and the server has no chance to recognize clients and does therefore not know their role. </p>



<p>This problem can be solved with sessions and cookies and means that session management must be implemented in the application. The application creates a session and stores session data such as the role of a requestor in this session. The session has a unique ID and the app saves only this ID in a cookie. The cookie is transferred to the browser and stored locally there. From now on, the browser always sends this cookie with the HTTP request and thus identifies itself to the application. The application can check the role of the requestor in the stored session data and control the appropriate access.</p>



<h3 class="wp-block-heading">Basic setup of the server</h3>



<p>First we need a working Server OS. I run Linux Ubuntu in production and have written an article about the <a href="https://digitaldocblog.com/singleblog?article=10" title="Basic Setup for Node-Js Apps running on Ubuntu Linux on Digitaldocblog Website owned by Patrick Rottländer ">basic setup of a production Linux server</a> on my blog site <a href="https://digitaldocblog.com/home?currpage=1" title="Digitaldocblog Website owned by Patrick Rottlaender">Digitaldocblog</a>. Since I am going to store the sessions in a MongoDB, MongoDB must be installed on the Linux server. I use <em>MongoDB Community Edition</em> but you can also install or upgrade to the <em>MongoDB Enterprise</em> Server version. In the lower part of the article you find the instructions how to install and setup your <em>MongoDB Community Edition</em> on your Linux System. In case you want to read the original documentation go on the MongoDB site and read how to install the <a href="https://docs.mongodb.com/manual/administration/install-community/" title="MongoDB Community Edition Documentation">MongoDB Community Edition</a> for your OS.  </p>



<p>In my express application I use a number of external modules or dependencies that have to be installed for the application in order for the application to run. In the repository of the <a href="https://github.com/prottlaender/bookingsystem" title="Node-Js Booking-System Code on GitHub Account of Patrick Rottlaender">bookingsystem</a>on my <a href="https://github.com/prottlaender" title="GitHub Account of Patrick Rottlaender">GitHub account</a> you find the <a href="https://github.com/prottlaender/bookingsystem/blob/master/package.json" title="package.json file of Booking System on GitHub Account of Patrick Rottlaender">package.json</a> file which contains all the necessary dependencies. In principle, it is sufficient if you put this <em>package.json</em> file in your application main directory and install all dependencies with <code>npm install</code>. </p>



<p>Alternatively, of course, all modules can also be installed individually with </p>



<p><code>npm install &lt;module&gt; --save</code></p>



<h3 class="wp-block-heading">Session Management</h3>



<p>I discuss in the first part different code snippets in my application main file <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file of Booking System on GitHub Account of Patrick Rottlaender">booking.js</a>. The goal here is that you understand how session management is basically implemented.</p>



<pre class="wp-block-code"><code>// booking.js

// Load express module and create app
const express = require('express');
const app = express();
// Trust the first Proxy
app.set('trust proxy', 1);
// Load HTTP response header security module
const helmet = require('helmet');
// use secure HTTP headers using helmet with every request
app.use(
  helmet({
      frameguard: {
        action: "deny",
      },
      referrerPolicy: {
        policy: "no-referrer",
    },
    })
  );
// Load envy module to manage environment variables
const envy = require('envy');
const env = envy();

// Set environment variables
const port = env.port
const host = env.host
const mongodbpath = env.mongodbpath
const sessionsecret = env.sessionsecret
const sessioncookiename = env.sessioncookiename
const sessioncookiecollection = env.sessioncookiecollection

// Load server side session and cookie module
const session = require('express-session');
// Load mongodb session storage module
const connectMdbSession = require('connect-mongodb-session');
// Create MongoDB session store object
const MongoDBStore = connectMdbSession(session)
// Create new session store in mongodb
const store = new MongoDBStore({
  uri: mongodbpath,
  collection: sessioncookiecollection
});
// Catch errors in case session store creation fails
store.on('error', function(error) {
  console.log(`error store session in session store: ${error.message}`);
});
// Use session to create session and session cookie
app.use(session({
  secret: sessionsecret,
  name: sessioncookiename,
  store: store,
  resave: false,
  saveUninitialized: false,
  // set cookie to 1 week maxAge
  cookie: {
    maxAge: 1000 * 60 * 60 * 24 * 7,
    sameSite: true
  },
}));

... //further code not taken into account at this point
</code></pre>



<p>I create a server application using the <a href="https://www.npmjs.com/package/express" title="Express-Js Web-Application Framework for node-js">Express-js&nbsp;</a> Web Application Framework. Therefore I load the Express-js  module with the <code>require()</code> function and store the <code>express()</code> function in the constant <em>app</em>. Because my app is running behind a reverse proxy server I set the app to trust the first proxy server. Then I load the <a href="https://www.npmjs.com/package/helmet" title="Helmet Package for node-js">helmet module</a> to use secure response headers in my app. I configure that all browsers should deny iFrames and that my app will set no referrer in the response header. </p>



<p>I use the <a href="https://www.npmjs.com/package/envy" title="Envy module for node-js">envy module</a> in my application to manage environment variables. Therefore I load the module with <code>require()</code> and store the <code>envy()</code> function in the constant <em>env</em>. With envy you can define your environment variables in your <em>.env</em> and <em>.env.example</em> files. These files must be stored in the application main directory as explained in the envy documentation. </p>



<p>Since my booking app is a real web application running on a web server in production I can not discuss the real environment variables because of security reasons. Therefore let us see how this work and make an example <em>.env</em> file. </p>



<pre class="wp-block-code"><code>// .env

port=myport
host=myhost
mongodbpath=myexamplemongodbpath
sessionsecret=myexamplesecret
sessioncookiename=booking
sessioncookiecollection=col_sessions

</code></pre>



<p>These variables have different values in my <em>.env</em> file. In the <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file">booking.js</a>file above I define the constant <em>env</em> for the envy function with <code>env = envy()</code> . Then I have access to the environment variables defined in my <em>.env</em> file with <em>env.&lt;variable&gt;</em>. I define constants for each variable and assign the variable from the .env file with <code>env.&lt;variable&gt;</code>. These constants can now be used as values in the code. </p>



<p>I load the <a href="https://www.npmjs.com/package/express-session" title="Node-Js Express-Session Module">express-session</a> module and the <a href="https://www.npmjs.com/package/connect-mongodb-session" title="Node-Js Connect-MongoDB-Session Module">connect-mongodb-session</a> module with the <code>require()</code> function. The session module stored in the constant <em>session</em> takes over the entire control of the session and cookie management. </p>



<p>The <em>connect-mongodb-session</em> stored in the constant <em>connectMdbSession</em> module is basically responsible for storing the session in the database. That is why we pass <em>session</em> as a parameter in the code and assign the constant <em>MongoDBstore</em>.</p>



<p><code>const MongoDBstore = connectMdbSession(session)</code> </p>



<p>With <code>new MongoDBStore</code> I create a new store object. Here I pass the <em>uri</em> of the mongodb path and the <em>collection</em> where sessions should be stored. </p>



<pre class="wp-block-code"><code>// booking.js
...

const store = new MongoDBStore({
  uri: mongodbpath,
  collection: sessioncookiecollection
});

...

</code></pre>



<p>The store object initialized in this way contains all necessary parameters to successfully store a session object in my MongoDB database.</p>



<p>After we have defined the storage of the session object, we take care of the session object itself. </p>



<p>With <code>app.use(session( {... cookie: {...} }))</code> I create a session object with various options. The session object will be created with each request and also contains a cookie object. I pass the values for <code>cookie: {...}</code> and then other options like<code>secret: sessionsecret</code>, the session object name with <code>name: sessioncookiename</code> as well as the location where the session object should be stored with <code>store: store</code>. Furthermore the session object has the option <code>saveUninitialized: false</code> and <code>resave: false</code> . </p>



<p>When the <em>saveUninitialized</em> option is set to <em>false</em> the session object is <em>not stored</em> into the store as long as the session is <strong>un-initialized</strong>. The option <code>resave: false</code> enforce that a session will <em>not be saved back</em> to the store even if the session is <strong>initialized</strong>.  So we must understand what <em>initialized</em> and <em>un-initialized</em> mean. This must be explained.</p>



<p>A browser send a request to the app. More precisely, the browser sends the request to a defined endpoint in the app. An endpoint defines a path within the app that reacts to HTTP requests and executes code. Depending on the HTTP method GET or POST, the endpoint expects that the requestor requires a document back (GET) or that the requestor wants to send data to the app (POST).</p>



<p>In the example below the browser should send a GET request to GET <em>home</em> endpoint. The endpoint render the HTML template <em>index</em> and send the HTML back to the browser. Then the request is finished. So this process, which starts with the <em>request</em> and ends with the <em>response</em> is the <em>runtime of the request</em>. </p>



<p>In the code snippet below you see 2 GET endpoints in the <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file">booking.js</a> file one for the <em>home</em> route and another one for the <em>register</em> route.  </p>



<pre class="wp-block-code"><code>// booking.js

... //further code not taken into account at this point

// GET home route only for anonym users. Authenticated users redirected to dashboard
app.get('/', redirectDashboard, (req, res) =&gt; {
	
  console.log(req.url);
  console.log(req.session.id);
  console.log(req.session);

  res.render('index', {
      title: 'User Login Page',
    });

});

// GET register route only for anonym users. Authenticated users redirected to dashboard
app.get('/register', redirectDashboard, (req, res) =&gt; {
  
  console.log(req.url);
  console.log(req.session.id);
  console.log(req.session);

  res.render('register', {
      title: 'User Registration Page',
    });
});

... //further code not taken into account at this point


</code></pre>



<p>The code with <code>app.use (session({ ... }))</code> in my <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file">booking.js</a> file ensures that a session object is always generated with each request. As long as a session object is not changed during the runtime of a request a separate session is created for each request and has its own session ID. The option <code>saveUninitialized: false</code> ensure that the session object will not be stored into the database. Each session object created in this way is <strong>un-initialized</strong>. </p>



<p>You can see the following output on the console for the <em>home</em> route and for the <em>register</em> route when we log the <em>path</em>, the <em>session-ID</em> and the <em>session object</em> on the console for each route.</p>



<pre class="wp-block-code"><code>/
BmbE8RVoTRcPP9nUnBm5JLE1w1mQiNyt
Session {
  cookie: {
    path: '/',
    _expires: 2021-04-24T04:27:04.265Z,
    originalMaxAge: 604800000,
    httpOnly: true,
    sameSite: true
  }
}

/register
awlPO-KpyVM51Gp6UAoeXGGmRWo-QFtP
Session {
  cookie: {
    path: '/',
    _expires: 2021-04-24T05:54:57.439Z,
    originalMaxAge: 604800000,
    httpOnly: true,
    sameSite: true
  }
}
  
</code></pre>



<p>The code of my app changes a session object during the runtime of a request by adding a data object when a user has successfully logged in. I will explain the code in detail in the next chapter but at the moment it is enough to know that. Therefore we play through the login of a user as follows.</p>



<p>The browser send a GET request to the <em>home</em> route as explained above, then the <em>index</em> template is rendered and the HTML page with the login form is sent back to the browser. During the runtime of this GET request a session object is created but the session object has not changed. We have already seen this above.</p>



<p>Then the user enters <em>email</em> and <em>password</em> in the login form and click submit. With this submit the browser send a POST request to the POST endpoint <em>/loginusers</em> and again a session object is generated for this POST request. During the runtime of the POST request, the code checks whether the transferred credentials are correct. If the credentials are correct, a data object with user data is generated and attached to the session object. Here the session object is changed during the runtime of the POST request. The existing session created with the POST request is now <strong>initialized</strong> at that moment. Because of the option <code>saveUninitialized: false</code> this session object is stored into the database store. When we look into the database store using the tool <a href="https://www.mongodb.com/products/compass" title="MongoDB Compass Management Console ">MongoDB Compass</a> we see that the entire session object has been saved into the <em>col<em>sessions</em></em> collection including the data object containing the required data of the user.</p>


<div class="wp-block-image">
<figure class="aligncenter"><img fetchpriority="high" decoding="async" width="2460" height="1126" src="https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object.png" alt="Session Object saved in the *col_sessions* " class="wp-image-136" srcset="https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object.png 2460w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-300x137.png 300w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1024x469.png 1024w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-768x352.png 768w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1536x703.png 1536w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-2048x937.png 2048w" sizes="(max-width: 2460px) 100vw, 2460px" /><figcaption>Session Object saved in the *col_sessions* </figcaption></figure>
</div>


<p>After the session initialization the code called by the POST endpoint redirect the request and send a new GET request to the <em>/dashboard</em> route. The code with <code>app.use(session({ ... }))</code>  is called again but now there is an initialized session existing in the store. Because of the option <code>resave: false</code> the existing session object will not be updated and dragged along unchanged with every further request.</p>



<p>You see this in the output on the console when we log the <em>path</em>, the <em>session-ID</em> and the <em>session object</em> on the console for each route. The first output on the console is created when the GET request is sent to the <em>home</em> route. Then, the second output, after the user clicked submit the POST route <em>/loginusers</em> is called and a new session object is created. You see this from the different session IDs. During the runtime of this POST request the data object is added to the session object which initializes the session. Then, the third output, the GET route <em>/dashboard</em> is called and we see the same session object ID but the session object now contain the data object with the user data.</p>



<pre class="wp-block-code"><code>/
TEAZITdX7nLWBDc8uOk2HhXIiMZO7W-4
Session {
  cookie: {
    path: '/',
    _expires: 2021-05-02T07:21:13.236Z,
    originalMaxAge: 604800000,
    httpOnly: true,
    sameSite: true
  }
}

/loginusers
gVlKut3bdEMiDHnK455FGjCi6YbPTBuZ
Session {
  cookie: {
    path: '/',
    _expires: 2021-05-02T07:21:35.202Z,
    originalMaxAge: 604800000,
    httpOnly: true,
    sameSite: true
  }
}

/dashboard
gVlKut3bdEMiDHnK455FGjCi6YbPTBuZ
Session {
  cookie: {
    path: '/',
    _expires: 2021-05-02T07:21:35.468Z,
    originalMaxAge: 604800000,
    httpOnly: true,
    secure: null,
    domain: null,
    sameSite: true
  },
  data: {
    userId: 5f716b7439777365c18639f1,
    status: 'active',
    name: 'Oskar David',
    lastname: 'Rottländer',
    email: 'oskar@test.com',
    role: 'player',
    age: 17,
    cat: 'youth'
  }
}

</code></pre>



<p>In summary, session management works as follows: A session object will be created with each request and the session object is only saved in the database when the user is logged in (<em>saveUninitialized: false</em>). As long as the user is logged in, the session object is not changed and the data of the session object in the database are not updated (<em>resave: false</em>).</p>



<p><strong>But what happens to the cookie ?</strong> This will be explained in the next chapter.</p>



<h3 class="wp-block-heading">User login</h3>



<p>When the session has been initialized the cookie containing the session ID is stored in the browser of the requestor. With every request the browser provide the cookie to authenticate the requestor. To authenticate the requestor the code <code>app.use(session({...}))</code>  is called and compare the session ID sent by the browser with the session IDs stored in the session store. If a session ID matches, the session object including the data object is attached to the request object to give the app access to the data object. Within the app we now have access to any attribute of the data object with <em>req.session.data.&lt;attribute&gt;</em>. Therefore we can now implement role based authorization by accessing the role of the requestor with <em>req.session.data.role</em> and use this information in conditions in the code to control access depending on the role of the requestor. </p>



<p>But lets start from the beginning with the login of the requestor or user as I call the requestor from now on. In order for a user to be able to login, he or she must first call the <strong>login page</strong> which can be displayed by calling up the home endpoint. </p>



<pre class="wp-block-code"><code>// booking.js

... // Code not discussed here

// Redirect GET requests from authenticated users to dashboard
const redirectDashboard = (req, res, next) =&gt; {
  if (req.session.data) {
    res.redirect('/dashboard')

  } else {
    next()

  }
}

... // Code not discussed here

// GET home route only for anonym users. Authenticated users redirected to dashboard

app.get('/', redirectDashboard, (req, res) =&gt; {

  res.render('index', {
      title: 'User Login Page',
    });

});

... // Code not discussed here

</code></pre>



<p>As you see above in the code I have first defined the middleware function <em>redirectDashboard</em>. This middleware ensure that only users who are not logged in see the login page. If we look at the code of the middleware function we can see that <em>req.session.data</em> is used in an if-condition to check whether a data object is attached to the current session object. In case the <strong>if-condition is true</strong>, the user is logged in and the request is redirected to the dashboard, but in case the <strong>if-condition is false</strong>, the user is not logged in and the <em>next()</em> function is called. </p>



<p>The GET endpoint has the <em>routingPath</em> to the home route. When a user visits the homepage of my booking application, the GET HTTP request ask for the home <em>routingPath</em>. The middleware function <em>redirectDashboard</em> is put in front of the <em>routingHandler</em> function. If the user is not logged in the <em>routingHandler</em> function render the HTML template <a href="https://github.com/prottlaender/bookingsystem/blob/master/views/index.pug" title="index.pug file">index.pug</a>and send the HTML back to the user or more precisely to the user&#8217;s browser. </p>



<p>So far so good. We now imagine a not logged in user who sees the index page in front of him or her now wants to login using his or her <em>email</em> and <em>password</em>.</p>



<p>As described above, the index page is nothing more than a login form for entering an email address and a password. When we look at the <a href="https://github.com/prottlaender/bookingsystem/blob/master/views/index.pug" title="index.pug file">index.pug</a> file we see that the form action attribute define that the form data <code>email</code> and <code>password</code> will be sent to the form handler <code>/loginusers</code> using the POST method when the Submit button is clicked.</p>



<pre class="wp-block-code"><code>...

form#loginForm.col.s12(
		method='post', 
		action='/loginusers'
		)

		input.validate(
			type='email', 
			name='email', 
			autocomplete='username' 
			required
			)
		...

		input.validate(
			type='password', 
			name='password', 
			autocomplete='current-password' 
			required
			)
		...

button.btn.waves-effect.waves-light(
		type='submit', 
		form='loginForm'
		)
...

</code></pre>



<p><strong>Note</strong>: To understand the <em>autocomplete</em> attributes of the input tags I recommend reading the documentation of the <a href="https://www.chromium.org/developers/design-documents/form-styles-that-chromium-understands" title="The Chromium Project">Chromium Project</a>. Most browsers have password management functionalities and automatically fill in the credentials after you provide a master password to unlock your local password store. By using these autocomplete attributes in login forms but also in user registration forms or change password forms you help browsers by using these <em>autocomplete</em> functions to better identify these forms.</p>



<p>When the user has entered his or her <em>email</em> and <em>password</em> in the HTML form and clicked the Submit button, the <strong>request body</strong> contain the <em>Form Data</em> attributes <em>email</em> and <em>password</em>. Then a POST HTTP request is sent via HTTPS to the POST endpoint <code>/loginusers</code> defined in my <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file">booking.js</a> file (see above). </p>



<p>In the picture below you can see the output of the network analysis in the developer tool of the chrome browser. Here you can see that the <em>Form Data</em> are not encrypted on <strong>browser side</strong> but you also see that the POST request URL <code>/loginusers</code> is HTTPS. This mean that when the browser sent the POST request to the server these data are encrypted with SSL/TLS in transit from the browser to the server.</p>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" width="2002" height="612" src="https://digitaldocblog.com/wp-content/uploads/2022/08/050-POST-route-request-with-form-data.png" alt="*Form Data* not encrypted on browser side" class="wp-image-137" srcset="https://digitaldocblog.com/wp-content/uploads/2022/08/050-POST-route-request-with-form-data.png 2002w, https://digitaldocblog.com/wp-content/uploads/2022/08/050-POST-route-request-with-form-data-300x92.png 300w, https://digitaldocblog.com/wp-content/uploads/2022/08/050-POST-route-request-with-form-data-1024x313.png 1024w, https://digitaldocblog.com/wp-content/uploads/2022/08/050-POST-route-request-with-form-data-768x235.png 768w, https://digitaldocblog.com/wp-content/uploads/2022/08/050-POST-route-request-with-form-data-1536x470.png 1536w" sizes="(max-width: 2002px) 100vw, 2002px" /><figcaption>*Form Data* not encrypted on browser side</figcaption></figure>
</div>


<p>On the <strong>server side</strong> we have the web application behind a proxy server listening to HTTP requests addressed to the POST endpoint <code>/loginusers</code>.  This POST endpoint is an anonym POST Route which means that the <em>routingHandler</em> controller function is restricted to not logged-in users only. This makes sense because a login function must not be used by already logged in users. So already logged in users can not send data to this POST endpoint. This check is controlled by the middleware function <em>verifyAnonym</em> which is put in front of the <em>routingHandler</em>.  </p>



<p>So lets look at the relevant code snippets in <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file">booking.js</a>.</p>



<pre class="wp-block-code"><code>// booking.js

...

// Load db controllers and db models
const userController = require('./database/controllers/userC');

...

// Verify POST requests only for anonym users
const verifyAnonym = (req, res, next) =&gt; {

  if (req.session.data) {
    var message = 'You are not authorized to perform this request because you are already logged-in !';
    res.status(400).redirect('/400badRequest?message='+message);

  } else {
    next()

  }
}

...

// Anonym POST Route
// Login user available for anonym only
app.post('/loginusers', verifyAnonym, userController.loginUser)

...

// GET bad request route render 400badRequest
app.get('/400badRequest', (req, res) =&gt; {
 
  res.status(400).render('400badRequest', {
    title: 'Bad Request',
    code: 400,
    status: 'Bad Request',
    message: req.query.message,
  })
})

...

</code></pre>



<p>At the beginning of the code I refer the constant <em>userController</em> to the user controller file <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/controllers/userC.js" title="userC.js file">userC.js</a> using the <code>require</code> method. In <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/controllers/userC.js" title="userC.js file">userC.js</a> all user functions are defined to control user related operations.</p>



<p><strong>Note</strong>: When you look into the <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/controllers/userC.js" title="userC.js file">userC.js</a> file you see that we export modules using <code>module.exports = {...}</code>. Using this directive we export in fact an object with various attributes and the values of these attributes are functions. So with  <code>module.exports = { loginUser: function(...) ...}</code> we export the object including the attribute <em>loginUser</em> which contains a function as value. So when we refer the constant  <em>userController</em> using the <code>require()</code> function in the <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file">booking.js</a> file we store the complete exported object with all its attributes to the <em>userController</em> constant. Now we have access to any attribute of the exported object from <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/controllers/userC.js" title="userC.js file">userC.js</a> file with <em>userController.&lt;attribute&gt;</em>. Because the attributes are in fact functions we call these functions with this statement.  </p>



<p>In the <em>verifyAnonym</em> function <em>req.session.data</em> is used in the if-condition to check whether a data object is attached to the current session object. In case the <strong>if-condition is true</strong>, the user is already logged-in and is redirected to the Bad Request GET endpoint <code>/400badRequest</code> which is the standard route in my application to show the user that something went wrong. The user can see what went wrong from a message that has been attached to the request using the request parameter <code>?message=+message</code>. In case <strong>the if-condition is false</strong>, the user is not logged-in and the <em>next()</em> function forwards the request to the <em>routingHandler</em> controller function that call the <code>loginUser</code> function using <em>userController.loginUser</em>.  This function has access to the attributes <em>email</em> and <em>password</em> of the <strong>request body</strong> with <em>req.body.email</em> and <em>req.body.password</em>. </p>



<p>So lets look at the relevant code snippets in <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/controllers/userC.js" title="userC.js file">userC.js</a> file.</p>



<pre class="wp-block-code"><code>// database/controllers/userC.js

// load the bcryptjs module
const bcrypt = require('bcryptjs');
// define hash saltrounds for password hashing
const saltRounds = 10;
// load the relevant Prototype Objects (exported from the models)
...

const User = require('../models/userM');

...

loginUser: function (req, res) {

    const inputemail = req.body.email
    const email = inputemail.toLowerCase()

    console.log(req.url);
    console.log(req.session.id);
    console.log(req.session);

    try {

      User.findOne({ email: email }, async function(error, user) {
        if (!user) {
          var message = 'User not found. Login not possible';
          res.status(400).redirect('/400badRequest?message='+message);

        } else {
          if (user._status !== 'active') {
            var message = 'Login not possible. Await User to be activated';
            res.status(400).redirect('/400badRequest?message='+message);

          } else {
              if (bcrypt.compareSync(req.body.password, user.password)) {

                var yearInMs = 3.15576e+10;
                var currentDate = new Date ()
                var currentDateMs = currentDate.getTime()
                var birthDateMs = user.birthdate.getTime()
                var age = Math.floor((currentDateMs - birthDateMs) / yearInMs)

                if (age &lt; 18) {
                  var cat = 'youth'
                } else {
                  var cat = 'adult'
                };

                var userData = {
                  userId: user._id,
                  status: user._status,
                  name: user.name,
                  lastname: user.lastname,
                  email: user.email,
                  role: user.role,
                  age: age,
                  cat: cat,
                }

                req.session.data = userData

                res.status(200).redirect('/dashboard')

              } else {
                var message = 'Login not possible. Wrong User password';
                res.status(400).redirect('/400badRequest?message='+message);
              }
          }
        }
      })

    } catch (error) {
      // if user query fail call default error function
      next(error)

    }
  // End Module
  },

...

</code></pre>



<p>In order to authenticate a user, the <em>loginUser</em> function must find a user in the user database with the same email address as the one that was sent by the browser and attached to the request body by the app. If a user was found with the email, the function must check whether the transmitted password matches the password that is stored in the database for this user. If the email and password match the user is authenticated and the login is successful, if not, the login fails. </p>



<p>Passwords are never saved in plain text. Therefore I use the <a href="https://www.npmjs.com/package/bcryptjs" title="Bcrypt-Js module for node-js">bcryptjs module</a> to hash passwords. The bcryptjs module is loaded into the code with the <code>require()</code> function and assigned to the constant <em>bcrypt</em>. We set the constant <em>saltRounds</em> to the value of 10. This is the so called cost factor in the bcrypt hashing function and controls how much time bcrypt need to calculate a single bcrypt hash. Increasing the cost factor by 1 doubles the time and the more time bycrypt need to hash the more difficult it is to brute force stored passwords.</p>



<p>Then I load the user model from <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/models/userM.js" title="userM.js file">userM.js</a> using the <code>require()</code> function and assign the constant <em>User</em>. Here at this point I have to explain the background. To do this, we also need a look at the <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/models/userM.js" title="userM.js file">userM.js</a> file. </p>



<p><strong>Note</strong>: I use MongoDB as the database and Mongoose to model the data. If you look in the file <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/models/userM.js" title="userM.js file">userM.js</a> you see that a user object is created with the function <em>new Schema()</em> and saved in the variable <em>userSchema</em>. This <em>userSchema</em> object describes a user with all its attributes. At the end of the file, the <em>mongoose.model()</em> function is used to reference the <em>userSchema</em> to the collection <em>col<em>users</em></em> in my MongoDB. This reference is assigned to the variable <em>User</em> and exported using the function <em>module.exports()</em>. With <em>User</em> I have access to the user model meaning to all user objects and attributes in my database that are stored in the <em>col<em>users</em></em> collection. So that I can use this access in the code of my <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/controllers/userC.js" title="userC.js file">userC.js</a> file I load <a href="https://github.com/prottlaender/bookingsystem/blob/master/database/models/userM.js" title="userM.js file">userM.js</a> with the <code>require()</code> function and assign the constant <em>User</em>. I can now use Mongoose functions for example to query user data from my <em>col<em>users</em></em> collection. This is exactly what we do with <em>User.findOne()</em> when we try to find a user with a certain email.</p>



<p>The actual <strong>user authentication</strong> now takes place in the <em>userFindOne()</em> function. </p>



<p>When we run <em>User.findOne()</em> we check the criteria that do not lead to a successful authentication. </p>



<ol class="wp-block-list"><li><strong>No user found</strong>: We are looking for a user object that matches the email that has been submitted. If no user object is found with that email or the user found is not active, the request is redirected to the 400badRequest route. If we have found an active user, the submitted password string is hashed with bcrypt and compared with the saved password. </li><li><strong>Wrong password</strong>: If the password comparison is not successful, the submitted password was wrong and the request is also redirected to the 400badRequest route. <br></li></ol>



<p><strong>Note</strong>: <em>User.findOne()</em> has a query object <code>{email: email}</code> and a callback function <code>async function(error, user {...})</code> as parameters. When the async function find a user with the email in the database, this async function returns a user object with all the user attributes and store this object into the <em>user</em> parameter. Within the scope of the async function I have now access to the user attributes using <em>user.&lt;attribute&gt;</em>.</p>



<p>Only in case the user with the email is found and the submitted password is correct the authentication is successful.</p>



<p>If the user is successfully authenticated, the category of the user is calculated based on the current date and the user&#8217;s birth date. Then a <em>userData</em> object is created in which various user attributes are stored. The data of the <em>userData</em> object are then attached to the session. More precisely, the object <em>data</em> is attached to the session with <em>req.session.data</em> and the value <em>userData</em> is assigned. Now the session is initialized and the session object is stored in the <em>col<em>sessions</em></em> collection of the MongoDB.</p>


<div class="wp-block-image">
<figure class="aligncenter"><img decoding="async" width="2460" height="1126" src="https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1.png" alt="Initialized session and session object stored in the col_sessions" class="wp-image-135" srcset="https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1.png 2460w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1-300x137.png 300w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1-1024x469.png 1024w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1-768x352.png 768w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1-1536x703.png 1536w, https://digitaldocblog.com/wp-content/uploads/2022/08/070-DB-session-object-1-2048x937.png 2048w" sizes="(max-width: 2460px) 100vw, 2460px" /><figcaption>Initialized session and session object stored in the col_sessions</figcaption></figure>
</div>


<p>Then the <strong>response</strong> is sent back to the browser. </p>



<p>In this response, the browser is instructed to call up a GET request to the GET endpoint <code>/dashboard</code>. The response is sent using <code>res.status(200).redirect('/dashboard')</code>. In the Response Header you see that the cookie with the name <em>booking</em> is set in the users browser using the <code>set-cookie</code> directive. The cookie only contain the session ID which has been signed and encrypted with the <em>secret</em> we provided in  <code>app.use(session( {... cookie: {...} }))</code>. </p>


<div class="wp-block-image">
<figure class="aligncenter"><img loading="lazy" decoding="async" width="1759" height="557" src="https://digitaldocblog.com/wp-content/uploads/2022/08/060-POST-route-request-with-form-data-set-cookie.png" alt="Response Header with cookie named *booking*" class="wp-image-138" srcset="https://digitaldocblog.com/wp-content/uploads/2022/08/060-POST-route-request-with-form-data-set-cookie.png 1759w, https://digitaldocblog.com/wp-content/uploads/2022/08/060-POST-route-request-with-form-data-set-cookie-300x95.png 300w, https://digitaldocblog.com/wp-content/uploads/2022/08/060-POST-route-request-with-form-data-set-cookie-1024x324.png 1024w, https://digitaldocblog.com/wp-content/uploads/2022/08/060-POST-route-request-with-form-data-set-cookie-768x243.png 768w, https://digitaldocblog.com/wp-content/uploads/2022/08/060-POST-route-request-with-form-data-set-cookie-1536x486.png 1536w" sizes="auto, (max-width: 1759px) 100vw, 1759px" /><figcaption>Response Header with cookie named *booking*</figcaption></figure>
</div>


<p>Then the browser send the GET request to the endpoint <code>/dashboard</code>. Lets have a look into the <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file">booking.js</a> file again.</p>



<pre class="wp-block-code"><code>// booking.js 

...

// Redirect GET requests from not authenticated users to login
const redirectLogin = (req, res, next) =&gt; {
  if (!req.session.data) {
    res.redirect('/')

  } else {
    next()

  }
}

...

// GET dashboard route only for authenticated users. Anonym users redirected to home
app.get('/dashboard', redirectLogin, async (req, res) =&gt; {

  // Check admin authorization and render admin_dashboard
  if (req.session.data.role == 'admin') {

    const user_query = User.find( {} ).sort({lastname: 1, name: 1});
    var users = await user_query.exec();

    const training_query = Training.find( {} ).sort({date: 'desc'});
    var trainings = await training_query.exec();

    const location_query = Location.find( {} ).sort({location: 'desc'});
    var locations = await location_query.exec();

    const booking_query = Booking.find( {} ).sort({_booktrainingdate: 'desc'});
    var bookings = await booking_query.exec();

    const invoice_query = Invoice.find( {} ).sort({invoicedate: 'desc'});
    var invoices = await invoice_query.exec();

    res.status(200).render('admin_dashboard', {
      title: 'Admin Dashboard Page',
      name: req.session.data.name,
      lastname: req.session.data.lastname,
      role: req.session.data.role,
      data_users: users,
      data_trainings: trainings,
      data_locations: locations,
      data_bookings: bookings,
      data_invoices: invoices,

      });

  // Check player authorization and render player_dashboard
  } else if (req.session.data.role == 'player') {

    var currentDate = new Date();
    console.log('current date: ' +currentDate);

    const availabletraining_query = Training.find( { _status: 'active', date: { $gte: currentDate } } ).sort({ date: 'desc' });
    var availabletrainings = await availabletraining_query.exec();

    const booking_query = Booking.find( { _bookuseremail: req.session.data.email, _bookparticipation: { $ne: 'invoice' } } ).sort({ _booktrainingdate: 'desc' });
    var bookings = await booking_query.exec();

    const myuser_query = User.findOne( { email: req.session.data.email } );
    var myuser = await myuser_query.exec();

    const invoice_query = Invoice.find( {invoiceemail: req.session.data.email} ).sort({invoicedate: 'desc'});
    var invoices = await invoice_query .exec();

    res.status(200).render('player_dashboard', {
      title: 'Player Dashboard Page',
      name: req.session.data.name,
      lastname: req.session.data.lastname,
      role: req.session.data.role,
      email: req.session.data.email,
      data_availabletrainings: availabletrainings,
      data_bookings: bookings,
      data_myuser: myuser,
      data_myinvoices: invoices,
      });

  // Check coach authorization and render coach_dashboard
  } else if (req.session.data.role == 'coach') {
   
    var currentDate = new Date().setHours(00, 00, 00);
    console.log('currentDate: ' +currentDate);

    const training_query = Training.find( { _status: 'active', date: { $gte: currentDate } } ).sort({ date: 'asc' });
    var trainings = await training_query.exec();

    res.status(200).render('coach_dashboard', {
      title: 'Coach Dashboard Page',
      name: req.session.data.name,
      lastname: req.session.data.lastname,
      role: req.session.data.role,
      data_trainings: trainings,
      });

  } else {
    // if user not authorized as admin, player or coach end request and send response
    var message = 'You are not authorized. Access prohibited';
    res.status(400).redirect('/400badRequest?message='+message);
  }

});

...

</code></pre>



<p>As you see above in the code we have first defined the middleware function <em>redirectLogin</em>. This middleware ensure that only users who are logged in see the dashboard page. In case the <strong>if-condition is true</strong>, the user is not logged in and the request is redirected to the home route, but in case the <strong>if-condition is false</strong>, the user is logged in and the <em>next()</em> function is called. </p>



<p>The GET HTTP request ask for the dashboard <em>routingPath</em>. The middleware function <em>&nbsp;redirectLogin</em> is put in front of the <em>routingHandler</em> function. If the user is not logged in the <em>&nbsp;redirectLogin</em> middleware redirect the request to the home route. In case the user is logged-in the <em>routingHandler</em> function is called using the request object and the response object as parameters.</p>



<pre class="wp-block-code"><code>app.get('/dashboard', redirectLogin, async (req, res) =&gt; {...})
</code></pre>



<p>If we look at the Request Header of this new GET request in the browser we can see that the cookie is dragged along unchanged with the GET request to the endpoint <code>/dashboard</code>. This happens from now on with every request until the session expires or until the user logout. </p>


<div class="wp-block-image">
<figure class="aligncenter"><img loading="lazy" decoding="async" width="1751" height="590" src="https://digitaldocblog.com/wp-content/uploads/2022/08/080-GET-route-request-dashboard-with-cookie.png" alt="Cookie dragged along with GET request " class="wp-image-139" srcset="https://digitaldocblog.com/wp-content/uploads/2022/08/080-GET-route-request-dashboard-with-cookie.png 1751w, https://digitaldocblog.com/wp-content/uploads/2022/08/080-GET-route-request-dashboard-with-cookie-300x101.png 300w, https://digitaldocblog.com/wp-content/uploads/2022/08/080-GET-route-request-dashboard-with-cookie-1024x345.png 1024w, https://digitaldocblog.com/wp-content/uploads/2022/08/080-GET-route-request-dashboard-with-cookie-768x259.png 768w, https://digitaldocblog.com/wp-content/uploads/2022/08/080-GET-route-request-dashboard-with-cookie-1536x518.png 1536w" sizes="auto, (max-width: 1751px) 100vw, 1751px" /><figcaption>Cookie dragged along with GET request </figcaption></figure>
</div>


<p>And now within the <em>routingHandler</em> function we do the <strong>user authorization</strong> check. The if condition check the users role using <em>req.session.data.role</em>. Depending on the role of the user different <em>&lt;role&gt;<em>dashboard</em></em> HTML templates are rendered and for each role different HTML is sent back to the user&#8217;s browser. Various queries are executed beforehand because we need role specific data within each <em>&lt;role&gt;<em>dashboard</em></em> HTML template. The return values ​​of the different queries <code>find()</code> and <code>findOne()</code> are only executed in case one of the if conditions become true. Then in each case the return values of the queries are stored in variables. In case all if conditions are false, meaning we cannot find a user with a role like <em>admin</em>, <em>player</em> or <em>coach</em> in the database for some reason the request is redirected to the Bad Request GET endpoint <code>/400badRequest</code> using the message as request parameter that this user is not authorized.</p>



<p>Within each if condition and for each role <em>admin</em>, <em>player</em> or <em>coach</em>, we create the response object by first setting the HTTP status to the value of 200 and then using the render method to render the respective HTML template.</p>



<pre class="wp-block-code"><code>res.status(200).render('&lt;role&gt;_dashboard', {...})
</code></pre>



<p>Within the render method, we now have the option of transferring a data object with different attributes to the HTML template. Later we can access these data in the respective HTML template and use it within the HTML template. How this works is not part of this article. But of course you can take a closer look at the templates <a href="https://github.com/prottlaender/bookingsystem/blob/master/views/admin_dashboard.pug" title="admin_dashboard.pug file">admin dashboard</a>, <a href="https://github.com/prottlaender/bookingsystem/blob/master/views/player_dashboard.pug" title="player_dashboard.pug file">player dashboard</a>and <a href="https://github.com/prottlaender/bookingsystem/blob/master/views/coach_dashboard.pug" title="coach_dashboard.pug file">coach dashboard</a>on my <a href="https://github.com/prottlaender" title="GitHub Account of Patrick Rottlaender">GitHub repository</a> and you will immediately see how this works.</p>



<h3 class="wp-block-heading">Create Authorizations</h3>



<p>As I have already shown in the upper part, I work with middleware functions to control access to GET and POST endpoints in my app. Therefore these middleware functions are the Authorizations and you can find them in the code of my <a href="https://github.com/prottlaender/bookingsystem/blob/master/booking.js" title="booking.js file">booking.js</a> file.</p>



<pre class="wp-block-code"><code>// booking.js

...

// Authorizations
// Redirect GET requests from not authenticated users to login
const redirectLogin = (req, res, next) =&gt; {
  if (!req.session.data) {
    res.redirect('/')

  } else {
    next()

  }
}

// Redirect GET requests from authenticated users to dashboard
const redirectDashboard = (req, res, next) =&gt; {
  if (req.session.data) {
    res.redirect('/dashboard')

  } else {
    next()

  }
}

// Authorize POST requests only for not authenticated users
const verifyAnonym = (req, res, next) =&gt; {
  if (!req.session.data) {
    next()

  } else {
    var message = 'You are already logged-in. You are not authorized to perform this request !';
    res.status(400).redirect('/400badRequest?message='+message);

  }
}

// Authorize POST requests only for anonym and admin users
const verifyAnonymAndAdmin = (req, res, next) =&gt; {
  if (!req.session.data) {
    next()

  } else {

    if (req.session.data.role == 'admin') {
      next()

    } else {
      var message = 'You are no Admin. You are not authorized to perform this request !';
      res.status(400).redirect('/400badRequest?message='+message);

    }

  }
}

// Authorize POST requests only for admin and player users
const verifyAdminAndPlayer = (req, res, next) =&gt; {
  if (req.session.data) {
    if (req.session.data.role == 'admin') {
      next()

    } else if (req.session.data.role == 'player') {
      next()

    } else {
      var message = 'You are no Admin, no Player. You are not authorized to perform this request !';
      res.status(400).redirect('/400badRequest?message='+message);
    }

  } else {
    var message = 'You are not logged-in. You are not authorized to perform this request !';
    res.status(400).redirect('/400badRequest?message='+message);
  }

}

// Authorize POST requests only for admin users
const verifyAdmin = (req, res, next) =&gt; {
  if (req.session.data) {
    if (req.session.data.role == 'admin') {
      next()

    } else {
      var message = 'You are no Admin. You are not authorized to perform this request !';
      res.status(400).redirect('/400badRequest?message='+message);
    }

  } else {
    var message = 'You are not logged-in. You are not authorized to perform this request !';
    res.status(400).redirect('/400badRequest?message='+message);

  }
}

// Authorize POST requests only for player users
const verifyPlayer = (req, res, next) =&gt; {
  if (req.session.data) {
    if (req.session.data.role == 'player') {
      next()

    } else {
      var message = 'You are no Player. You are not authorized to perform this request !';
      res.status(400).redirect('/400badRequest?message='+message);
    }

  } else {
    var message = 'You are not logged-in. You are not authorized to perform this request !';
    res.status(400).redirect('/400badRequest?message='+message);

  }
}

// Authorize POST requests only for coach users
const verifyCoach = (req, res, next) =&gt; {
  if (req.session.data) {
    if (req.session.data.role == 'coach') {
      next()

    } else {
      var message = 'You are no Coach. You are not authorized to perform this request !';
      res.status(400).redirect('/400badRequest?message='+message);

    }

  } else {
    var message = 'You are not logged-in. You are not authorized to perform this request !';
    res.status(400).redirect('/400badRequest?message='+message);

  }
}

...

</code></pre>



<p>As I have already explained above I use <strong>redirect functions</strong> as a middleware to control access to the <strong>GET endpoints</strong> <em>home</em>, <em>register</em> and <em>dashboard</em>. These middleware functions basically control access based on whether a user is logged-in or not. The redirect function <em>redirectDashboard</em> allow only not logged-in users access to the <em>home</em> endpoint and to the <em>register</em> endpoint, while users who are already logged-in have no access and would be redirected directly to the <em>dashboard</em> route if they try to access here. The <em>redirectLogin</em> middleware function allow only logged-in users access to the <em>dashboard</em> route while not logged-in users are redirected to the login or better to the <em>home</em> endpoint.</p>



<p>In addition to  <em>redirect functions</em> I use <strong>verify functions</strong> as a middleware to control access to the <strong>POST endpoints</strong>. With the help of POST requests, data are sent via POST endpoints to the app. That is why it is particularly important to control who is allowed to send data and who is not. I use basically 5 types of POST endpoints.</p>



<p><strong>Anonym POST endpoint</strong>. I only have one endpoint here. The <em>loginusers</em> endpoint can only be called by not logged-in users. Therefore the <em>verifyAnonym&nbsp;</em> middleware is set before the <em>routingHandler</em> function to verify if the user is not logged-in. </p>



<pre class="wp-block-code"><code>// booking.js
...

// Anonym POST endpoint
// Login user available for anonym only
app.post('/loginusers', verifyAnonym, userController.loginUser)

...

</code></pre>



<p><strong>Shared POST endpoints</strong>. The <em>createusers</em> endpoint can be called by not logged-in users and Admin users. The <em>&nbsp;verifyAnonymAndAdmin&nbsp;</em> middleware is set before the <em>routingHandler</em> function to verify if the user is not logged-in or if the user that is logged-in has the role <em>admin</em>.  The <em>&nbsp;updateuseremail</em> and <em>setnewuserpassword</em> endpoints can be called only by Admin and Player users. Therefore the <em>&nbsp;verifyAdminAndPlayer&nbsp;</em> middleware is set before the <em>routingHandler</em> function to verify if the user is logged-in and if the users role is <em>admin</em> or <em>player</em>. </p>



<pre class="wp-block-code"><code>// booking.js
...

// Shared POST endpoints
// Create Users available for anonym and admin
app.post('/createusers', verifyAnonymAndAdmin, birthdateFormatValidation, userController.createUser)

// Update User-Email available for admin and player
app.post('/updateuseremail', verifyAdminAndPlayer, userController.updateUserEmail)

// Update User-Password available for admin and player
app.post('/setnewuserpassword', verifyAdminAndPlayer, userController.setNewUserPassword)

...

</code></pre>



<p><strong>Admin POST endpoints</strong>.  I have 19 endpoints here and each of these endpoint can only be called by Admin users. The <em>&nbsp;verifyAdmin</em> middleware is set before the <em>routingHandler</em> function to verify if the user is logged-in and if the users role is <em>admin</em>. </p>



<pre class="wp-block-code"><code>// booking.js
...

// Admin POST endpoints
// Admin User Management
app.post('/callupdateusers', verifyAdmin, userController.callUpdateUsers)

app.post('/updateuser', verifyAdmin, birthdateFormatValidation, userController.updateUser)

app.post('/terminateusers', verifyAdmin, userController.terminateUser)

app.post('/activateusers', verifyAdmin, userController.activateUser)

app.post('/removeusers', verifyAdmin, userController.removeUser)

// Admin Update Training
app.post('/callupdatetrainings', verifyAdmin, trainingController.callUpdateTrainings)

app.post('/updatetraining', verifyAdmin, trainingController.updateTraining)

// Admin Location Management
app.post('/createlocations', verifyAdmin, locationController.createLocation)

app.post('/callupdatelocations', verifyAdmin, locationController.callUpdateLocations)

app.post('/updatelocation', verifyAdmin, locationController.updateLocation)

app.post('/callcreatetrainings', verifyAdmin, trainingController.callCreateTrainings)

app.post('/createtraining', verifyAdmin, trainingController.createTraining)

// Admin Invoice Management
app.post('/createinvoice', verifyAdmin, invoiceController.createInvoiceUser)

app.post('/callcancelinvoice', verifyAdmin, invoiceController.callCancelInvoice)

app.post('/cancelinvoice', verifyAdmin, invoiceController.cancelInvoice)

app.post('/callpayinvoice', verifyAdmin, invoiceController.callPayInvoice)

app.post('/payinvoice', verifyAdmin, invoiceController.payInvoice)

app.post('/callrepayinvoice', verifyAdmin, invoiceController.callRePayInvoice)

app.post('/repayinvoice', verifyAdmin, invoiceController.rePayInvoice)

...

</code></pre>



<p><strong>Player POST endpoints</strong>.  I have 7 endpoints here and each of these endpoint can only be called by Player users. The <em>&nbsp;verifyPlayer</em> middleware is set before the <em>routingHandler</em> function to verify if the user is logged-in and if the users role is <em>player</em>. </p>



<pre class="wp-block-code"><code>// booking.js
...

// Player POST endpoints
// Player Booking Management
app.post('/callbooktrainings', verifyPlayer, bookingController.callBookTrainings)

app.post('/booktrainings', verifyPlayer, bookingController.bookTraining)

app.post('/bookingreactivate', verifyPlayer, bookingController.bookingReactivate)

app.post('/callcancelbookings', verifyPlayer, bookingController.callCancelBooking)

app.post('/cancelbookings', verifyPlayer, bookingController.cancelBooking)

// Player User Management
app.post('/callupdatemyuserdata', verifyPlayer, userController.callUpdateMyUserData)

app.post('/updatemyuserdata', verifyPlayer, birthdateFormatValidation, userController.updateMyUserData)

...

</code></pre>



<p><strong>Coach POST endpoints</strong>.  I have 2 endpoints here and each of these endpoint can only be called by Coach users. The <em>&nbsp;verifyCoach</em> middleware is set before the <em>routingHandler</em> function to verify if the user is logged-in and if the users role is <em>coach</em>. </p>



<pre class="wp-block-code"><code>// booking.js
...

// Coach POST endpoints
// Coach Confirmation Management
app.post('/callparticipants', verifyCoach, bookingController.callParticipants)

app.post('/callconfirmpatricipants', verifyCoach, bookingController.callConfirmPatricipants)

...

</code></pre>



<h3 class="wp-block-heading">User logout</h3>



<p>The user initiates a <strong>logout</strong> himself by clicking on the logout link in the navigation of the application. This sends a GET request to the <code>/logout</code> endpoint of the application. In this routing definition, the session is first deleted from the database using <code>req.session.destroy()</code> and then the cookie is removed from the browser and the user is redirected to the <em>200success site</em> using <code>res.status(200).clearCookie('booking').redirect()</code>.</p>



<pre class="wp-block-code"><code>// booking.js

...

// GET logout route only for authenticated users. Anonym users redirected to home
app.get('/logout', redirectLogin, (req, res) =&gt; {
  req.session.destroy(function(err) {
    if (err) {
      res.send('An err occured: ' +err.message);
    } else {
      var message = 'You have been successfully logged out';
      res.status(200).clearCookie('booking').redirect('/200success?message='+message)
    }
  });
})

...

</code></pre>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Node.js series Part 4. Express Website with authentication and authorization in a Mac Production Environment</title>
		<link>https://digitaldocblog.com/mac/nodejs-series-part-4-express-website-with-authentication-and-authorization-in-a-mac-production-environment/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Thu, 18 Jun 2020 07:00:00 +0000</pubDate>
				<category><![CDATA[Mac OS]]></category>
		<category><![CDATA[Web-Development]]></category>
		<category><![CDATA[Webdesign]]></category>
		<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Blog]]></category>
		<category><![CDATA[Blog-Application]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[Express.js]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[MongoDB]]></category>
		<category><![CDATA[Mongoose]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[NPM Node package manager]]></category>
		<category><![CDATA[Role Based Access Control]]></category>
		<category><![CDATA[Web-Application]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=117</guid>

					<description><![CDATA[In a real production environment the app runs as a service in the background and this service is managed by a process manager. And the app should run behind a&#8230;]]></description>
										<content:encoded><![CDATA[
<p>In a real production environment the app runs as a service in the background and this service is managed by a process manager. And the app should run behind a reverse proxy server. This reverse proxy server manage the TLS encryption, receives the requests from the client and route any request to the app running in the background. So the connection from the client to the reverse proxy server is TLS encrypted. Therefore the data transferred between client and the reverse proxy are secured. </p>



<p>How to setup such a <strong>production environment</strong> will be shown in the <strong>first chapter</strong> of this documentation. </p>



<p>The express app itself also contains some <strong>security features</strong>. The app contains a session based user authentication and HTTP headers which help to further secure the app. This is explained of the <strong>second chapter</strong> of this documentation.</p>



<p>Finally the express app should use the <strong>template engine PUG</strong> to render the HTML for us. This is describes of the <strong>third chapter</strong> of this documentation. </p>



<h3 class="wp-block-heading">1. Setup the production environment</h3>



<h4 class="wp-block-heading">Installation of mongodb</h4>



<p>The command <code>brew tap</code> without any arguments lists the GitHub repositories that are currently linked to your Homebrew installation. </p>



<pre class="wp-block-code"><code>Patricks-MBP:~ patrick$ brew tap
homebrew/cask
homebrew/core
homebrew/services
</code></pre>



<p>The formula mongodb has been removed from homebrew-core. But fortunately the MongoDB Team is maintaining a custom <a href="https://github.com/mongodb/homebrew-brew">Homebrew tap on GitHub</a>. Read the instructions in the README.md file. </p>



<p>Add the custom tap in the Mac OS terminal and install mongodb.</p>



<pre class="wp-block-code"><code>Patricks-MBP:~ patrick$ brew tap mongodb/brew

Patricks-MBP:~ patrick$ brew tap
homebrew/cask
homebrew/core
homebrew/services
mongodb/brew

Patricks-MBP:~ patrick$ brew install mongodb-community@4.2

</code></pre>



<p>After the installation the relevant paths are.</p>



<pre class="wp-block-code"><code>the configuration file (/usr/local/etc/mongod.conf)
the log directory path (/usr/local/var/log/mongodb)
the data directory path (/usr/local/var/mongodb)

</code></pre>



<p>Check the services with homebrew</p>



<pre class="wp-block-code"><code>brew services list

Name              Status  User    Plist
mongodb-community started patrick /Users/patrick/Library/LaunchAgents/homebrew.mxcl.mongodb-community.plist
  
</code></pre>



<p>Start and stop mongodb.</p>



<pre class="wp-block-code"><code>brew services start mongodb-community

brew services stop mongodb-community

</code></pre>



<h4 class="wp-block-heading">Setup mongodb for the project</h4>



<p>Setup an admin user</p>



<pre class="wp-block-code"><code>:# mongo

&gt; use admin
switched to db admin

&gt; db
admin

&gt; db.createUser({ user: "adminUser", pwd: "adminpassword", roles: &#91;{ role: "userAdminAnyDatabase", db: "admin" }, {"role" : "readWriteAnyDatabase", "db" : "admin"}] })

&gt; db.auth("adminUser", "adminpassword")
1

&gt; show users
{
    "_id" : "admin.adminUser",
    "userId" : UUID("5cbe2fc4-1e54-4c2d-89d1-317340429571"),
    "user" : "adminUser",
    "db" : "admin",
    "roles" : &#91;
        {
            "role" : "userAdminAnyDatabase",
            "db" : "admin"
        },
        {
            "role" : "readWriteAnyDatabase",
            "db" : "admin"
        }
    ],
    "mechanisms" : &#91;
        "SCRAM-SHA-1",
        "SCRAM-SHA-256"
    ]
}

&gt; exit

</code></pre>



<p>Enable authentication with <code>security: authorization: enabled</code></p>



<pre class="wp-block-code"><code>#&gt; nano /usr/local/etc/mongod.conf

systemLog:
  destination: file
  path: /usr/local/var/log/mongodb/mongo.log
  logAppend: true
storage:
  dbPath: /usr/local/var/mongodb
net:
  port: 27017
  bindIp: 127.0.0.1
security:
  authorization: enabled


</code></pre>



<p>Login and authenticate with admin</p>



<pre class="wp-block-code"><code>#&gt; mongo

MongoDB shell version v4.2.3
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&amp;gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("b3e7f48a-a05c-4894-87db-996cb34eb1fb") }
MongoDB server version: 4.2.3

&gt; show dbs
&gt; db
test
&gt; use admin
switched to db admin
&gt; db
admin
&gt; show dbs
&gt; db.auth("adminUser", "adminpassword")
1
&gt; show dbs
admin               0.000GB
config              0.000GB
local               0.000GB

&gt; 

</code></pre>



<p>If you login you dont see any databases when you call <code>show dbs</code>. The default database you are connected to is <code>test</code>. </p>



<p>Then you connect to admin database. For admin you setup the admin user with the roles <code>userAdminAnyDatabase</code> and <code>readWriteAnyDatabase</code>. With these permissions the admin user can manage users for any database and has read and write access to any database. </p>



<p>So wehen you logon to admin database with the admin user you are able to see all databases with <code>show dbs</code>. </p>



<p>Mongodb comes with 3 standard dbs pre installed:</p>



<ul class="wp-block-list"><li>admin </li><li>config</li><li>local<br></li></ul>



<p>Create a new database for our express-security app (authenticated as admin user &#8211; see above)</p>



<pre class="wp-block-code"><code>&gt; use express-security
switched to db express-security
&gt; db
express-security
&gt; show dbs
admin               0.000GB
config              0.000GB
local               0.000GB
&gt; 

</code></pre>



<p>The DB which you&#8217;ve created is not listed here. We need to insert at least one collection into it for displaying that database in the list. </p>



<pre class="wp-block-code"><code>&gt; db
express-security

&gt; db.createCollection("col_default")
{ "ok" : 1 }

&gt; show dbs
admin               0.000GB
config              0.000GB
express-security    0.000GB
local               0.000GB

&gt; exit

</code></pre>



<p>Create an owner user for express-security database using the admin user</p>



<pre class="wp-block-code"><code>#&gt; mongo

MongoDB shell version v4.2.3
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&amp;gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("79f79b63-9d08-489f-9e6c-bfc10d8cc09e") }
MongoDB server version: 4.2.3

&gt; db
test

&gt; show dbs

&gt; use admin
switched to db admin

&gt; db.auth("adminUser", "adminpassword")
1

&gt; db
admin

&gt; show dbs
admin               0.000GB
config              0.000GB
express-security    0.000GB
local               0.000GB

&gt; use express-security
switched to db express-security

&gt; db.createUser({ user: "owner_express-security", pwd: "passowrd", roles: &#91;{ role: "dbOwner", db: "express-security" }] })
Successfully added user: {
	"user" : "owner_express-security",
	"roles" : &#91;
		{
			"role" : "dbOwner",
			"db" : "express-security"
		}
	]
}

&gt; db
express-security

&gt; show users
{
	"_id" : "express-security.owner_express-security",
	"userId" : UUID("7a0bafb2-d2ed-4d18-9aba-e2f15a503ec5"),
	"user" : "owner_express-security",
	"db" : "express-security",
	"roles" : &#91;
		{
			"role" : "dbOwner",
			"db" : "express-security"
		}
	],
	"mechanisms" : &#91;
		"SCRAM-SHA-1",
		"SCRAM-SHA-256"
	]
}

&gt; exit 
</code></pre>



<p>Connection string to connect to express-security db using the owner_express-security user:</p>



<pre class="wp-block-code"><code>mongodb://owner_express-security:password@localhost/express-security
</code></pre>



<h4 class="wp-block-heading">Installation of PM2</h4>



<p>PM2 is a process manager for Node.js applications. It can daemonize applications to run them as a service in the background.</p>



<p>I install pm2 as a global npm package on my Mac.</p>



<pre class="wp-block-code"><code>Patricks-Macbook Pro:~ patrick$ npm install pm2 -g
</code></pre>



<p>Then navigate to your project directory.</p>



<pre class="wp-block-code"><code>Patricks-Macbook Pro:~ patrick$ cd Software/dev/node/articles/2020-05-15-express-security/express-security

Patricks-Macbook Pro:~ patrick$ ls -l 
total 112
drwxr-xr-x    5 patrick  staff    160 30 Mai 05:28 database
drwxr-xr-x  115 patrick  staff   3680 30 Mai 19:35 node_modules
-rw-r--r--    1 patrick  staff  34366 30 Mai 19:35 package-lock.json
-rw-r--r--    1 patrick  staff    339 30 Mai 19:35 package.json
-rw-r--r--@   1 patrick  staff  12343 30 Jun 05:00 secserver.js
drwxr-xr-x    3 patrick  staff     96 30 Mai 05:03 static

Patricks-Macbook Pro:express-security patrick$ 

</code></pre>



<p>Start your app using pm2</p>



<pre class="wp-block-code"><code>Patricks-Macbook Pro:express-security patrick$ pm2 start secserver.js

Patricks-Macbook Pro:~ patrick$ pm2 list
┌─────┬──────────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id  │ name         │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
├─────┼──────────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────────┤
│ 0   │ secserver    │ default     │ 1.0.0   │ fork    │ 640      │ 16h    │ 0    │ online    │ 0%       │ 48.6mb   │ patrick  │ disabled │
└─────┴──────────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

Patricks-Macbook Pro:~ patrick$ 

</code></pre>



<p>Other comands to control the process manager. </p>



<pre class="wp-block-code"><code>pm2 start secserver.js

pm2 start &lt;id&gt;

pm2 list

pm2 stop &lt;id&gt;

pm2 restart &lt;id&gt;

pm2 show &lt;id&gt;

</code></pre>



<h4 class="wp-block-heading">Installation of nginx</h4>



<p><a href="https://nginx.org/en/">nginx</a> is an open source HTTP and an HTTP Reverse Proxy Server (also mail proxy and load balancer etc.). I install nginx on my Mac with Homebrew. </p>



<pre class="wp-block-code"><code>brew install nginx

</code></pre>



<p>You can list the brew services with the following command.</p>



<pre class="wp-block-code"><code>Patricks-MBP:digitaldocblog-V3 patrick$ brew services list

Name              Status  User    Plist
mongodb-community started patrick /Users/patrick/Library/LaunchAgents/homebrew.mxcl.mongodb-community.plist
nginx             started patrick /Users/patrick/Library/LaunchAgents/homebrew.mxcl.nginx.plist
Patricks-MBP:digitaldocblog-V3 patrick$
</code></pre>



<p>You can start and stop the brew services as follows.</p>



<pre class="wp-block-code"><code>brew install nginx

brew services start nginx

brew services stop nginx

</code></pre>



<h4 class="wp-block-heading">Setup nginx with TLS/SSL</h4>



<p>SSL/TLS works by using the combination of a public certificate and a private key. </p>



<p>The <strong>SSL key (private key)</strong> is kept secret on the server. It is used to encrypt content sent to clients. </p>



<p>The <strong>SSL certificate</strong> is publicly shared with anyone requesting the content. It can be used to decrypt the content signed by the associated SSL key.</p>



<p><strong>create private key</strong></p>



<pre class="wp-block-code"><code>Patricks-MBP:express-security patrick$ cd /usr/local/etc/nginx

Patricks-MBP:nginx patrick$ ls -l
total 144
-rw-r--r--  1 patrick  admin  1077  5 Apr 13:18 fastcgi.conf
-rw-r--r--  1 patrick  admin  1077  5 Apr 13:18 fastcgi.conf.default
-rw-r--r--  1 patrick  admin  1007  5 Apr 13:18 fastcgi_params
-rw-r--r--  1 patrick  admin  1007  5 Apr 13:18 fastcgi_params.default
-rw-r--r--  1 patrick  admin  2837  5 Apr 13:18 koi-utf
-rw-r--r--  1 patrick  admin  2223  5 Apr 13:18 koi-win
-rw-r--r--  1 patrick  admin  5231  5 Apr 13:18 mime.types
-rw-r--r--  1 patrick  admin  5231  5 Apr 13:18 mime.types.default
-rw-r--r--  1 patrick  admin  3106 15 Mai 05:19 nginx.conf
-rw-r--r--  1 patrick  admin  2680  5 Apr 13:18 nginx.conf.default
-rw-r--r--  1 patrick  admin  3091 21 Jan 05:40 nginx.conf.working
-rw-r--r--  1 patrick  admin   636  5 Apr 13:18 scgi_params
-rw-r--r--  1 patrick  admin   636  5 Apr 13:18 scgi_params.default
drwxr-xr-x  3 patrick  admin    96 21 Jan 06:02 servers
-rw-r--r--  1 patrick  admin   664  5 Apr 13:18 uwsgi_params
-rw-r--r--  1 patrick  admin   664  5 Apr 13:18 uwsgi_params.default
-rw-r--r--  1 patrick  admin  3610  5 Apr 13:18 win-utf

Patricks-MBP:nginx patrick$ mkdir ssl

Patricks-MBP:nginx patrick$ ls -l
total 152
-rw-r--r--  1 patrick  admin  1077  5 Apr 13:18 fastcgi.conf
-rw-r--r--  1 patrick  admin  1077  5 Apr 13:18 fastcgi.conf.default
-rw-r--r--  1 patrick  admin  1007  5 Apr 13:18 fastcgi_params
-rw-r--r--  1 patrick  admin  1007  5 Apr 13:18 fastcgi_params.default
-rw-r--r--  1 patrick  admin  2837  5 Apr 13:18 koi-utf
-rw-r--r--  1 patrick  admin  2223  5 Apr 13:18 koi-win
-rw-r--r--  1 patrick  admin  5231  5 Apr 13:18 mime.types
-rw-r--r--  1 patrick  admin  5231  5 Apr 13:18 mime.types.default
-rw-r--r--@ 1 patrick  admin   373 18 Mai 05:38 nginx.conf
-rw-r--r--  1 patrick  admin  2680  5 Apr 13:18 nginx.conf.default
-rw-r--r--  1 patrick  admin  3091 21 Jan 05:40 nginx.conf.working
-rw-r--r--@ 1 patrick  admin  1390 17 Mai 05:19 nginx_old.conf
-rw-r--r--  1 patrick  admin   636  5 Apr 13:18 scgi_params
-rw-r--r--  1 patrick  admin   636  5 Apr 13:18 scgi_params.default
drwxr-xr-x  5 patrick  admin   160 18 Mai 05:20 servers
drwxr-xr-x  4 patrick  admin   128 16 Mai 05:41 ssl
-rw-r--r--  1 patrick  admin   664  5 Apr 13:18 uwsgi_params
-rw-r--r--  1 patrick  admin   664  5 Apr 13:18 uwsgi_params.default
-rw-r--r--  1 patrick  admin  3610  5 Apr 13:18 win-utf

Patricks-MBP:nginx patrick$ cd ssl

Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl genrsa -out privateKey.pem 4096

Patricks-MBP:ssl patrick$ ls -l
total 16
-rw-r--r--  1 patrick  admin  3247 16 Mai 05:22 privateKey.pem

</code></pre>



<p><strong>create certificate signing request (CSR)</strong></p>



<pre class="wp-block-code"><code>Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl req -new -key privateKey.pem -out csr.pem

Patricks-MBP:ssl patrick$ ls -l
total 16
-rw-r--r--  1 patrick  admin  1740 16 Mai 05:23 csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai 05:22 privateKey.pem

</code></pre>



<p>In case I would like to request an official certificate I must send this csr to the certificate authority. This authority would then create an <strong>authority signed certificate</strong> from the CSR and send it back to me.</p>



<p>This step is done by ourselves and this is the reason why we create a <strong>self signed certificate</strong>. This self signed certificate is not a official certificae and not trusted by any browser. It is not useful to use a self signed certificate in production because it produces error messages in the browsers. But for local development a self signed certificate is ok. </p>



<p>So create the self signed certificale. The csr file can then be removed. </p>



<p><strong>create the self signed certificate</strong></p>



<pre class="wp-block-code"><code>Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl x509 -in csr.pem -out selfsignedcertificate.pem -req -signkey privateKey.pem -days 365

Patricks-MBP:ssl patrick$ ls -l
total 24
-rw-r--r--  1 patrick  admin  1740 16 Mai 05:23 csr.pem
-rw-r--r--  1 patrick  admin  3247 16 Mai 05:22 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai 05:39 selfsignedcertificate.pem

Patricks-MBP:ssl patrick$ rm csr.pem

Patricks-MBP:ssl patrick$ ls -l
total 24
-rw-r--r--  1 patrick  admin  3247 16 Mai 05:22 privateKey.pem
-rw-r--r--  1 patrick  admin  1980 16 Mai 05:39 selfsignedcertificate.pem
 
</code></pre>



<p><strong>show certificate details</strong></p>



<pre class="wp-block-code"><code>Patricks-MBP:ssl patrick$ pwd
/usr/local/etc/nginx/ssl

Patricks-MBP:ssl patrick$ openssl x509 -in selfsignedcertificate.pem -text -noout

</code></pre>



<h4 class="wp-block-heading">Configure nginx Servers with SSL</h4>



<p>In our configuration we enforce ssl. Therefore we create a <strong>default Webserver</strong> listening on Port 80 with the server name  <code>servtest.rottlaender.lan</code>. </p>



<p>Any request to <code>servtest.rottlaender.lan:80</code> is redirected to my <strong>Reverse Proxy Server</strong> which is listening on <code>servtest.rottlaender.lan:443</code>.<br></p>



<p>The <strong>default Webserver</strong> is configured in <code>/usr/local/etc/nginx/nginx.conf</code>.</p>



<pre class="wp-block-code"><code># /usr/local/etc/nginx/nginx.conf
# default Webserver

worker_processes  1;
error_log  /usr/local/etc/nginx/logs/error.log;

events {
    worker_connections  1024;
}

http {
    include       		mime.types;
    default_type  		application/octet-stream;
    sendfile        	on;
    keepalive_timeout  	65;

    access_log  /usr/local/etc/nginx/logs/access.log;

    # default Webserver redirect from port 80 to port 443 ssl
    
    server {
      	listen 80;
    	listen &#91;::]:80;
    	server_name servtest.rottlaender.lan;
    	return 301 https://$host$request_uri;
    }
    
    include servers/*;
    
}
</code></pre>



<p>The <strong>Reverse Proxy Server</strong> is configured in <code>/usr/local/etc/nginx/servers/reverse</code>.</p>



<pre class="wp-block-code"><code>// /usr/local/etc/nginx/servers/reverse
// reverse Proxy Server

server {

    listen      443 ssl;
    server_name servtest.rottlaender.lan;

    ssl_certificate      ssl/selfsignedcertificate.pem;
    ssl_certificate_key  ssl/privateKey.pem;
    ssl_session_cache    shared:SSL:1m;
    ssl_session_timeout  5m;
    ssl_ciphers  HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers  on;

    location / {
       proxy_pass http://localhost:3300;
       proxy_set_header X-Forwarded-For $remote_addr;
    }
}

</code></pre>



<p>The server name servtest.rottlaender.lan is linked in /private/etc/hosts to the ip 192.168.178.20 which is the ip of my computer in my local network.</p>



<pre class="wp-block-code"><code>Patricks-MBP:digitaldocblog-V3 patrick$ cat /private/etc/hosts
##
# Host Database
#
# localhost is used to configure the loopback interface
# when the system is booting.  Do not change this entry.
##
127.0.0.1			localhost
255.255.255.255		broadcasthost
::1             	localhost
192.168.178.20 		servtest.rottlaender.lan
</code></pre>



<h3 class="wp-block-heading">2. Express Secure App (Security Features HTML version)</h3>



<p>This is a very simple application but show the basic security features you should use when you run a node app in a production environment. </p>



<p>The app is a website with a simple layout and navigation. </p>



<p>The Home page contain static information and can be accessed by everyone. </p>



<p>On the register page, users can find a form to register. The user data entered here are saved in the database and the user is logged in at the same time. Known users can log in with their email and password after successful registration on the login page. The login and register page can only be accessed if the user is not logged in. If a user is logged in and tries to access the login or register, he will be redirected to the dashboard page.</p>



<p>The dashboard is a personalized area of the website. This area can only be accessed if the user is logged in. If a user is not logged in, he will be redirected to the login page.</p>



<p>Logout is not really a page but a link that contains a logout function. Users who are logged in can log out using this link. Users who are not already logged in will be redirected to the login page.</p>



<h4 class="wp-block-heading">Download the code from GitHub</h4>



<p>Pls. <a href="https://github.com/prottlaender/node-part-4-express-security-with-db-html">go to my GitHub site</a> and clone the code. Here you find a some inline documentation in the code. The details are explained in this chapter. </p>



<h4 class="wp-block-heading">Create your app home directory express-security</h4>



<p>My app home directory is different to the one that is available after you cloned the code from GitHub. </p>



<pre class="wp-block-code"><code>Patricks-MBP:2020-05-15-express-security patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security

Patricks-MBP:2020-05-15-express-security patrick$ mv node-part-5-express-security-with-db-pug express-security

Patricks-MBP:2020-05-15-express-security patrick$ cd express-security

Patricks-MBP:express-security patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security/express-security
</code></pre>



<h4 class="wp-block-heading">Manage environment variables</h4>



<p>To manage environment variables for my app I use <code>envy</code>. First you need the files <code>.env</code> and <code>.env.example</code> in the root of your project directory. In <code>.env.example</code> you create a list of all potential environment variables without any values and in <code>.env</code> you use the defined variables and assign the values to them. </p>



<pre class="wp-block-code"><code>Patricks-MBP:express-security patrick$ ls -al
total 152
drwxr-xr-x   14 patrick  staff    448 23 Jun 05:29 .
drwxr-xr-x    4 patrick  staff    128 26 Mai 05:40 ..
-rw-------    1 patrick  staff    181 23 Jun 05:59 .env
-rw-r--r--    1 patrick  staff     53 23 Jun 05:59 .env.example
....

Patricks-MBP:express-security patrick$ cat .env.example
port=
mongodbpath=
sessionsecret=
sessioncookiename=

Patricks-MBP:express-security patrick$ cat .env
port=&lt;YOUR_PORT&gt;
mongodbpath=&lt;YOUR_CONNECTION_STRING&gt;
sessionsecret=&lt;YOUR_SESSION_SECRET&gt;
sessioncookiename=&lt;YOUR_SESSION_COOKIE_NAME&gt;

Patricks-MBP:express-security patrick$ 

</code></pre>



<p>Envy must be installed as dependency and required in the main application file <em>secserver.js</em>. Then you can set the environment variables as follows. </p>



<pre class="wp-block-code"><code>// secserver.js

....

// envy module to manage environment variables
const envy = require('envy');

// set the environment variables
const env = envy()
const port = env.port
const mongodbpath = env.mongodbpath
const sessionsecret = env.sessionsecret
const sessioncookiename = env.sessioncookiename
....
</code></pre>



<h4 class="wp-block-heading">Start the MongoDB Server</h4>



<p>To run the db server we install <code>mongoose</code> as dependency and require it in the db.js configuration file. The database connection will be initiated with <code>mongoose.connect</code> and the StartMongoServer function will be exported to be called in the main application file <em>secserver.js</em>.</p>



<pre class="wp-block-code"><code>const envy = require('envy')
const env = envy()

const mongodbpath = env.mongodbpath

const mongoose = require('mongoose');
mongoose.set('useNewUrlParser', true);
mongoose.set('useUnifiedTopology', true);

const StartMongoServer = async function() {
  try {

    await mongoose.connect(mongodbpath)
    .then(function() {
      console.log(`Mongoose connection open on ${mongodbpath}`);
    })
    .catch(function(error) {
      console.log(`Connection error message: ${error.message}`);
    })

  } catch(error) {
    res.json( { status: "db connection error", message: error.message } );
  }

};

module.exports = StartMongoServer;
</code></pre>



<h4 class="wp-block-heading">Authentication and authorization</h4>



<p>For user authentication we use the module <code>express-session</code> and to store session data in the session store in our database we use <code>connect-mongodb-session</code>. Therefore we install these modules as dependencies in our project and require the modules in our <em>secserver.js</em> main application file.</p>



<p>Then we create with <code>new MongoDBStore</code> a session storage in our MongoDB to store session data in collection <code>col_sessions</code>. errors are catched with <code>store.on</code>. </p>



<p>We use the session in our app with <code>app.use( session({...}) )</code>. With every request to our site a new session object is created with a unique session ID which include a session cookie object. The session object has keys options and the values for each key define how to deal with the session object. The session ID is created and signed using the <code>secret</code> option. We use <code>name</code> to provide a session cookie name and <code>store</code> to define where the session object should be stored (in case we store the session). </p>



<p>We can access the session object with <code>req.session</code> and the session ID with <code>req.session.id</code>. With every request we have a new session and this new session will be created but not stored anywhere so far. We say the session is <em>uninitialized</em>. The <code>saveUninitialized</code> false option ensure that a session will only be written to the store in case it has been modified. What does this mean?<br></p>



<p>We can modify the session when we store additional data into it. We always do this when the user is logging in via the <code>login</code> or the <code>register</code> route. When we post the data from the <em>login-</em> or from the <em>registration-form</em> to the server we call <em>loginUser</em> or the <em>createUser</em> module which is defined in <code>database/controllers/userC.js</code>. Both modules do basically the same thing: They create a userData Object and store the userData object into the session object and redirect the user to the dashboard when login or registration was successful. </p>



<pre class="wp-block-code"><code>....

var userData = { 
	userId: user._id, 
	name: user.name, 
	lastname: user.lastname, 
	email: user.email, 
	role: user.role 
	}

    req.session.userData = userData

    res.redirect('/dashboard')

....

</code></pre>



<p>If the user is successfully logged in the session is <em>initialized</em> (modified), the session object incl. the userData object are stored into the store and a cookie is stored into the requesting browser. The content of the cookie is only a hash of the session Id and with each request of a logged in user the session on the server is looked up. </p>



<p>The cookie in the browser will live max 1 week as we defined in the cookie object <code>maxAge</code> set to 1 week. Because of the cookie option <code>sameSite</code> true the cookie scope is limited to the same site. </p>



<p>Then the <code>resave</code> false option ensures that the session will not be updated with every request. This mean the session ID that has been created when the user has logged in will be kept until the user is logged out again.<br></p>



<pre class="wp-block-code"><code>// secserver.js

....

// server side session and cookie module
const session = require('express-session');
// mongodb session storage module
const connectMdbSession = require('connect-mongodb-session');

....

// Create MongoDB session storage
const MongoDBStore = connectMdbSession(session)
const store = new MongoDBStore({
  uri: mongodbpath,
  collection: 'col_sessions'
});

// catch errors in case store creation fails
store.on('error', function(error) {
  console.log(`error store session in session store: ${error.message}`);
});

// Create the express app
const app = express();

....

// use session to create session and session cookie
app.use(session({
  secret: sessionsecret,
  name: sessioncookiename,
  store: store,
  resave: false,
  saveUninitialized: false,
  // set cookie to 1 week maxAge
  cookie: {
    maxAge: 1000 * 60 * 60 * 24 * 7,
    sameSite: true
  },

}));

....
</code></pre>



<h4 class="wp-block-heading">Secure HTTP headers</h4>



<p>Response headers are HTTP header that come with the HTTP response from the server to the client. The http response header contain data that could possibly damage the integrity of the client. It is therefore important to secure the response header of your application.</p>



<p>To secure the http response headers I user the module <a href="https://helmetjs.github.io/">helmet</a>. This is a relatively easy-to-use module consisting of various middleware functionalities to secure various http response headers.</p>



<p>First we install <code>helmet</code>as a dependency of our project. Then we require helmet and use helmet right after we created the app. </p>



<pre class="wp-block-code"><code>// secserver.js

// hTTP module
const http = require('http');
// express module
const express = require('express');
// hTTP header security module
const helmet = require('helmet');

// Create the express app
const app = express();

....

// use secure HTTP headers using helmet
app.use(helmet())

</code></pre>



<p>Using simply <code>app.use(helmet())</code> set the http header security to default. Then the following 7 out 11 helmet features can be used.</p>



<ol class="wp-block-list"><li><a href="https://helmetjs.github.io/docs/dns-prefetch-control">dnsPrefetchControl</a> controls browser DNS prefetching</li><li><a href="https://helmetjs.github.io/docs/frameguard/">frameguard</a> to prevent clickjacking</li><li><a href="https://helmetjs.github.io/docs/hide-powered-by/">hidePoweredBy</a> to remove the X-Powered-By header</li><li><a href="https://helmetjs.github.io/docs/hsts/">hsts</a> for HTTP Strict Transport Security</li><li><a href="https://helmetjs.github.io/docs/ienoopen/">ieNoOpen</a> sets X-Download-Options for IE8+</li><li><a href="https://helmetjs.github.io/docs/dont-sniff-mimetype/">noSniff</a> to keep clients from sniffing the MIME type</li><li><a href="https://helmetjs.github.io/docs/xss-filter/">xssFilter</a> adds some small XSS protections<br></li></ol>



<p>When we then request our home page to retrieve the http headers using <code>curl -k --head</code> in the terminal we see the following output. </p>



<pre class="wp-block-code"><code>Patricks-MBP:express-security patrick$ curl -k --head https://servtest.rottlaender.lan
HTTP/1.1 200 OK
Server: nginx/1.19.0
Date: Fri, 26 Jun 2020 16:14:14 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1734
Connection: keep-alive
X-DNS-Prefetch-Control: off
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
ETag: W/"6c6-U2uWyDNyzlyBAbSI/Quxqo9RRQE"

Patricks-MBP:express-security patrick$ 
</code></pre>



<h4 class="wp-block-heading">App routing</h4>



<p><strong>get routes:</strong> We have the following <code>get</code> routes and navigation.</p>



<ul class="wp-block-list"><li>Home (/)</li><li>Login (/login)</li><li>Register (/register)</li><li>Dashboard (/dashboard)</li><li>Logout (/logout)<br></li></ul>



<p><code>get</code> routes involve an optional middleware and respond HTML back to the client. </p>



<pre class="wp-block-code"><code>app.get('/&lt;route&gt;', &lt;optional: someMiddleware&gt;, (req, res) =&gt; {
	
	res.send(`&lt;some HTML&gt;`)
})
</code></pre>



<p>I will not explain the HTML and css in detail. But as everyone can see, the HTML is the same for every route except for the <code>&lt;body&gt;</code>. Of course, this is not very nice and becomes a bit more efficient with the use of a template engine, which I will explain below using the <a href="https://pugjs.org/api/getting-started.html">PUG template engine</a>. I will then rebuild the app using PUG.</p>



<p>Lets have a look at the <code>middleware</code>. If a request is made for a route and a middleware function is included, the middleware function is first executed before the next routing function <code>function(req, res)</code> is called. A condition is built into the middleware function which is checked. My middleware is built so that in case the condition is true the middleware code is executed directly and the next routing function is omitted. If the condition is false, the next routing function <code>function(req, res)</code> is called.</p>



<p>I have built 2 different middleware functions which each check</p>



<p><strong>middleware 1 (login- and register route):</strong> a user is logged in</p>



<pre class="wp-block-code"><code>// secserver.js
....
// middleware 1 to redirect authenticated users to their dashboard
const redirectDashboard = (req, res, next) =&gt; {
  if (req.session.userData) {
    res.redirect('/dashboard')

  } else {
    next()
  }
}
....
</code></pre>



<p>If a user is logged in the request should be redirected to the dashboard route, in any other case (user is not logged in) the next routing function <code>function(req, res)</code> is called and respond the HTML to the browser. This middleware 1 is included in the <strong>/login-</strong> and <strong>/register</strong> route. This mean logged in users will be redirected to their dashboard, not logged in users will see the login- and register form. </p>



<p><strong>middleware 2 (dashboard- and logout route):</strong> a user is not logged in.</p>



<pre class="wp-block-code"><code>// secserver.js
....

// middleware 2 to redirect not authenticated users to login
const redirectLogin = (req, res, next) =&gt; {
  if (!req.session.userData) {
    res.redirect('/login')
  } else {
    next()
  }
}
....
</code></pre>



<p>If a user is not logged in the request should be redirected to the login roure, in any other case (user is logged in) the next routing function <code>function(req, res)</code> is called and respond the HTML to the browser. This middleware 2 is included in the <strong>/dashboard-</strong> and <strong>/logout</strong> route. This mean not logged in users will be redirected to login route, logged in users will see the dashboard- or can log themselves out. </p>



<p><strong>post routes:</strong> We have the following <code>post</code> routes.</p>



<ul class="wp-block-list"><li>/login</li><li>/register<br></li></ul>



<p>The login and the register <code>get</code> routes contain a form in the HTML. With these forms the user provide the data to login and for user registration. When the user click the send button the <em>action</em> is to call the login- or register <code>post</code> route. This will happen for all not logged in users. The login and the register <code>get</code> routes have the middleware <code>redirectDashboard</code>to redirect the user to the dashbard if the user is already logged in.<br></p>



<pre class="wp-block-code"><code>// secserver.js
....

app.get('/login', redirectDashboard, (req, res) =&gt; {
....
res.send(`
....
&lt;div class="form"&gt;
	&lt;form id='register_form' method='post' action='/register'&gt;
	......
	&lt;label for='send'&gt;
   		&lt;input class='sendbutton' type='submit' name='send' value='Send'&gt;
	&lt;/label&gt;
	&lt;/form&gt;
&lt;/div&gt;

`)
)}
....

app.get('/register', redirectDashboard, (req, res) =&gt; {
....
res.send(`
....
&lt;div class="form"&gt;
	&lt;form id='login_form' method='post' action='/login'&gt;
	......
	&lt;label for='send'&gt;
   		&lt;input class='sendbutton' type='submit' name='send' value='Send'&gt;
	&lt;/label&gt;
	&lt;/form&gt;
&lt;/div&gt;
`)
)}
.....
</code></pre>



<p>The <code>post</code> routes contain functions to login- (loginUser) or register (createUser) the user.<br></p>



<pre class="wp-block-code"><code>// secserver.js
....
// Post routes to manage user login and user registration
app.post('/login', userController.loginUser);

app.post('/register', userController.createUser);
....
</code></pre>



<p>The <code>loginUser</code> function is defined in the user controller <code>database/controllers/userC.js</code>. This function lookup a user in the database based on the email address that has been provided by the request body. The data that are attached to the request body have been provided by the user vie the login form of the app. If no user could be found in the database login is not possible. If a user exist with the given email address then the provided password will be compared with the one stored in the database. If the password match fail login is not possible because the provided password is wrong. in any other case, the login takes place and a userData object is created and attached to the session object.</p>



<pre class="wp-block-code"><code>// database/controllers/userC.js

User.findOne({ email: req.body.email }, function(error, user) {
  if (!user) {
    res.status(400).send({ code: 400, status: 'Bad Request', message: 'No User found with this email' })

    } else {
      if (bcrypt.compareSync(req.body.password, user.password)) {
    
        var userData = { userId: user._id, name: user.name, lastname: user.lastname, email: user.email, role: user.role }

          req.session.userData = userData

          res.redirect('/dashboard')

        } else {
          res.status(400).send({ code: 400, status: 'Bad Request', message: 'Wrong User password' })
        }

      }
    })

  }
</code></pre>



<p>The <code>createUser</code> function is also defined in the user controller <code>database/controllers/userC.js</code>. This function create a new User object based on the data from the request body provided by the user via the form. The provided password will be hashed and stored together with all other data into the database. Finally a userData object is created and attached to the session and the user will be redirected to the dashboard after the registration was successful.<br></p>



<pre class="wp-block-code"><code>// database/controllers/userC.js

createUser: async function (req, res) {
    // assign input data from request body to input variables
    const name = req.body.name
    const lastname = req.body.lastname
    const email = req.body.email
    const password = req.body.password
    const role = req.body.role

    const newUser = new User({
      name: name,
      lastname: lastname,
      email: email,
      password: password,
      role: role
    })

    newUser.password = await bcrypt.hash(newUser.password, saltRounds)

    await newUser.save(function(err, user) {
          if (err) {
            // if a validation err occur end request and send response
            res.status(400).send({ code: 400, status: 'Bad Request', message: err.message })
          } else {
            // req.session.userId = user._id

            var userData = { userId: user._id, name: user.name, lastname: user.lastname, email: user.email, role: user.role }

            req.session.userData = userData

            res.redirect('/dashboard')
          }
        })
  },
</code></pre>



<p>And we have a default <code>get</code> route.</p>



<ul class="wp-block-list"><li>/favicon.ico<br></li></ul>



<p>Browsers will by default try to request /favicon.ico from the root of a hostname, in order to show an icon in the browser tab. As we dont use favicon so far we must avoid that these requests returning a 404 (Not Found). Here The /favicon.ico request will be catched and send a 204 No Content status.</p>



<pre class="wp-block-code"><code>// secserver.js
....
app.get('/favicon.ico', function(req, res) {
    console.log(req.url);
    res.status(204).json({status: 'no favicon'});
});
....
</code></pre>



<h3 class="wp-block-heading">3. Express App (Pug Template Version)</h3>



<p>From a functional point of view this app is pretty much the same app then the HTML version. The difference is that we use PUG templates instead of HTML in each res.send().</p>



<h4 class="wp-block-heading">Setup a seperate Database</h4>



<p>For the PUG version of my app I set up a new database to manage the users and the sessions.</p>



<pre class="wp-block-code"><code>#&gt; mongo

MongoDB shell version v4.2.3
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&amp;gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("b3e7f48a-a05c-4894-87db-996cb34eb1fb") }
MongoDB server version: 4.2.3

&gt; db
test

&gt; use admin
switched to db admin

&gt; db
admin

&gt; db.auth("adminUser", "adminpassword")
1

&gt; show dbs
admin               0.000GB
config              0.000GB
express-security    0.000GB
local               0.000GB

&gt; use express-security-pug
switched to db express-security-pug

&gt; db.createUser({ user: "owner_express-security-pug", pwd: "passowrd", roles: &#91;{ role: "dbOwner", db: "express-security-pug" }] })

Successfully added user: {
    "user" : "owner_express-security-pug",
    "roles" : &#91;
        {
            "role" : "dbOwner",
            "db" : "express-security-pug"
        }
    ]
}

&gt; db
express-security-pug

&gt; exit
</code></pre>



<p>Connection string to connect to express-security-pug db using the owner_express-security-pug user.</p>



<pre class="wp-block-code"><code>mongodb://owner_express-security-pug:password@localhost/express-security-pug
</code></pre>



<h4 class="wp-block-heading">Download the code from GitHub</h4>



<p>Pls. <a href="https://github.com/prottlaender/node-part-5-express-security-with-db-pug">go to my GitHub site</a> and clone the code. Here you find some inline documentation in the code.</p>



<h4 class="wp-block-heading">Create your app home directory express-security-pug</h4>



<p>My app home directory is different to the one that is available after you cloned the code from GitHub. </p>



<pre class="wp-block-code"><code>Patricks-MBP:2020-05-15-express-security patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security

Patricks-MBP:2020-05-15-express-security patrick$ mv node-part-5-express-security-with-db-pug express-security-pug

Patricks-MBP:2020-05-15-express-security patrick$ cd express-security-pug

Patricks-MBP:express-security-pug patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security/express-security-pug
</code></pre>



<h4 class="wp-block-heading">Install PUG and use it in your app</h4>



<p>First we install <a href="https://pugjs.org/api/getting-started.html">PUG</a> as a dependency. </p>



<pre class="wp-block-code"><code>Patricks-MBP:express-security-pug patrick$ pwd
/Users/patrick/software/dev/node/articles/2020-05-15-express-security/express-security-pug

Patricks-MBP:express-security-pug patrick$ npm install pug --save
</code></pre>



<p><a href="https://pugjs.org/api/getting-started.html">PUG</a> is already <a href="https://pugjs.org/api/express.html">fully integrated</a> into Express. Pls. read the documentation <a href="https://expressjs.com/en/guide/using-template-engines.html">how to use template engines in Express</a>. </p>



<p>After you installed PUG the view engine must be set in your main application file <em>secserverpug.js</em>.</p>



<pre class="wp-block-code"><code>// secserverpug.js
....
// use Pug Template Engine
app.set('view engine', 'pug')
app.set('views', './views')
....
</code></pre>



<p>These instructions tell your app that PUG template engine is used and that the templates can be found in <code>/views</code> directory.</p>



<h4 class="wp-block-heading">PUG Directory setup</h4>



<p>In <code>/views</code> I setup the templates for home, login, registration and an error template. </p>



<p>In <code>/views/includes</code> I setup the files containing HTML or JavaScript. These can be included in the templates. </p>



<pre class="wp-block-code"><code>Patricks-MBP:express-security-pug patrick$ ls -l
total 128
-rw-r--r--    1 patrick  staff    771  1 Jul 06:04 README.md
drwxr-xr-x    5 patrick  staff    160 29 Jun 05:26 database
drwxr-xr-x  150 patrick  staff   4800 29 Jun 06:11 node_modules
-rw-r--r--    1 patrick  staff  47547 29 Jun 06:11 package-lock.json
-rw-r--r--    1 patrick  staff    367 29 Jun 06:11 package.json
-rw-r--r--    1 patrick  staff   4393  2 Jul 05:34 secserverpug.js
drwxr-xr-x    3 patrick  staff     96 29 Jun 05:26 static
drwxr-xr-x    8 patrick  staff    256  2 Jul 05:45 views

Patricks-MBP:express-security-pug patrick$ ls -l views
total 40
-rw-r--r--  1 patrick  staff   549 30 Jun 05:35 dashboard.pug
-rw-r--r--  1 patrick  staff   522  2 Jul 05:50 err.pug
-rw-r--r--  1 patrick  staff   420 29 Jun 05:39 home.pug
drwxr-xr-x  6 patrick  staff   192 29 Jun 05:17 includes
-rw-r--r--  1 patrick  staff   735 30 Jun 05:02 login.pug
-rw-r--r--  1 patrick  staff  1067 30 Jun 05:08 register.pug

Patricks-MBP:express-security-pug patrick$ ls -l views/includes
total 32
-rw-r--r--  1 patrick  staff   76 29 Jun 05:39 foot.pug
-rw-r--r--  1 patrick  staff  167 29 Jun 05:24 head.pug
-rw-r--r--  1 patrick  staff  489  2 Jul 05:13 nav.pug
-rw-r--r--  1 patrick  staff  420 29 Jun 05:08 script.js

Patricks-MBP:express-security-pug patrick$ 
</code></pre>



<h4 class="wp-block-heading">The responsive Website Design</h4>



<p>Each site like <em>home</em>, <em>login</em>, <em>register</em> and <em>dashboard</em> has a <strong>specific site template</strong> in <code>/views</code> directory. The site content will be defined in the <em>main section</em> of each template. PUG enables files with HTML or JavaScript to be included. This makes the <em>site templates</em> clear and easy to maintain. The <strong>includes</strong> are located in <code>/views/includes</code> directory.</p>



<p>The website is build based on a grid design and each site template has the following structure.</p>



<pre class="wp-block-code"><code>doctype html

HTML

	Head
		include includes/head.pug	
	
	Body
		
		Grid-Container
			
				Header
					include includes/nav.pug
			
				Main
					... site template specific HTML ...
			
				Footer
					include includes/foot.pug
					
		&lt;script&gt;
			  include includes/script.js

</code></pre>



<p>The design of the website is defined in the css in <code>static/css/style.css</code>.</p>



<p>Here in the css we define the <strong>Site Structure</strong> as <em>grid areas</em> consisting of header, main and footer and link them to the <em>grid-container</em>. </p>



<pre class="wp-block-code"><code>....

.header { grid-area: header; background-color: #ffffff; border-radius: 5px;}
.main { grid-area: main; background-color: #ffffff; border-radius: 5px;}
.footer { grid-area: footer; background-color: #ffffff; border-radius: 5px;}

.grid-container {
  display: grid;
  grid-template-areas:
      "header"
      "main"
      "footer";
  grid-gap: 5px;
  background-color: #d1d1e0;
  padding: 50px;
}

....
</code></pre>



<p>The <strong>Navigation</strong> is defined in the Header area of the Grid-Container and the HTML comes into the template via <code>include includes/nav.pug</code>.</p>



<pre class="wp-block-code"><code>// includes/nav.pug

//(this) refers to the DOM element to which the onclick attribute belongs to
// the a DOM element will be given as parameter to the function

a(class="burgericon" onclick="myFunction(this)")
  div(class='burgerline' id='bar1')
  div(class='burgerline' id='bar2')
  div(class='burgerline' id='bar3')

a(class='link' href='/') Home
a(class='link' href='/login') Login
a(class='link' href='/register') Register
a(class='link' href='/dashboard') Dashboard
a(class='link' href='/logout') Logout
</code></pre>



<p>So the navigation design is then defined in the css. Each navigation object is an <code>a</code> link. We have <code>a</code> links with class <code>link</code> and <code>burgericon</code>. The burgericon is used to open the navigation bar onclick when the screen is smaller than 600px (like iphone displays etc., explained below), is cosisting of 3 burgerlines and these lines are created using 3 div objects with class <code>burgerline</code>. The burgelines will be transformed with speed 0.4s when you click on the burgericon (explained below). The burgericon is not visible and aligned on the right edge. All other navigation links are visible and aligned on the left edge.<br></p>



<pre class="wp-block-code"><code>/* static/css/style.css */
....

/* style the navigation links with float on the left (side by side) */
.header a.link {
  float: left;
  display: block;
  padding: 14px 16px;
  text-decoration: none;
  font-size: 1.4vw;
  color: #28283e;
}

/* hover effect for each navigation link */
.header a.link:hover {
  background-color: #28283e;
  color: #ffffff;
}

/* style the burgericon link on the right */
.header a.burgericon {
  float: right;
  display: none;
  padding: 14px 16px;
}

/* style each burgerline that create the burgericon */
.burgerline {
  width: 35px;
  height: 5px;
  background-color: #28283e;
  margin: 6px 0;
  transition: 0.4s;
}
....
</code></pre>



<p>When the display screen is lower than 600px the navigation links will not be shown and the burgericon (on the right side) will be faded in instead. </p>



<pre class="wp-block-code"><code>/* static/css/style.css */
....
/* for screens up to 600px remove the navigation links and show the burgericon instead */
@media screen and (max-width: 600px) {
  .header a.link { display: none; }
  .header a.burgericon { display: block; }
}
....
</code></pre>



<p>When you click on the burgericon the burgerlines will be transformed so that you will see a cross instead of the hamburger like icon. The 2nd burgerline with <code>id='bar2'</code> will not be shown at all while the other 2 burgelines will be rotated 45 degrees counterclockwise (burgerline with <code>id='bar1'</code>) and clockwise (burgerline with <code>id='bar1'</code>). </p>



<pre class="wp-block-code"><code>/* static/css/style.css */
....
/* style burgerlines after on onclick event */
/* the .change class will be added onclick with classList.toggle in the JavaScript */
/* rotate first bar */
.change #bar1 {
  /* rotate -45 degrees (counterclockwise) move 15px down in Y-direction */
  transform: rotate(-45deg) translateY(15px);
}
/* fade out the second bar */
.change #bar2 {
  opacity: 0;
}
/* rotate third bar */
.change #bar3 {
  /* rotate +45 degrees (clockwise) move 15px up in Y-direction */
  transform: rotate(45deg) translateY(-15px);
}
....
</code></pre>



<p>After clicking on the burgericon, the burgerlines are transformed as described. The links of the navigation menu are displayed one below the other (float none) and aligned left.</p>



<pre class="wp-block-code"><code>/* static/css/style.css */
....
/* for screens up to 600px and after onclick event the responsive class will be added to the header */
@media screen and (max-width: 600px) {
  /* show navigation links left with no float (links shown among themselves) */
  .header.responsive a.link {
    float: none;
    display: block;
    text-align: left;
  }
}
....
</code></pre>



<p>All onclick functionalities are controlled by the javascript which is embedded in the HTML of each site template (pls see above includes/nav.pug). In the HTML, the onclick event is initiated in the burgericon link and the function myFunction is called with <code>onclick =" myFunction(this) "</code>. With the parameter <em>this</em> the entire burgericon object is transferred to the javascript function.</p>



<p>With each click on the burger icon, the class <code>change</code> is added to each burgerline or, if available, removed. This is done by the toggle() function. If <code>change</code> is set, the hamburg icon is transformed into a cross according to the specification in the css (see above). If <code>change</code> is withdrawn with a new click, the hamburger icon is displayed again.</p>



<p>But it happens even more in the javascript when you click on the hamburger icon. The element that has the id <code>responsivenav</code> is searched for and the variable<code>reponsiveNavElement</code> is assigned to this element. Is the class of the reponsiveNavElement <code>header</code> the class<code>responsive</code> is added after clicking on the hamburger icon. If the class <code>responsive</code> is set, as described above, the links of the navigation menu are displayed one below the other (float none) and aligned left. So it applies in the css <code>.header.responsive a.link {....}</code></p>



<p>In all other cases only the class <code>header</code> is set. So it applies in the css <code>.header a.link {....}</code> and the navigation links are not shown. </p>



<pre class="wp-block-code"><code>// includes/script.js

// the (burgerlines) parameter represent the DOM element that has been given to the function
function myFunction(burgerlines) {
  burgerlines.classList.toggle('change');

  var reponsiveNavElement = document.getElementById('responsivenav');
    if (reponsiveNavElement.className === 'header') {
      reponsiveNavElement.classList.add('responsive')
    } else {
      reponsiveNavElement.className = 'header';
    }
  }

</code></pre>



<p>Finally at the end of the css we define the defaults for <em>h1</em>, for our text content, the forms, the input fields and the send buttons.<br></p>



<h3 class="wp-block-heading">Summary and Outlook</h3>



<p>In this part 4 of my little node.js series we have seen how to setup a production ready environment for our express app. I showed this using a Mac OS, but in principle this setup also applies to Linux, for example.</p>



<p>The basic setup is, to put it simply, the app runs as a service on the server in the background using a Process Manager, but has no interface to the client. This client interface regulates a reverse proxy which is upstream of the app and accepts all requests and forwards them to the app, as well as the responses from the app back to the client. The communication is SSL/TLS secured.</p>



<p>At the center of the setup is a separate local MongoDB that manages all application data. In our example, these are the users, but also the sessions. I prefer to set up my own MongoDB on my server but of course it is conceivable to use a cloud-based solution or to install another database locally.</p>



<p>The express app itself uses secure HTTP response headers so that HTTP attacks like <em>clickjacking</em>, <em>MIME type sniffing</em> or some <em>smaller XSS attacks</em> on the client are made as difficult as possible. Access to personal areas of the application is secured by session-based authentication and authorization. The session-relevant data is stored in the database and not in the browser cookie, which means additional security with regard to attacks on the client. The browser cookie only contains a hash of the session ID to query the relevant user data from the database.</p>



<p>I would like to end my node.js series with Part 4. I discussed and demonstrated the basic concepts and procedures in parts 1 to 4. Of course there will be other interesting articles on the topic node.js and web programming on <a href="https://digitaldocblog.com">Digitaldocblog</a>. Just take a look.</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Node.js series Part 3. The Simple Express Blog App with MongoDB</title>
		<link>https://digitaldocblog.com/webserver/nodejs-series-part-3-the-simple-express-blog-app-with-mongodb/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Wed, 08 Apr 2020 07:00:00 +0000</pubDate>
				<category><![CDATA[Web-Development]]></category>
		<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Blog-Application]]></category>
		<category><![CDATA[Express.js]]></category>
		<category><![CDATA[MongoDB]]></category>
		<category><![CDATA[Mongoose]]></category>
		<category><![CDATA[NPM Node package manager]]></category>
		<category><![CDATA[Web-Application]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=114</guid>

					<description><![CDATA[In this Part 3 of my node.js series I rebuild the simple blog app from the last Part 2 so that a MongoDB database is used instead of a simple&#8230;]]></description>
										<content:encoded><![CDATA[
<p>In this Part 3 of my node.js series I rebuild the simple blog app from the last <a href="https://digitaldocblog.com/webserver/nodejs-series-part-2-create-a-simple-blog-app-with-expressjs/" title="Part 2">Part 2 </a>so that a <a href="https://www.mongodb.com/">MongoDB</a> database is used instead of a simple data file. To do this, a MongoDB must first be installed locally on the development system. We also use <a href="https://mongoosejs.com/">Mongoose</a> to access the database in our express blog application. I have described in detail on <a href="https://digitaldocblog.com/singleblog?article=4">Digitaldocblog</a> how to install MongoDB in my blog post <a href="https://digitaldocblog.com/singleblog?article=4">Mongodb and Mongoose on Ubuntu Linux</a>.</p>




<p>The creation of the database and the cloning of the application root directory from my <a href="https://github.com/prottlaender/node-part-3-express-blog-with-db-V2">GitHub page</a> will be briefly described in the following step-by-step instructions. This is followed by a description of the application functionality. Then I will explain more generally how data is modeled in a database-based Expressjs application with the help of mongoose and how the data is accessed so that the more basic software architecture is understood. The relevant code of our blog app is commented. In this respect, the explanations of the individual code passages can be found in the inline documentation.</p>




<h3 class="wp-block-heading">Create a MongoDB Database</h3>



<p>After the installation of MongoDB you have started the <em>mongod</em> service on your system. It is very important that you setup MongoDB with client authentication enabled. You have also created an admin user with a password on your admin default db. Pls. follow the description in my article <a href="https://digitaldocblog.com/singleblog?article=4">Mongodb and Mongoose on Ubuntu Linux</a>.</p>




<p>Then you create a database for your project and a user for your database using the <em>mongo</em> command on the console.</p>




<pre class="wp-block-code"><code>Patricks-MBP:digitaldocblog-V3 patrick$ mongo

MongoDB shell version v4.2.3
connecting to: mongodb://127.0.0.1:27017/?compressors=disabled&amp;gssapiServiceName=mongodb
Implicit session: session { "id" : UUID("4e8c592e-104d-48ef-b495-62129e446d23") }
MongoDB server version: 4.2.3

&gt; db
test

&gt; show dbs

&gt; use admin
switched to db admin

&gt; db.auth("admin", "YOUR-ADMIN-PASSWD")
1

&gt; show dbs
admin               0.000GB
config              0.000GB
local               0.000GB

&gt; use YOUR-DB-NAME
switched to db YOUR-DB-NAME

&gt; db
YOUR-DB-NAME

&gt; db.createCollection("col_default")
{ "ok" : 1 }

&gt; show dbs
admin               0.000GB
config              0.000GB
local               0.000GB
YOUR-DB-NAME	    0.000GB

&gt; db.createUser({ user: "YOUR-DB-USER", pwd: "YOUR-DB-USER-PASSWD", roles: [{ role: "dbOwner", db: "YOUR-DB-NAME" }] })
Successfully added user: {
	"user" : "YOUR-DB-USER",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "YOUR-DB-NAME"
		}
	]
}

&gt; show users
{
	"_id" : "YOUR-DB-NAME.YOUR-DB-USER",
	"userId" : UUID("ef080b98-849f-41f4-b561-a658a88e4595"),
	"user" : "YOUR-DB-USER",
	"db" : "YOUR-DB-NAME",
	"roles" : [
		{
			"role" : "dbOwner",
			"db" : "YOUR-DB-NAME"
		}
	],
	"mechanisms" : [
		"SCRAM-SHA-1",
		"SCRAM-SHA-256"
	]
}

&gt; show dbs
admin               0.000GB
config              0.000GB
local               0.000GB
YOUR-DB-NAME	    0.000GB
&gt; 

&gt; exit
bye

</code></pre>



<p>If you enter the command <em>mongo</em> on the console, the MongoDB client connects to the MongoDB service. The MongoDB client is the so-called MongoDB shell with which the user can work on the console. After the connection is established, the MongoDB shell is displayed in the terminal and with the command <em>db</em> we see the output <em>test</em>. </p>




<p>The <em>db</em> command shows the database you are currently connected to. test is the default database and is actually no longer used. The <em>show dbs</em> command shows all available databases. Since we have activated client authentication nothing is shown here because we are not yet authenticated.</p>




<p>With <em>use admin</em> we switch to admin db and authenticate ourselves with <em>db.auth</em>. After we have entered the <em>db.auth</em> command in the MongoDB shell as described, the shell shows us a 1. This shows that the authentication was successful. As admin user we can now show all databases with <em>show dbs</em>.</p>




<p>With the command <em>use YOUR-DB-NAME</em> we create the database YOUR-DB-NAME and also switch from the database admin to the database YOUR-DB-NAME. With the command <em>db.createCollection</em> I always create a default collection in YOUR-DB-NAME to display the new database YOUR-DB-NAME with the <em>show dbs</em> command in the MongoDB shell. If you simply create the database and no collection the new database is not shown with the <em>show dbs</em> command. </p>




<p>Then I create a user with <em>db.createUser</em> for my new database YOUR-DB-NAME, assign the role <em>dbOwner</em> to this user and the password <em>YOUR-DB-USER-PASSWD</em>.</p>




<p>The database YOUR-DB-NAME is now created and with <em>exit</em> we leave the MongoDB shell.</p>




<h3 class="wp-block-heading">Create the application root directory</h3>



<p>Clone the Simple Blog App from my <a href="https://github.com/prottlaender/node-part-3-express-blog-with-db-V2">GitHub page</a> and then switch to the directory <em>node-part-3-express-blog-with-db-V2</em>.</p>




<pre class="wp-block-code"><code>Patricks-MBP:~ patrick$ cd node-part-3-express-blog-with-db-V2

</code></pre>



<p>The application root directory then looks like this.</p>




<pre class="wp-block-code"><code>Patricks-MBP:node-part-3-express-blog-with-db-V2 patrick$ ls -l
total 72
-rw-r--r--   1 patrick  staff    768 9 Apr 05:19 README.md
-rw-r--r--   1 patrick  staff   2162 9 Apr 05:19 blog.js
drwxr-xr-x   6 patrick  staff    192 9 Apr 05:19 database
drwxr-xr-x   3 patrick  staff     96 9 Apr 05:19 modules
drwxr-xr-x  77 patrick  staff   2464 9 Apr 05:19 node_modules
-rw-r--r--   1 patrick  staff  22302 9 Apr 05:19 package-lock.json
-rw-r--r--   1 patrick  staff    188 9 Apr 05:19 package.json

Patricks-MBP:node-part-3-express-blog-with-db-V2 patrick$ ls -l database
total 16
drwxr-xr-x  4 patrick  staff  128  9 Apr 05:19 controllers
-rw-r--r--  1 patrick  staff  704  9 Apr 05:19 db_.js
drwxr-xr-x  4 patrick  staff  128  9 Apr 05:19 models

Patricks-MBP:node-part-3-express-blog-with-db-V2 patrick$ ls -l database/controllers
total 40
-rw-r--r--  1 patrick  staff  9464 9 Apr 05:19 blogController.js
-rw-r--r--  1 patrick  staff  5306 9 Apr 05:19 userController.js

Patricks-MBP:node-part-3-express-blog-with-db-V2 patrick$ ls -l database/models
total 16
-rw-r--r--  1 patrick  staff  1574 9 Apr 05:19 blogModel.js
-rw-r--r--  1 patrick  staff  1216 9 Apr 05:19 userModel.js

Patricks-MBP:node-part-3-express-blog-with-db-V2 patrick$ ls -l modules
total 8
-rw-r--r--  1 patrick  staff  175  9 Apr 05:19 logger.js

Patricks-MBP:node-part-3-express-blog-with-db-V2 patrick$ 

</code></pre>



<p>Read the README.md. After you run <em>npm install</em> You must adapt your database connection string and rename <em>database/db_.js</em> into <em>database/db.js</em>. Your database connection string ist as follows.</p>




<pre class="wp-block-code"><code>mongodb://YOUR-DB-USER:YOUR-DB-USER-PASSWD@localhost:27017/YOUR-DB-NAME

</code></pre>



<p>The application should run with <em>npm blog.js</em>. The application code is explained and commented inline. In addition, I will explain details in the following chapters.</p>




<h3 class="wp-block-heading">How does the application work ?</h3>



<p>The application looks like a very simple web application but already contains a lot of program logic. This program logic is mainly contained in the two user and blog controllers. </p>




<p>The <strong>application data</strong> are <em>users</em> and <em>blogs</em>. These data are stored in a MongoDB and can be created, changed or deleted by the application. When data is entered, i.e. when data sets are created and when data fields are changed, <strong>input validation</strong> takes place. This input validation is based on the mongoose built in input validation and especially for string values on the mongoose custom validation, whereby it is checked whether the input of a string corresponds to a defined regular expression (<a href="https://regex101.com/">regex for ECMA Java Script</a>).</p>




<p>The primary identifier for users is the email address. The <strong>users must be unique</strong> and can only be created if the email address is not already assigned to an existing user.</p>




<p>The users can be blog authors and therefore they have a reference to each blogid whose author they are. Whenever a blog post is created, the application checks whether a user with the specified email address already exists and whether the email address already belongs to an existing user. If the user does not exist or the specified email address belongs to an existing user, the blog post cannot be created. If a user exists and it is also his own email address, the new blog post can be created and the author&#8217;s name, first name and email address are stored on the blog in the database. In parallel the blogid of the new blog post is stored as reference on the user object.</p>




<p>We thus have a <strong>reference from users to their blogs where they are authors</strong> and a reference from blogs to the users or blog authors. When creating a blog, it is a condition that the author exists as a user in the database, but it may still be that we have blogs with authors in the database for which there is no longer a user. This is because it is possible to <strong>delete a user without deleting all blogs where the user is the author</strong>. Therefore it may still be that we have blogs with authors in the database for which there is no longer a user.</p>




<p>Consequently, when a blog is deleted, it is checked whether the author exists as a user. If the user no longer exists, the blog entry will still be deleted. If there is a user in the database, the blog post is deleted and the reference blogid on the user is deleted at the same time.</p>




<p>But we can also <strong>update the email address of a user</strong>. This is a very realistic use case because email addresses can change for users and consequently a user would like to change their changed email address accordingly in our application. Because the email address is the unique identifier of a user, we have to change the email address both on the user and on all blogs where the user is entered as the author. </p>




<p>Our application can also display blogs. All blogs are displayed or only the blogs for a specific year or month or only for a defined date. In order to display the blogs in this way, the blog data are selected accordingly from the database.</p>




<p>In addition, the application also displays a homepage and an about page, which have no further significance.</p>




<h3 class="wp-block-heading">Data Models</h3>



<p>To organize our application data in the <a href="https://www.mongodb.com/">MongoDB</a> we define so called models using <a href="https://mongoosejs.com/">Mongoose</a>. Models contain the essential data structure and data definitions that a database collection should have. Our database will have 2 collections one for the <em>users</em> and one for the <em>blogs</em>.</p>




<p>For each database collection exactly one model is defined in a seperate file. In our example, we need one model for each of the two collections <em>col_users</em> and <em>col_blogs</em>. </p>




<p>For <em>col_users</em> the model is defined in the file </p>




<ul class="wp-block-list">
	<li><em>database/models/userModel.js</em><br></li>
</ul>



<p>for the <em>col_blogs</em> the model is defined in the file </p>




<ul class="wp-block-list">
	<li><em>database/models/blogModel.js</em>.<br></li>
</ul>



<p>I would like to show a standard model here.</p>




<pre class="wp-block-code"><code>// model.js
....

mongoose = require('mongoose')
var Schema = mongoose.Schema

var dataSchema = new Schema ({ key1:..., key2:..., ..., keyN:...}) 

var Data = mongoose.model('col_data', dataSchema)

module.exports = Data

</code></pre>



<p>With <strong>mongoose.Schema</strong> we create a database Schema. With <strong>new Schema(&#8230;)</strong> we define exactly what the Schema looks like and which keys or data fields are defined for each data object that will be stored in the collection. The new Schema is stored in a variable that typically describes the purpose of the new Schema (userSchema or blogSchema in our app). With <strong>mongoose.model()</strong> we create the model and map it to the database collection. The first argument in the <em>mongoose.model()</em> function is the singular name of the database collection. So if we create the model with <em>col_data</em> mongoose is looking for <em>col_datas</em> collection in the database. The second argument in the callback is the new Schema that we defined before. This model will be stored in the Variable <em>Data</em> and exported with <strong>module.exports</strong>.<br/></p>




<p>It is extremely important that all input data are validated before the data is saved in the database. This ensures that the application only processes validated data.</p>




<p>In the model definition of or blog application, I mark all <strong>auto data fields</strong> with a preceding underscore. With auto data fields, the value is either created by the database (like the id) or a default value is always set (like created). This means that these data fields are never manipulated by data input.</p>




<p><strong>Input data fields</strong> are subject to mongoose built-in validation. The topic of input validation is described in great detail in the <a href="https://mongoosejs.com/docs/validation.html">mongoose documentation</a>. Nevertheless, I would like to go into a few points of input validation here.</p>




<p>Basically the specification of validation properties takes place in the database Schema definition per key of the data object. We have built-in validation properties or short validators like <em>required</em>, <em>minlength</em> or <em>maxlength</em> but also the option to define a custom validation function using the property <em>validate</em>. Here in the example below the custom validation function of the key &#8222;name&#8220; checks the input for the match with a <a href="https://regex101.com/">regex</a>. If the input value does not correspond to the <a href="https://regex101.com/">regex</a>, the function return  false and the validation has failed. If the function return true, the data can be saved and the validation was successful.</p>




<pre class="wp-block-code"><code>// model.js

....

mongoose = require('mongoose')
var Schema = mongoose.Schema

var dataSchema = new Schema ({ 

 // define the auto data fields
  _id: {
    type: Schema.ObjectId,
    auto: true
  },

  _created: {
    type: Date,
    default: Date.now()
  },

  // define the input data fields
  name: {
    type: String,
    required: true,
    // input string validation function
    // input sting must match regex criteria
    validate: function(name) {
      return /^[a-zA-Z0-9üÜäÄöÖ.’-]+$/.test(name)
    }
  },  
....

})

....

</code></pre>



<h3 class="wp-block-heading">Data Controllers</h3>



<p>To organize our application data accesses and data manipulations using Mongoose we define so called data controllers in separate files. </p>




<p>In principle, each <em>data model</em> that we have defined in our <em>data model files</em> are representing a <em>data perimeter</em>. A data perimeter can be accessed via so-called <em>controller modules</em> which are defined in separate <em>controller files</em>. These controller modules are in turn defined as <em>controller functions</em> in the corresponding <em>data controller</em>. </p>




<p>These controller functions define the <em>data operations</em> like search queries, create, update and delete data within the controller functions. These data operations are defined in a try &#8230; block and if an error occurs when accessing the data, this error is forwarded to the catch &#8230; block. This catch block then takes over the error handling. I do this try catch thing for all operations except for save(). With save() errors are handled directly in the callback.</p>




<p>In addition to the req and res parameter, the controller functions also receive next as a parameter. This is required to call the default error handler of the server which is defined in our main application file blog.js. </p>




<p>If the request cannot be ended with a response due to an <strong>error</strong> accessing the data, then the request will be passed to the catch block. The catch block call the next(error) function with the error as parameter in order to pass control to the <strong>default error handler</strong> to respond to the request. </p>




<p>If the request cannot be ended with a response due to an <strong>err</strong> accessing the data, then the request will also be passed to the catch block. But the catch block then call an <strong>individual error handler</strong> to respond to the request. </p>




<p>In our application we have the <em>user data perimeter</em> and the <em>blog data perimeter</em> and for each of these different data perimeters there is a separate data controller.</p>




<h4 class="wp-block-heading">User Controller</h4>



<p>The <strong>user data controller</strong> exists to control data access and data manipulations for the <em>user data perimeter</em>.</p>




<ul class="wp-block-list">
	<li>database/controllers/userController.js<br></li>
</ul>



<p>The user data controller contain the following <strong>user controller modules</strong> and functions.</p>




<h4 class="wp-block-heading">createUser</h4>



<p>The createUser function create a new User object in the database. We try first to find a user with the given email address. </p>




<p>If an error occurs here in the query, the request error is passed to the default error handler via catch block. If no error occurs, either a user is found or not. If a user is found, the user cannot be created with createUser and the response is 400 bad request. </p>




<p>If no user is found, the user can be created. This means that the newUser object is saved and the input data is validated when it is saved. If an input data validation error occurs, we will respond with 400 bad request. otherwise there is a 200 ok response and the new user object has been successfully created in the database.</p>




<h4 class="wp-block-heading">updateUserEmail</h4>



<p>With the updateUserEmail function, the email address of a user object can be changed. In parallel also the author email will be adapted on the blog objects where the user is author of blogs. </p>




<p>First the user is searched for with his existing email address. If the function runs on an error, the request and the error are passed on to the default error handler with catch. </p>




<p>If no user is found, we respond with 400 bad request. If, on the other hand, a user is found, the email address can be updated by saving the user object with the new email address. When saving, the input validation takes place again. Only if the input validation was successful the storage of the user object is completed.</p>




<p>Now the email address must be updated on the blogs where the user is the author. </p>




<p>For this we are looking for all blogs where the author email corresponds to the existing email address of the user. If the search runs for an err, we will respond with an individual 502 bad gateway this time because the request has been partially processed up to this point. The email address on the user object has already been updated, but the data for updating the author email addresses on the blogs cannot be queried. </p>




<p>If there is no err in the blog query, the user may not be the author of any blog. Then we answer with 200 ok and confirm that only the email address on the user object has been changed. </p>




<p>If, on the other hand, the user is the author of blogs, we use the updateMany() function to update the email address of any blog found.</p>




<p>This updateMany() function receive a filter object with the previously existing email address of the user as first parameter and then the update object to replace this existing email address with the new email address of the user. This operation is executed on any blog found in the database and is also carried out in a try block. If an err occurs, the request is passed on for individual error handling to the catch block. Here in the catch block we answer with a 502 bad gateway because the update on the user object has been already performed successfully but the update of the author email address on the blogs found was not successful because the updateMany() operation failed. </p>




<p>If the blog update process was successful, we respond with 200 ok. In this case the email address has been changed on the user object and also on his blogs.</p>




<h4 class="wp-block-heading">removeUser</h4>



<p>With removeUser function a user object can be removed from the database. We try first to find the user that should be removed with the given email address. If an error occurs, the request error is passed to the default error handler via catch block. </p>




<p>If no error occurs, either a user is found or not. </p>




<p>If no user is found, the user cannot be removed and the response is 400 bad request. </p>




<p>If the user to be removed has been found we try to delete this user from the database using the deleteOne() function. If an error occurs here, the request error is also passed to the default error handler via catch block. </p>




<p>If the deletion was successful we respond with 200 ok.</p>




<h4 class="wp-block-heading">Blog Controller</h4>



<p>The <strong>blog data controller</strong> exists to control data access and manipulations for the <em>blog data perimeter</em>.</p>




<ul class="wp-block-list">
	<li>database/controllers/blogController.js<br></li>
</ul>



<p>The blog data controller contain the following <strong>blog controller modules</strong> and functions.</p>




<h4 class="wp-block-heading">createBlog</h4>



<p>The createBlog module create a new blog object in the database and update the new blog id reference on the author user object. </p>




<p>The new blog object can only be created if the author data email, first- and lastname match with a user object in the database. So the author must already exist as a user in the database. </p>




<p>We try first to find a user with the author email address. If an error occurs, the request error is passed to the default error handler via catch block. If no error occurs, either a user is found or not. </p>




<p>If no user is found, we respond with 400 bad request. </p>




<p>If a user is found, it is checked whether the provided first name or last name does not match the first name or last name of the user in the database found with the given email address. If one of the names does not match, the condition is false and we send a 400 bad request. </p>




<p>If both names are the same, there is a perfect match of email, first name and last name and the provided author data are validated. Then the blog object can be saved and the blog input data will be validated.</p>




<p>If an input data validation error occurs, we will respond with 400 bad request. </p>




<p>In any other case the new blog has been saved to the database and we try to update the user object by adding the new blog id on the user object as a blog reference. Therefore, we try to use the updateOne() function to find the user object based on the author&#8217;s email address to add the blog id of the newly created blog object as a blog reference.</p>




<p>This operation is carried out in a try block and if an err occurs, it is passed on to the catch block. In case of an err we answer with a 502 bad gateway. The blog object is created but the user object is still not updated because the updateOne() operation was not successful. If the updateOne() operation was successful we respond with 200 ok.</p>




<h4 class="wp-block-heading">removeBlog</h4>



<p>The removeBlog module remove a blog object from the database and update the user object by removing the relevant blog id reference on the author user object. </p>




<p>We try to find a blog object that should be removed based on the blog title. If an error occurs during that query the default error handling function will be invoked via the catch block and next(error). In case of no error the query return one blog object or null which means that no blog object has been found. </p>




<p>If no blog has been found we respond with 400 bad request. </p>




<p>If the query was successful and we found a blog object we try to find a user based on the author email address. If an error occurs the default error handling function will be invoked via the catch block. In case of no error the query return one user object or no user object.</p>




<p>If we have no user object found that mean that the author of the blog is no longer a user in our application. In this case we try to remove only the blog using the deleteOne() function. If this blog deletion function fail we have no success and an error. We catch this error in the catch block to invoke the default error handler function. If the deleteOne() function can be executed successfully, we respond with ok 200. The blog object with the title has been deleted. </p>




<p>If we have found a user object this means that this user is the author of the blog object we want to delete. In this case we first try to remove the blog object using the deleteOne() function and catch the error in the catch block to invoke the default error handler function when the execution of deleteOne() fail. </p>




<p>When the blog deletion was successful, we also try to delete the blog id as a reference on the user object using the updateOne() function. If updateOne throw an err we go ahead with catch and respond with 502 bad gateway as the blog could be removed but the user object update failed. </p>




<p>If the user update of the user object was successful we respond with 200 ok.<br/></p>




<h4 class="wp-block-heading">returnAllBlogs</h4>



<p>The returnAllBlogs module is a function that searches all blog objects in the database and returns them in an array blogs when the requested url is /blogs. If the requested url is different than the next() function will be called to transfer to the next request handler in the row.</p>




<p>We try to search for all blog objects using the find() function. This find function return an error in case the query fail. In this case the catch block will invoke the default error handler function. In case the query was successful the function return an array blogs including the blog objects. </p>




<p>In case the array is empty or better the length is equal to 0 no blog object has been found in the database and we return 200 ok no blogs found. </p>




<p>In case there are blog objects available we create for each blog object found a dataset object of blog attributes containing the title, the author (including name, firstname and email) and the date, push this dataset into a blog array and return this blog array with 200 ok.</p>




<h4 class="wp-block-heading">returnYearBlogs</h4>



<p>The returnYearBlogs module is a function that searches blog objects of a certain year in the database and returns them in an array blogs when the requested url is /blogs/year. If the requested url is different than the next() function will be called to transfer to the next request handler in the row.</p>




<p>We try to search for all blog objects in a year using the find() function and return an error in case the query fail. In this case the catch block will invoke the default error handler function. </p>




<p>In case the query was successful the function return a query array blogs including the blog objects found for the relevant search criteria. In case no blog object has been found (length of blogs is equal to 0) in the database we return 200 ok no blogs found. </p>




<p>In case there are blog objects available we create for each blog object found a dataset object of blog attributes containing the title, the author (including name, firstname and email) and the date, push this dataset into another blog array and return this blog array with 200 ok.</p>




<h4 class="wp-block-heading">returnMonthBlogs</h4>



<p>The returnMonthBlogs module is a function that searches blog objects of a certain year and month in the database and returns them in an array blogs when the requested url is /blogs/year/month. If the requested url is different than the next() function will be called to transfer to the next request handler in the row.</p>




<p>We try to search for all blog objects in a year and month using the find() function and return an error in case the query fail. In this case the catch block will invoke the default error handler function. </p>




<p>In case the query was successful the function return a query array blogs including the blog objects found for the relevant search criteria. In case no blog object has been found (length blogs is equal to 0) in the database we return 200 ok no blogs found. </p>




<p>In case there are blog objects available we create for each blog object found a dataset object of blog attributes containing the title, the author (including name, firstname and email) and the date, push this dataset into another blog array and return this blog array with 200 ok.</p>




<h4 class="wp-block-heading">returnDateBlogs</h4>



<p>The returnDateBlogs module is a function that searches blog objects of a certain date in the database and returns them in an array blogs. </p>




<p>As this is the last routing handler in the app.get() routing function where the path is defined as /blogs/:year?/:month?/day? the requested url matches this definition or not. </p>




<p>In case the requested url is something like /blogs/year/month/day/<strong>else</strong> the url is not defined and then the default route error handler will be invoked and return 404 Not found. </p>




<p>In case the requested url path match /blogs/year/month/day we try to search for all blog objects for the date using the find() function and return an error in case the query fail. In case of an error the catch block will invoke the default error handler function. </p>




<p>In case the query was successful the function return a query array blogs including the blog objects found for the relevant search criteria. </p>




<p>In case no blog object has been found (length blogs is equal to 0) in the database we return 200 ok no blogs found. </p>




<p>In case there are blog objects available we create for each blog object found a dataset object of blog attributes containing the title, the author (including name, firstname and email) and the date, push this dataset into another blog array and return this blog array with 200 ok.</p>




<h3 class="wp-block-heading">Data operations in detail</h3>



<p>Our application perform the following data operations.</p>




<ul class="wp-block-list">
	<li>create data</li>
	<li>search and return data</li>
	<li>update data</li>
	<li>delete data <br></li>
</ul>



<p>These operations will be implemented in the controller modules. We use the following functions to perform these operations.</p>




<ul class="wp-block-list">
	<li>save()</li>
	<li>find()</li>
	<li>findOne()</li>
	<li>updateOne()</li>
	<li>updateMany()</li>
	<li>deleteOne()<br></li>
</ul>



<p>If we want to <strong>create</strong> a new Data object we call <em>new Data(  &#8230;  )</em> and apply the key  specifications according to the Schema defined in the model. </p>




<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: Please note that we are using new Data(  &#8230;  ) here because we export <em>Data</em> in the above model example definition. If you look in the model files of our blog app, you will notice that I export in the user model <em>User</em> and in the blog model <em>Blog</em>. Consequently, for example, when creating a new user object in the user data controller, the call new User(  &#8230;  ) is made.</p>
</blockquote>



<p>The function <strong>save()</strong> receive a callback function with <em>error</em> as first parameter and <em>data</em> as second parameter. Within the callback we can work with <em>error</em> as the error object and with <em>data</em> as the data object that will be saved in the database. With save(), error handling takes place directly in the call back function using the <em>if (error) &#8230; else &#8230;</em> block.</p>




<pre class="wp-block-code"><code>// controller.js

newData = new Data({key1:..., key2:..., ..., keyN:...})

newData.save(function(error, data) {
	if (error) {

	// do something when an error occurs

	} else {

	// do what you need to do when the creation was successful

	} 
})

</code></pre>



<p>If we want to <strong>search and return data</strong> we call <em>find()</em> or <em>findOne()</em>. </p>




<p>If we expect that a search return several data objects as a result we are using the <em>find()</em> function. </p>




<p>The <em>find()</em> function is covered with the <em>try &#8230; catch &#8230;</em> blog. </p>




<p><strong>find()</strong> receive as first parameter a filter object as search criteria and as second parameter a callback function with <em>error</em> as first parameter and <em>data</em> as second parameter. Because we use <em>try catch</em> error handling is regulated in the catch block and we respond to the request in case of an error within this catch block. </p>




<p>Within the callback we can work with <em>data</em> which is an array containing all the data objects that have been found in the database. In case no data objects were found the <em>data array</em> is empty. In case the function fail we catch the error.</p>




<p>Suppose you want to search in <strong>Data</strong> perimeter for data objects that have a specificName. Then we pass the filter object  name: specificName  and then the callback function(error, data) &#8230; to the Data.find() function. </p>




<pre class="wp-block-code"><code>// controller.js

// create the filter object
var searchDataWithName = { name: specificName }

try {

	Data.find(searchDataWithName, function(error, data) {

	if (data.length == 0) {

	// do something when no data have been found 

	} else {

	// do something with the data array that have been found 

	}

	})

} catch (error) {

// do something when an error occurs

}

</code></pre>



<p>If a search should return only one data object as a result we are using the <em>findOne()</em> method.</p>




<p>The <em>findOne()</em> function is covered with the <em>try &#8230; catch &#8230;</em> blog. </p>




<p><strong>findOne()</strong> receive as first parameter a filter object as search criteria and as second parameter a callback function with <em>error</em> as first parameter and <em>data</em> as second parameter. Because we use <em>try catch</em> error handling is regulated in the catch block and we respond to the request in case of an error within this catch block. </p>




<p>Within the callback we can work with <em>data</em> which is the object that has been found in the database. In case no data object has been found the value of data is null. In case the function fail we catch the error.</p>




<p>Suppose you want to search in <strong>Data</strong> perimeter for one data object that has a specificName. Then we pass the filter object  name: specificName  and then the callback function(error, data) &#8230; to the Data.findOne() function. </p>




<pre class="wp-block-code"><code>// controller.js


// create the filter object
var searchDataWithName = { name: specificName }

try {

Data.findOne(searchDataWithName, function(error, data) {

	if (!data) {

	// do something when no data have been found
	
	} else {

	// do something with the data in the data object that have been found 

	} 

	})

} catch (error) {

// do something when an error occurs

}



</code></pre>



<p>If we want to <strong>update</strong> an existing Data object we search the Data object, apply the changes and then call <em>save()</em>. We can also use <em>updateOne()</em> or <em>updateMany()</em>. </p>




<p>If we do search and <strong>save()</strong> the data object first must be found in the database and will then be returned using the <em>findOne()</em> function. Then we can apply the changes to one or more data attribute values i.e. update the email address of a user and then call save() to save the updated data object back into the database. This procedure has the advantage that input validation is used with save() and is therefore recommended whenever we receive changes directly as input values and these input values must be validated. </p>




<pre class="wp-block-code"><code>// controller.js

// create the filter object
var searchDataWithName = { name: specificName }

try {

	Data.findOne(searchDataWithName, function(error, data) {

	if (!data) {

	// do something when no data have been found
	
	} else {

		// specify the update value
		var updateEmail = newEmail
	
		// update the value
		data.email = updateEmail

		data.save(function(err, updatedData) {

			if (err) {

			// do something when an err occurs
			
			} else {

			// do something when the update was successful

			}
		})
	} 
	})

} catch (error) {

// do something when an error occurs

}

</code></pre>



<p>If we use updateMany() or updateOne() the data objects or the one data object will be looked up and updated in the database in one step. The data object(s) will <strong>not be returned</strong>. This makes processing faster and saves network resources.</p>




<p>The functions updateMany() and updateOne() are both covered with the <em>try &#8230; catch &#8230;</em> blog. </p>




<p><strong>updateMany()</strong> receive as first parameter a filter object as search criteria, as second parameter the update object and as third parameter a callback function with <em>error</em> as first parameter and <em>result</em> as second parameter. Because we use <em>try catch</em> error handling is regulated in the catch block and we respond to the request in case of an error within this catch block. </p>




<p>Within the callback we can work with <em>result</em> which is an object containing data about the processing result  n: 1, nModified: 1, ok: 1 . This mean n objects found, n objects modified and the processing was ok (ok: 0 if the processing failed).</p>




<p>Suppose you want to find in <strong>Data</strong> perimeter for data objects that have a specificName and update all objects found with specificNewName. Then we pass the filter object  name: specificName , the update object  name: specificNewName  and then the callback function(error, result) &#8230; to the Data.updateMany() function. </p>




<pre class="wp-block-code"><code>// controller.js

// create the filter object
var searchDataWithName = { name: specificName }

// create the update object
var updatedName = { name: specificNewName }

....

try {
      
   Data.updateMany(
      	
     searchDataWithName, 
      	
     updatedName, 
      	
     function(err, result) {
      
       // do something with the result
          
     })
     
} catch (err) {
     	
// do something with the err

}

....

</code></pre>



<p><strong>updateOne()</strong> also receive as first parameter a filter object as search criteria, as second parameter the update object and as third parameter a callback function with <em>error</em> as first parameter and <em>result</em> as second parameter. Because we use <em>try catch</em> error handling is regulated in the catch block and we respond to the request in case of an error within this catch block. </p>




<p>Within the callback we can work with <em>result</em> which is an object containing data about the processing result  n: 1, nModified: 1, ok: 1 . This mean n objects found, n objects modified and the processing was ok (ok: 0 if the processing failed).</p>




<p>Suppose you want to find in <strong>Data</strong> data perimeter for one data object that has a specificName and you want to update this single object with specificNewName. Then we pass the filter object  name: specificName , the update object  name: specificNewName  and then the callback function(error, result) &#8230; to the Data.updateOne() function. </p>




<pre class="wp-block-code"><code>// controller.js

// create the filter object
var searchDataWithEmail = { name: specificEmail }

// create the update object
var updatedEmail = { name: specificNewEmail}

try {
   
   Data.updateOne(
      	
     searchDataWithEmail, 
      	
     updatedEmail, 
      	
     function(err, result) {
      
     // do something with the result
          
    })
     
} catch (err) {

// do something with the err

}

....

</code></pre>



<p>If we want to <strong>remove</strong> an existing Data object we search for the data object using <em>findOne()</em> to get the data object returned, and then call <em>deleteOne()</em>. </p>




<p>We do this 2 step approach i.e. in <em>removeBlogs</em> because we use the returned blog object to determine  first the id of the blog object and then use it to find and update the user object. You can do this in one single step only by using <em>deleteOne()</em>, but take into account that you will not receive a data object with which further queries or updates can be carried out.</p>




<p><strong>deleteOne()</strong> receive as first parameter a filter object as search criteria and as second parameter a callback function with <em>error</em> as first parameter and <em>result</em> as second parameter. Because we use <em>try catch</em> error handling is regulated in the catch block and we respond to the request in case of an error within this catch block. </p>




<p>Within the callback we can work with <em>result</em> which is an object containing data about the processing result  n: 1, deletedCount: 1, ok: 1 . This mean n objects found, n objects deleted and the processing was ok (ok: 0 if the processing failed). You can use the result object in the callback to check if the object to be removed has been found or not. But this an operation we don&#8217;t need to perform because we are using the 2 step approach with findOne() and then deleteOne().</p>




<pre class="wp-block-code"><code>....

if (result.n == 0) {
  // do something if no object to be removed has been found
} else {
  // do something when the delete was successful
}

....

</code></pre>



<p>Suppose you want to find in <strong>User</strong> data perimeter for a data object that has a specificName and you want to remove this object from the database. Then we pass the filter object  name: specificName  and then the callback function(error, result) &#8230; to the User.deleteOne() function. </p>




<pre class="wp-block-code"><code>// contoller.js

/ create the filter object
var searchDataWithName = { name: specificName }

try {

	User.findOne(searchDataWithName, function(error, user) {

	if (!user) {

	// do something when no data have been found
	
	} else {
	
		try {
		
		User.deleteOne(searchDataWithName, function(error, result) {
		
			// do something when the delete was successful
		
		})
		
		
		} catch (error) {
		
		// do something when an error occurs		
		}		
}
})


} catch (error) {

// do something when an error occurs

}

</code></pre>



<h3 class="wp-block-heading">HTTP request Routing</h3>



<p>In our application main file <em>blog.js</em> we define routes to process HTTP POST and HTTP GET requests.</p>




<p><strong>HTTP POST requests</strong> are routed using the app.post(&#8218;routingPath&#8216;, routingHandler) method. </p>




<p>During the <strong>create-, update- and remove data</strong> operations app.post() routes are called and the request and response objects are transferred to the corresponding routingHandler to take over the request handling. The request body contain all relevant input data to process the request. </p>




<p>The routingHandler then calls the controller module and transfers the request and response object to this module. Then the module takes over the complete processing of the request and finally sends the response. </p>




<p>So for example if an HTTP POST request is made to the path <em>/createusers</em> the app.post(&#8218;/createusers&#8216;, userController.createUser) route is called and the <em>createUser</em> module take over the complete processing. </p>




<pre class="wp-block-code"><code>// blog.js

....

// load database controllers
const userController = require('./database/controllers/userController');
const blogController = require('./database/controllers/blogController');

....

// define routes (routing table)

....

app.post('/createusers', userController.createUser)

app.post('/updateuseremail', userController.updateUserEmail)

app.post('/removeusers', userController.removeUser)

app.post('/createblogs', blogController.createBlog)

app.post('/removeblogs', blogController.removeBlog)

....

</code></pre>



<p>You have to imagine it in reality with a real web application like this: On a website there is an HTML form and the end user of the application can enter user data such as last name, first name and email address to add for example a new user in the application or in the application database. The moment the user of the application clicks on the send button, the data entered in the form is transferred to the route /createusers with HTTP POST which means that the relevant app.post(&#8218;/createusers&#8216;, userController.createUser) function is called to handle this HTTP POST request. </p>




<p>Unfortunately we are not yet dealing with a real web application because there is no HTML form until now. This is exactly where <a href="https://www.postman.com/">Postman</a> comes in to test an HTTP POST action. In principle, Postman replaces the form. With Postman I can (amongst other things) transfer HTTP POST requests to the application and thus I also transfer the request body. It is therefore necessary that you familiarize yourself with <a href="https://www.postman.com/">Postman</a> to test the applications app.post() routes.</p>




<p><strong>HTTP GET requests</strong> are routed using the app.get(&#8218;routingPath&#8216;, routingHandler) method.</p>




<p>So for example if an HTTP GET request is made to the path <em>/</em> the app.get(&#8218;/&#8216;, routingHandler) route is called and the <em>routingHandler</em> take over the complete processing and send directly the response. The same happens when an HTTP GET request is made on the route <em>/about</em>. This is easy and not spectacular.</p>




<pre class="wp-block-code"><code>// blog.js

....

// load database controllers
const userController = require('./database/controllers/userController');
const blogController = require('./database/controllers/blogController');

....

// define routes (routing table)

....

app.get('/', (req, res) =&gt; {
  res.send('Hello, this is the Blog Home Page.')
})

app.get('/about', (req, res) =&gt; {
  res.send('Hello, this is the Blog About Page.')
})

....


</code></pre>



<p>It is a bit more special when an HTTP GET request is made to the route app.get(&#8218;/blogs/:year?/:month?/:day?&#8216;, &#8230;. ). </p>




<p>The route definition here provides so-called request parameters. The <em>/blogs</em> route can be parameterized with the parameters year, month and day. The question mark behind each parameter indicates that the parameter is optional.</p>




<p>The request is forwarded to several routing handlers. </p>




<p>The first routing handler assigns the request url and the request parameters year, month and day to variables and calls next() to hand over the request to the next routing handler in the row.</p>




<p>The next routingHandler is <em>blogController.returnAllBlogs</em>. This routingHandler calls the controller module <em>returnAllBlogs</em> and transfers the request and response object to this module. Then the module takes over the complete processing of the request and finally sends an array containing all blogs as response <strong>if the request url is equal to /blogs</strong> otherwise next() is called to hand over the request to the next routing handler in the row.</p>




<p>The next routingHandler is <em>blogController.returnYearBlogs</em>. This routingHandler calls the controller module <em>returnYearBlogs</em> and transfers the request and response object to this module. Then the module takes over the complete processing of the request and finally sends an array containing all blogs in the specified year as response <strong>if the request url is equal to /blogs/year</strong> otherwise next() is called to hand over the request to the next routing handler in the row.</p>




<p>The next routingHandler is <em>blogController.returnMonthBlogs</em>. This routingHandler calls the controller module <em>returnMonthBlogs</em> and transfers the request and response object to this module. Then the module takes over the complete processing of the request and finally sends an array containing all blogs in the specified year and month as response <strong>if the request url is equal to /blogs/year/month</strong> otherwise next() is called to hand over the request to the next routing handler in the row.</p>




<p>The next and last routingHandler is <em>blogController.returnDateBlogs</em>. This routingHandler calls the controller module <em>returnDateBlogs</em> and transfers the request and response object to this module. Then the module takes over the complete processing of the request and finally sends an array containing all blogs from the specified date.</p>




<p>Any other route will end up in an error 404 Bad request. </p>




<pre class="wp-block-code"><code>// blog.js

....

// load database controllers
const userController = require('./database/controllers/userController');
const blogController = require('./database/controllers/blogController');

....

// define routes (routing table)

....

app.get('/blogs/:year?/:month?/:day?',

        (req, res, next) =&gt; {
          url = req.url
          year = req.params.year
          month = req.params.month
          day = req.params.day

          next()
        },

        blogController.returnAllBlogs,
        blogController.returnYearBlogs,
        blogController.returnMonthBlogs,
        blogController.returnDateBlogs

      )

....

</code></pre>



<h3 class="wp-block-heading">Status Handling</h3>



<p>In our application we work with the following HTTP Status codes.</p>




<ul class="wp-block-list">
	<li>code 200: status: Ok. Request completed.</li>
	<li>code 500: status: Internal Server Error. Request failed due to server error.</li>
	<li>code 400: status: Bad Request. Request failed due to wrong input data.</li>
	<li>code 404: status: Not Found. Request failed due to wrong route.</li>
	<li>code 502: status: Bad Gateway. Request partly completed but not fully completed due to missing data.<br></li>
</ul>



<h3 class="wp-block-heading">Summary and Outlook</h3>



<p>In this Part 3 of my node.js series I explained how to setup our express blog app using a MongoDB database. We learned how to create data models and controllers to control access to the data. But the blog app has still no HTML frontend or no HTML template rendering is used.</p>




<p>So in the next <a href="https://digitaldocblog.com/mac/nodejs-series-part-4-express-website-with-authentication-and-authorization-in-a-mac-production-environment/" title="Part 4">Part 4</a> of my node.js series I will introduce how to extend the blog app with an HTML interface. Therefore we implement the use of the template engine <a href="https://pugjs.org/api/getting-started.html">Pug</a>. With <a href="https://pugjs.org/api/getting-started.html">Pug</a> we can create an HTML interface for our blog application.</p>




]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Node.js series Part 2. Create a Simple Blog App with Express.js</title>
		<link>https://digitaldocblog.com/webserver/nodejs-series-part-2-create-a-simple-blog-app-with-expressjs/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Sat, 21 Mar 2020 08:00:00 +0000</pubDate>
				<category><![CDATA[Web-Development]]></category>
		<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Express.js]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[NPM Node package manager]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=111</guid>

					<description><![CDATA[Chapter 1: Introduction In Part 1 of my node.js series we have learned how to easily build a web server with node.js on-board resources. Lastly we installed the Express.js module&#8230;]]></description>
										<content:encoded><![CDATA[
<h3 class="wp-block-heading">Chapter 1: Introduction</h3>



<p>In <a href="https://digitaldocblog.com/webserver/nodejs-series-part-1-create-a-simple-web-server-with-nodejs/" title="Node.js series Part 1. Create a Simple Web-Server with Node.js">Part 1</a> of my node.js series we have learned how to easily build a web server with node.js on-board resources. Lastly we installed the Express.js module and configured simple routes as application end points that respond to http client requests. In this part 2 I would like to take a closer look at the most important functionalities of Express.js with you.</p>




<p>In connection with Express, terms like <em>framework</em> or <em>web api</em> keep coming up. Everyone says Express is a <em>node.js web application framework</em> which is absolutely true. But what does this mean? I would like to explain this here with my own words.</p>




<p>Developers install the Express module in the application root directory and integrate the module with the require() function in the application main file. This is done with the following command line.</p>




<pre class="wp-block-code"><code>var express = require('express')
var app = express()
</code></pre>



<p>The express module is first referenced to the variable <em>express</em> with the require() function and an express() function is exported from the express module. The express() function generates an <em>express application</em> which is stored in the variable <em>app</em>. From this moment on, the developer has a whole range of <em>Express Features</em> available that can be used in the web application that is to be built. The range of express features is provided by the <strong><a href="https://expressjs.com/">Express Framework</a></strong>.</p>




<p>The features provided relate to the development of web applications. That means all these features from the Express Framework are designed to program the response behavior of a web server to requests from a client exactly as it is desired. These features are essentially about <em>Routing-</em> and so-called <em>Middleware-functionalities</em>.</p>




<p>The <strong>Routing</strong> tools in the Express Framework enable the developer to program the response behavior of the web server to the request for a special <em>application end point</em>. An application end point is a route that is e.g. defined as &#8222;/&#8220; or &#8222;/about&#8220;. The response from the server should be different here. The developer can program the end points in a very simplified way so that the request for &#8222;/&#8220; is answered by the server with the <em>Home Page</em>, the request for &#8222;/about&#8220; is answered with the <em>About Page</em>. The <strong>Request-</strong> and <strong>Response Objects</strong> are very central to the routing tools. The developer can access the properties of these objects using various methods and thus program his end points accordingly.</p>




<p>To continue the example above the server immediately responds to the client&#8217;s request and delivers either the <em>Home Page</em> or the <em>About Page</em>. If the server should process after the request and before the response some code, we are talking about <strong>Middleware</strong>. Express offers various methods to access <em>express internal middleware</em> and then switch it between the request and the response. You can also program your <em>own middleware</em> functions, which can then be integrated into the application with the require() function.</p>




<p>All in all, Express is a very powerful framework that is all about programming web servers or web applications. The Express Features serve as an <em>application programming interface</em> between the client and server and that is why these features are also called <strong>Express web api</strong>. We will take a closer look at <em>Express Routing</em> and <em>Express Middleware</em> in this Part 2 of the node.js series.</p>




<h3 class="wp-block-heading">Chapter 2: Express Routing</h3>



<p>We start here with the directory structure of the <em>application root directory</em> from Part 1 of my node.js series and explain the content of the <em>server.js</em> file.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ ls -l
total 56
drwxr-xr-x  52 patrick  staff   1664 21 Mär 08:13 node_modules
-rw-r--r--   1 patrick  staff  14292 21 Mär 11:28 package-lock.json
-rw-r--r--   1 patrick  staff    162 21 Mär 10:16 package.json
-rw-r--r--   1 patrick  staff   2435 21 Mär 17:47 server.js
Patricks-MBP:express-basic patrick$ 

</code></pre>



<p>The <em>server.js</em> file is the <em>application main file</em> and contain the code of the application. The <em>package.json</em> file contain meta data of the application or project such as which external modules or dependencies have been installed to be used by the application. The package-lock.json file contain the complete directory tree of all installed dependencies and finally the directory <em>node_modules</em> contain all dependencies or external modules or packages that have been installed.</p>




<p>At the end of part 1 we already installed the external module Express.js with <em>npm install express &#8211;save</em> and can now load this module in our application. The <em>server.js</em> file looks like this.</p>




<pre class="wp-block-code"><code>// server.js

// load http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// define the routes
app.get('/', (req, res) =&gt; {
  res.send('Hello, this is my home Page')
})

app.get('/about', (req, res) =&gt; {
  res.send('Hello, this is my about Page')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

</code></pre>



<p>We do the following with the code in the <em>server.js</em> file (from top to bottom).</p>




<ol class="wp-block-list">
	<li>load modules</li>
	<li>define routes</li>
	<li>create the server</li>
	<li>define the port the server is listening</li>
	<li>log a success message to the console when all the code runs without problems<br></li>
</ol>



<h4 class="wp-block-heading">The Routing method</h4>



<p>As you can see in the code we already defined 2 simple routes or application end points (URIs). These routes are defined with routing methods and have the following notation.</p>




<pre class="wp-block-code"><code>app.routingMethod( 'routingPath', routingHandler(req, res) =&gt; { ... })

</code></pre>



<p>In our server.js file, we created an app as an instance of the express class by calling the express() function right after we loaded the express module. Routing methods can now be attached to this app. Each routing method corresponds to an http verb such as GET, POST, PUT or DELETE. In our example, we will deal with get() and post() here. The routing method receives 2 parameters:</p>




<ol class="wp-block-list">
	<li><strong>routingPath</strong>: the path of the route end point starting from the root of the application &#8222;/&#8220;. Simply this is the path that the http request passed to the application. If the path passed meets an end point definition, the routingHandler code is executed.</li>
	<li><strong>routingHandler:</strong> the routingHandler is a callback function that receives parameters. Here in the notation above the callback receives 2 parameters. The first parameter is the request object, the second the response object. Because the parameters <em>req</em> and <em>res</em> were passed, the code has access to the methods and properties of the request and response objects. We will talk about these Objects later in this article. <br></li>
</ol>



<p>While the routingPath is a pretty logical thing, we&#8217;ll take a closer look at the routingHandler. The routingHandler contains code that is executed after a request and sends the response. </p>




<h4 class="wp-block-heading">The routing handler</h4>



<p>The routing handler is a callback function with <em>req</em> and <em>res</em> as parameters and process code after the requests and before sending the response. The response is sent with the res.send() method back to the client. </p>




<pre class="wp-block-code"><code>app.routingMethod( 'routingPath', routingHandler(req, res) =&gt; { 

... 

})

</code></pre>



<p>Here the res.send() method send a string back to the client.</p>




<pre class="wp-block-code"><code>// server.js

.........

// define the routes
app.get('/', (req, res) =&gt; {

  res.send('Hello, this is my Home Page')

})

......

</code></pre>



<p>The routing handler can also receive a function as a parameter. </p>




<pre class="wp-block-code"><code>app.routingMethod( 'routingPath', routingHandler(req, res, next) =&gt; { 

... 

})

</code></pre>



<p>Here the routing handler receive <em>req</em> and <em>res</em> but also the function <em>next()</em> as parameter. The next() function forward the processing to the next function(req, res). This final function sen the response to the client with the res.send() method. </p>




<pre class="wp-block-code"><code>// server.js

.........

app.get('/', (req, res, next) =&gt; {
  console.log('this request will be responded by next()');
  next();
}, function (req, res) {
  res.send('This is the response to the request from next()');
})

.........

</code></pre>



<p>As we have seen above the routing handler can receive <em>req</em> and <em>res</em> and <em>functions</em> as parameter. </p>




<p>You can also collect routing handler functions into an array and pass the array as parameter into the routing method. </p>




<pre class="wp-block-code"><code>app.routingMethod( 'routingPath', [array] )

</code></pre>



<p>So lets define 3 functions and pass them as array into the routing method.</p>




<pre class="wp-block-code"><code>// server.js

.........

var r1 = function (req, res, next) {
  console.log('r1 ! The request will be responded by r3');
  next();
}

var r2 = function (req, res, next) {
  console.log('r2 ! The request will be responded by r3');
  next();
}

var r3 = function (req, res) {
  res.send('r3 ! Hello, this is the response !');
}

app.get('/example', [r1, r2, r3]);

.........

</code></pre>



<p>In the example above there are 3 routing handler functions that are collected in an array. The array is passed to the routing method as a parameter. r1 and r2 each execute a console.log instruction and then pass the routing on to the next routing handler function with next(). r3 then returns the response to the client using the res.send() method.</p>




<p>You can collect functions in an array and insert them into the routing method before a last routing handler.</p>




<pre class="wp-block-code"><code>app.routingMethod( 'routingPath', [array], (req, res, next) =&gt; { 

... 

})

</code></pre>



<p>The last of these functions in the array forward the request to the last routing handler for processing. This last routing handler also contains a next() function and processes code first before finally answering the request.</p>




<pre class="wp-block-code"><code>// server.js

.........

var r1 = function (req, res, next) {
  console.log('r1 ! The request will be responded by final');
  next();
}

var r2 = function (req, res, next) {
  console.log('r2 ! The request will be responded by final');
  next();
}

var r3 = function (req, res, next) {
  console.log('r3 ! The request will be responded by final');
  next();
}

app.get('/example', [r1, r2, r3], (req, res, next) =&gt; {
    console.log('log 4 ! The request will be responded by final');
    next();
  }, function (req, res) {
      console.log('final ! This is the response to the request')
})

.........

</code></pre>



<h3 class="wp-block-heading">The response method res.send()</h3>



<p>The <em>res.send(body)</em> method send the http response back to the client. The body parameter can be </p>




<ul class="wp-block-list">
	<li>a string</li>
	<li>an object </li>
	<li>an array</li>
	<li>a buffer object. <br></li>
</ul>



<h5 class="wp-block-heading">res.send() with string response</h5>



<p>When res.send() body is a string the method sets the Content-Type to “text/html”. </p>




<p>res.send(body); body = String.</p>




<pre class="wp-block-code"><code>// server.js

.........

// define the routes
app.get('/', (req, res) =&gt; {

  res.send('Hello, this is my Home Page')

})

......

</code></pre>



<p>Start the server with <em>node server.js</em> and run <em>curl -i localhost:3000</em> in another terminal window and check the output.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ curl -i localhost:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 27
ETag: W/"1b-AX0brvVSaJSaZMvUvejQv+1wFQA"
Date: Sat, 21 Mar 2020 16:50:26 GMT
Connection: keep-alive

Hello, this is my Home Page

Patricks-MBP:express-basic patrick$ 

</code></pre>



<h5 class="wp-block-heading">res.send() with object or array response</h5>



<p>When the body is an Array or an Object, the Content-Type is application/json.</p>




<p>res.send(body); body = Object.</p>




<pre class="wp-block-code"><code>// server.js

.........

// define the routes
app.get('/', (req, res) =&gt; {

  res.send( { error: 'something wrong in my app' } )

})

......

</code></pre>



<p>Start the server with <em>node server.js</em> and run <em>curl -i localhost:3000</em> in another terminal window and check the output.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ curl -i localhost:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 37
ETag: W/"25-8fYR5HXYWF9HTHAAgk3niZYRep4"
Date: Sat, 21 Mar 2020 13:34:03 GMT
Connection: keep-alive

{"error":"something wrong in my app"}

Patricks-MBP:express-basic patrick$ 

</code></pre>



<p>res.send(body); body = Array.</p>




<pre class="wp-block-code"><code>// server.js

.........

// define the routes
app.get('/', (req, res) =&gt; {

  var array = [1, 2, 3]
  res.send(array)
  
})

......

</code></pre>



<p>Start the server with <em>node server.js</em> and run <em>curl -i localhost:3000</em> in another terminal window and check the output.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ curl -i localhost:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 7
ETag: W/"7-nvUMyCrkdCefuOgolhQnArzLszo"
Date: Sat, 21 Mar 2020 13:35:57 GMT
Connection: keep-alive

[1,2,3]

Patricks-MBP:express-basic patrick$ 

</code></pre>



<h5 class="wp-block-heading">res.send() with Buffer response</h5>



<p>When the body is a Buffer object the Content-Type will be set to <em>application/octet-stream</em>.</p>




<p>res.send(body); body = Buffer.</p>




<pre class="wp-block-code"><code>// server.js

.........

// define the routes
app.get('/', (req, res) =&gt; {

 res.send(Buffer.from('&lt;p&gt;This is my buffer HTML&lt;/p&gt;'))
  
})

......

</code></pre>



<p>Start the server with <em>node server.js</em> and run <em>curl -i localhost:3000</em> in another terminal window and check the output.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ curl -i localhost:3000
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/octet-stream
Content-Length: 29
ETag: W/"1d-TcgrXqCfH0MW//p+ASn/khisjHQ"
Date: Sat, 21 Mar 2020 13:39:23 GMT
Connection: keep-alive

&lt;p&gt;This is my buffer HTML&lt;/p&gt;

Patricks-MBP:express-basic patrick$ 

</code></pre>



<h3 class="wp-block-heading">Properties of the Request Object</h3>



<p>The request object has properties that can be used on the request object. You can access various properties that have been provided with the request and process these data in your routing handler. </p>




<p>For example: You can access the request properties <em>path</em> and <em>method</em> with <em>req.path</em> and <em>req.method</em>. So when the client request i.e. the route &#8222;/users&#8220; with HTTP GET then these properties will have the corresponing value.<br/></p>




<pre class="wp-block-code"><code>// server.js

.........

app.get('/users', (req, res) =&gt; {

  path = req.path
  method = req.method

  console.log('Access / with GET. Path: ' +path +' and Method: ' +method);

  res.send('Hello, this is my Users Page')

})

.........

</code></pre>



<p>So start the server with <em>node server.js</em> in the server terminal and then access the route with <em>curl -i localhost:3000/users</em> in a different terminal. In the server terminal you see the following output.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ node server.js
My node.js web server is alive and running at port 3000

Access /users with GET. Path: /users and Method: GET

</code></pre>



<p>For example: You have many users in your database and each user should have his own profile page in your web app. To provide such a profile page for each of your users you could create one route for each user. This mean you would have many routes and you must change your application by programming a new route whenever a new user join to your user population. This is not useful.</p>




<p>This can be avoided by using request parameters. Request parameters are defined in your route as follows.</p>




<pre class="wp-block-code"><code>// server.js

.........

app.get('/users/:name', (req, res) =&gt; {

  path = req.path
  method = req.method
  username = req.params.name

  console.log('Access /users/patrick with GET. Path: ' +path +' and Method: ' +method);
  
  console.log('Request Parameter name is: ' +username);

  res.send('Hello, this is my Home Page')

})

.........

</code></pre>



<p>Start the server with <em>node server.js</em> in the server terminal and then access the route with <em>curl -i localhost:3000/users/patrick</em> in a different terminal. In the server terminal you see the following output.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ node server.js
My node.js web server is alive and running at port 3000

Access /users/patrick with GET. Path: /users/patrick and Method: GET

Request Parameter name is: patrick

</code></pre>



<p>Each of your users can access their individual profile page via a route <em>/users/:name</em>. </p>




<p>Suppose you want to show all users on the route <em>/users</em> in a list, you would expect that this would be possible without additional parameters when accessing the route <em>/users</em>. Unfortunately, this does not work in the current configuration.</p>




<p>Keep the server started in the server terminal and then access the route with <em>curl -i localhost:3000/users</em> in a different terminal. In the server terminal you see the following output.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ curl -i localhost:3000/users
HTTP/1.1 404 Not Found
X-Powered-By: Express
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 144
Date: Sat, 21 Mar 2020 06:51:23 GMT
Connection: keep-alive

&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;title&gt;Error&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;pre&gt;Cannot GET /users&lt;/pre&gt;
&lt;/body&gt;
&lt;/html&gt;
Patricks-MBP:express-basic patrick$ 

</code></pre>



<p>The 404 HTTP status code tell you that the route <em>/users</em> does not exist and nothing has been found. To solve this issue you can add a question mark to the name in your route definition. This tells Express that the request parameter is optional and that the route /users can also be requested. </p>




<pre class="wp-block-code"><code>// server.js

.........

app.get('/users/:name?', (req, res) =&gt; {

  path = req.path
  method = req.method
  username = req.params.name

  console.log('Access /users/patrick with GET. Path: ' +path +' and Method: ' +method);
  
  console.log('Request Parameter name is: ' +username);

  res.send('Hello, this is my Home Page')

})

.........

</code></pre>



<p>Start the server again in the server terminal and then access the route with <em>curl -i localhost:3000/users</em> in a different terminal again.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ curl -i localhost:3000/users
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 27
ETag: W/"1b-AX0brvVSaJSaZMvUvejQv+1wFQA"
Date: Sat, 21 Mar 2020 08:23:03 GMT
Connection: keep-alive

Hello, this is my Home Page

Patricks-MBP:express-basic patrick$ 

</code></pre>



<h3 class="wp-block-heading">Chapter 3: Middleware in Express</h3>



<p>As I mentioned in the introduction, middleware simply means that code is executed between the request and the response in order to parameterize the response if necessary. Lets have a look at the following route definition from above to show how routing in express sometimes behave like a middleware.</p>




<pre class="wp-block-code"><code>....

app.get('/example', [r1, r2, r3], (req, res, next) =&gt; {
    console.log('log 4 ! The request will be responded by final');
    next();
  }, function (req, res) {
      console.log('final ! This is the response to the request')
      
....

</code></pre>



<p>After a GET request on the route /example, the routing handler functions r1, r2 and r3 are executed. Console.log commands are executed from r1 to r3 and the request is forwarded with next(). Until then, however, no response is returned. The request is forwarded by r3 to the last routing handler function, which also first executes a console.log instruction before the request is then answered by the last routing handler function. From r1 to the response, the routing handler functions behaved like middleware.</p>




<p>It is also possible to load modules as middleware and always execute the code of the middleware when processing a request with any route and before the response. </p>




<p>We assume the following directory structure. </p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ ls -l
total 64
drwxr-xr-x   8 patrick  staff    256 21 Mär 16:06 modules
drwxr-xr-x  52 patrick  staff   1664 21 Mär 08:13 node_modules
-rw-r--r--   1 patrick  staff  14292 21 Mär 11:28 package-lock.json
-rw-r--r--   1 patrick  staff    162 21 Mär 10:16 package.json
-rw-r--r--   1 patrick  staff   1635 21 Mär 09:22 server.js

Patricks-MBP:express-basic patrick$ 

</code></pre>



<p>A very good but simple example of how middleware modules work is the use of a simple console logger. To demonstrate this, I create a logger.js file in the modules directory. </p>




<p>The looger.js file in the modules directory has the following code.</p>




<pre class="wp-block-code"><code>// modules/logger.js

var logger = function(req, res, next) {

	var method = req.method
	var path = req.path

	console.log(method +' ' +path);
	next()

}

module.exports = logger;

</code></pre>



<p>The logger.js file contain a simple function logger() that will be exported using module.exports. Whenever a request is made for any route, the req.method and the req.path will be logged in the terminal and then the processing will be forwarded with the next() function. So first the request is passed to the application, then the console.log instruction is executed and then the request will be forwarded to the respective routing method.<br/></p>




<p>The routes are defined in our main application file server.js. The server.js file in the modules directory has the following content.</p>




<pre class="wp-block-code"><code>// server.js

// load node http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// load own modules
const logger = require('./modules/logger')

// Integrate Middleware
app.use(logger);

// define the routes
app.get('/', (req, res) =&gt; {
  res.send('Hello, this is the Blog Home Page.')
})

app.get('/about', (req, res) =&gt; {
  res.send('Hello, this is the Blog About Page.')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

</code></pre>



<p>In the server.js main application file I load the module and store the logger() function into the variable logger. The  middleware is integrated in the code with app.use. </p>




<p>After that I start the server with node server.js. In another terminal window I use curl to call routes &#8222;/&#8220; and &#8222;/about&#8220;.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-blog patrick$ curl -i localhost:3000/
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 34
ETag: W/"22-jCGRti3cySGTHqyUOi9QHFbFw2U"
Date: Sat, 21 Mar 2020 12:53:32 GMT
Connection: keep-alive

Hello, this is the Blog Home Page.

Patricks-MBP:express-blog patrick$ curl -i localhost:3000/about
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 35
ETag: W/"23-hKbwboK7j6DObmy7eAWiCel3bMQ"
Date: Sat, 21 Mar 2020 12:53:35 GMT
Connection: keep-alive


Hello, this is the Blog About Page.

Patricks-MBP:express-blog patrick$

</code></pre>



<p>In the first terminal window in which the server is running we see that the logging has worked for both routes. The console.log command of the logger middleware module was executed for both requests.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-blog patrick$ node server.js
My node.js web server is alive and running at port 3000

GET /
GET /about


</code></pre>



<h3 class="wp-block-heading">Chapter 4: Create a Small Blog app with express</h3>



<p>The small Blog app should be a web app without any HTML rendering and no input validation. It should simply show how we can make use of the things we learned so far. This blog will have blogs and users stored in a data file. We will create routes end points to add-, and remove users and blogs and we will create modules which contain the program logic to manipulate data.</p>




<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: You <strong>should never</strong> ever implement an application in production without any input validation. From a security perspective, this is absolutely unacceptable. I will deal with the topic <em>input validation with Express</em> in an extra post here on Digitaldocblog.</p>
</blockquote>



<p>You can find the code discussed here in this chapter on my <a href="https://github.com/prottlaender/node-part-2-express-blog-V1">GitHub page</a>. </p>




<p>First we create a new application root directory <em>express-blog</em>. In this directory we create the main application file <em>blog.js</em> and a <em>package.json</em> file with the touch command.</p>




<pre class="wp-block-code"><code>Patricks-MBP:2020-03-08 patrick$ mkdir express-blog
Patricks-MBP:2020-03-08 patrick$ ls -l
total 0
drwxr-xr-x  9 patrick  staff  288 21 Mär 07:34 express-basic
drwxr-xr-x  2 patrick  staff   64 21 Mär 07:18 express-blog
drwxr-xr-x  6 patrick  staff  192 21 Mär 09:26 node-basic

Patricks-MBP:2020-03-08 patrick$ cd express-blog

Patricks-MBP:express-blog patrick$ ls -l

Patricks-MBP:express-blog patrick$ touch blog.js
Patricks-MBP:express-blog patrick$ touch package.json

Patricks-MBP:express-blog patrick$ ls -l
total 0
-rw-r--r--  1 patrick  staff  0 21 Mär 07:19 blog.js
-rw-r--r--  1 patrick  staff  0 21 Mär 07:19 package.json

Patricks-MBP:express-blog patrick$ 

</code></pre>



<p>We open the package.json file in our editor and edit the file as follows. </p>




<pre class="wp-block-code"><code>{
  "name": "simple_blog",
  "version": "0.0.1",
  "main": "blog.js",
  "author": "Patrick Rottlaender"
}

</code></pre>



<p>We install express with <em>npm install express &#8211;save &#8211;save-exact</em> to ensure that we will install express in the latest version but without updates to the version by future installs. </p>




<pre class="wp-block-code"><code>Patricks-MBP:express-blog patrick$ npm install express --save --save-exact
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN simple_blog@0.0.1 No description
npm WARN simple_blog@0.0.1 No repository field.
npm WARN simple_blog@0.0.1 No license field.

+ express@4.17.1
added 50 packages from 37 contributors and audited 126 packages in 2.167s
found 0 vulnerabilities

Patricks-MBP:express-blog patrick$ ls -l
total 40
-rw-r--r--   1 patrick  staff      0 21 Mär 07:19 blog.js
drwxr-xr-x  52 patrick  staff   1664 21 Mär 07:33 node_modules
-rw-r--r--   1 patrick  staff  14287 21 Mär 07:33 package-lock.json
-rw-r--r--   1 patrick  staff    155 21 Mär 07:33 package.json

Patricks-MBP:express-blog patrick$ 

</code></pre>



<p>Then we create a data.json file and a modules directory. </p>




<pre class="wp-block-code"><code>Patricks-MBP:express-blog patrick$ touch data.json

Patricks-MBP:express-blog patrick$ mkdir modules

Patricks-MBP:express-blog patrick$ ls -l
total 40
-rw-r--r--   1 patrick  staff      0 21 Mär 07:19 blog.js
-rw-r--r--   1 patrick  staff      0 21 Mär 07:44 data.json
drwxr-xr-x   2 patrick  staff     64 21 Mär 07:51 modules
drwxr-xr-x  52 patrick  staff   1664 21 Mär 07:33 node_modules
-rw-r--r--   1 patrick  staff  14287 21 Mär 07:33 package-lock.json
-rw-r--r--   1 patrick  staff    155 21 Mär 07:33 package.json

Patricks-MBP:express-blog patrick$ 

</code></pre>



<p>The structure of the application root directory has now been created and we can start developing the blog app.</p>




<p><strong>First step</strong>: First I want to write the initial code in the blog.js main application file. The code loads the express module which we installed earlier and starts the server on port 3000 of the local host. In addition, a home route is already defined.</p>




<pre class="wp-block-code"><code>// blog.js

// load node http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// define the routes
app.get('/', (req, res) =&gt; {
  res.send('Hello, this is the Blog Home Page.')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My Blog server is running at port 3000')

</code></pre>



<p>To create users and blog data I should have 2 different modules. One that will create the users and the other one will create the blogs. Therefore I create 2 files in the modules directory. </p>




<pre class="wp-block-code"><code>Patricks-MBP:express-blog patrick$ touch modules/createUsers.js
Patricks-MBP:express-blog patrick$ touch modules/createBlogs.js

Patricks-MBP:express-blog patrick$ ls -l modules
total 0
-rw-r--r--  1 patrick  staff  0 21 Mär 08:02 createBlogs.js
-rw-r--r--  1 patrick  staff  0 21 Mär 08:02 createUsers.js

Patricks-MBP:express-blog patrick$ 

</code></pre>



<p><strong>Second step</strong>: In the data.json file I will store the user and blog data. The data file is currently empty but will have the following structure.</p>




<pre class="wp-block-code"><code>{
	"users":[{name:..., lastname:..., email:..., id:... },{...},...],
	"blogs":[{title:..., author:..., date:..., id:... },{...},...]

}

</code></pre>



<p>There is a json data object with the keys <em>users</em> and <em>blogs</em>. These keys have an array value with objects as array elements. </p>




<pre class="wp-block-code"><code>i,j = 0, 1, 2 ...

data.users[i]
data.blogs[j]

</code></pre>



<p>I will now program the 2 modules <em>usersAdd</em> and <em>blogsAdd</em> in the createUsers.js and createBlogs.js files. These files contain functions that initially create user or blog records or add corresponding user or blog records.</p>




<p>The <em>createUsers.js</em> file contains the following code.</p>




<pre class="wp-block-code"><code>// modules/createUsers.js

// load node fs module
const fs = require('fs')

const usersAdd = function(req, res) {

    var dataExp = fs.readFileSync('./data.json', 'utf8')

    if(!dataExp) {
      console.log('no data available');
      var data = {}
      data.users = []

      var newUserName = req.body.name
      var newUserLastname = req.body.lastname
      var newUserEmail = req.body.email
      var newUserId = 100

      var newUser = {
        name: newUserName,
        lastname: newUserLastname,
        email: newUserEmail,
        id: newUserId
      }

    data.users.push(newUser)

    dataToFile = JSON.stringify(data)

      fs.writeFile('./data.json', dataToFile, function(err) {
        if (err) throw err;
        console.log('Intial User created');
        });

    } else {
      var data = JSON.parse(dataExp)

      if(!data.users) {
        console.log('no users are available')
        data.users = []

        var newUserName = req.body.name
        var newUserLastname = req.body.lastname
        var newUserEmail = req.body.email
        var newUserId = 100

        var newUser = {
          name: newUserName,
          lastname: newUserLastname,
          email: newUserEmail,
          id: newUserId
        }

      data.users.push(newUser)

      dataToFile = JSON.stringify(data)

        fs.writeFile('./data.json', dataToFile, function(err) {
          if (err) throw err;
          console.log('Intial User created');
          });

      } else {
        console.log('users are available')
        var newID
        var minID = data.users[0].id

        for (i = 0; i &lt; data.users.length; i++) {
          if (data.users[i].id &gt;= minID) {
              newID = data.users[i].id + 100
          }
        }

        var newUserName = req.body.name
        var newUserLastname = req.body.lastname
        var newUserEmail = req.body.email
        var newUserId = newID

        var newUser = {
          name: newUserName,
          lastname: newUserLastname,
          email: newUserEmail,
          id: newUserId
        }

      data.users.push(newUser)

      dataToFile = JSON.stringify(data)

      fs.writeFile('./data.json', dataToFile, function(err) {
        if (err) throw err;
        console.log('User added');
        });
      }
  }
}

module.exports = usersAdd;

</code></pre>



<p>The <em>fs</em> module is loaded first. This is a node internal module which is used to read files and also to write data into a file. Then we create the <em>usersAdd</em> function that will be exported with <em>module.exports</em>.</p>




<p>The usersAdd function read the data.json file with fs.readFileSync and store the received data into the dataExp variable. If dataExp is empty we have an empty data file and must first create the empty data Object and store this into the variable data. Then we create the users key on the data object with data.users and assign an empty array. </p>




<p>From the request body we receive the name, lastname and email and store these data into new user variables. For the initial user entry we define the id 100. </p>




<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: Later in this tutorial we will see the POST route definition /createusers in the application main file blog.js. This POST route will call usersAdd with the parameters req and res so that we have access to the req.body object in createUsers.js. We will see this later. </p>
</blockquote>



<p>We create a newUser object with the keys like name, lastname, email, id and assign the new user variables we received from the request body to them as values. Then we push the newUser object into the array of the users key (created with data.users = [ ] at the beginning) which is linked to the data object that we created at the beginning with data =  .</p>




<pre class="wp-block-code"><code>data.users.push(newUser)

</code></pre>



<p>After this we have the following JavaScript data object.</p>




<pre class="wp-block-code"><code>{

	"users": { [ {name:..., lastname:..., email:..., id:... } ] }

}

</code></pre>



<p>With JSON.stringify() we take the JavaScript data object, create a JSON string out of it and store this string into the dataToFile variable. With fs.writeFile we write the string into the so far empty data.json file.<br/></p>




<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: If you use JavaScript to develop applications, JavaScript objects have to be converted into strings if the data is to be stored in a database or a data file. The same applies if you want to send data to an API or to a webserver. The JSON.stringify() function does this for us.</p>
</blockquote>



<p>So far we know what the code does in case the data.json file is completely empty and the first if condition is true (if(!dataExp)). And now lets see what happens what the code does in case the first if condition is false which means that the data.json file already has data because that is the case if the variable dataExp is not empty. </p>




<p>As we have seen we store a JSON string in the data file. So when we read the file and store the data into the variable dataExp we have JSON string data in that variable. To process these data in our JavaScript code we must parse this JSON so that a JavaScript object is created from the JSON string. This is what we do with the JSON.parse() function. </p>




<p>Here we store the JavaScript object into the variable data.</p>




<pre class="wp-block-code"><code>var data = JSON.parse(dataExp)

</code></pre>



<p>Since our code is designed to store only one data object in the data.json file, the variable data is our data object. So when we have the data object we can ask if users are already existing. </p>




<p>Therefore we use the if condition to check if the key data.users exist.</p>




<p>In case the condition if(!data.users) is true, the key data.users is not existing which means that <strong>no users are available so far</strong>. At his point we create a data.users key and assign an empty array. In the following, a newUser object is created in the same way as described above and inserted into the array using the data.users.push() method. Now the data object has an additional key data.users and this key has a newUser Object as value. Now we can convert this entire data object into a string again and completely overwrite the data.json file.</p>




<p>In case the condition if(!data.users) is false, the key data.users is existing and <strong>users are available</strong>. If users exist the individual user records naturally have existing ids. So we first have to create an algorithm to get a new user ID for the new user.</p>




<p>We first create a variable newID and a variable minID. minID corresponds to the userID which has to be assigned at least since it corresponds to the  userID of our first user data object. minID must be the id value of the first user data object record. Since the user objects are stored in an array, this must correspond to the user object at position zero.</p>




<pre class="wp-block-code"><code>var minID = data.users[0].id

</code></pre>



<p>newID should be plus 100 larger than the id of the last found user object.</p>




<p>That is why we iterate through all user objects, adding the value 100 to the id value of the last user object found. Finally, we take the id value of the last user object and add 100 and assign this value to newID. </p>




<p>In the following again a newUser object is created, the entire data object is converted into a string and stored into the data.json file.</p>




<p>The code for the blogsAdd function is implemented exactly according to the same scheme. The only difference is that the blog id only grows by 1. </p>




<p>The createBlogs.js file contains the following code.</p>




<pre class="wp-block-code"><code>// modules/createBlogs.js

// load node fs module
const fs = require('fs')

const blogsAdd = function(req, res) {

    var dataExp = fs.readFileSync('./data.json', 'utf8')

    if(!dataExp) {
      console.log('no data available')
      data = {}
      data.blogs = []

      var newBlogTitle = req.body.title
      var newBlogAuthor = req.body.author
      var newBlogDate = req.body.date
      var newBlogId = 1

      var newBlog = {
        title: newBlogTitle,
        author: newBlogAuthor,
        date: newBlogDate,
        id: newBlogId
      }

    data.blogs.push(newBlog)

    dataToFile = JSON.stringify(data)

      fs.writeFile('./data.json', dataToFile, function(err) {
        if (err) throw err;
        console.log('Intial Blog created');
        });

    } else {
      var data = JSON.parse(dataExp)

      if (!data.blogs) {
        console.log('no blog are available')
        data.blogs = []

        var newBlogTitle = req.body.title
        var newBlogAuthor = req.body.author
        var newBlogDate = req.body.date
        var newBlogId = 1

        var newBlog = {
          title: newBlogTitle,
          author: newBlogAuthor,
          date: newBlogDate,
          id: newBlogId
        }

      data.blogs.push(newBlog)

      dataToFile = JSON.stringify(data)

        fs.writeFile('./data.json', dataToFile, function(err) {
          if (err) throw err;
          console.log('Intial blog created');
          });

      } else {
        console.log('blogs are available');
        var newID
        var minID = data.blogs[0].id

        for (i = 0; i &lt; data.blogs.length; i++) {
          if (data.blogs[i].id &gt;= minID) {
              newID = data.blogs[i].id + 1
          }
        }

        var newBlogTitle = req.body.title
        var newBlogAuthor = req.body.author
        var newBlogDate = req.body.date
        var newBlogId = newID

        var newBlog = {
          title: newBlogTitle,
          author: newBlogAuthor,
          date: newBlogDate,
          id: newBlogId
        }

      data.blogs.push(newBlog)

      dataToFile = JSON.stringify(data)

      fs.writeFile('./data.json', dataToFile, function(err) {
        if (err) throw err;
        console.log('blog added');
        });
      }
  }
}

module.exports = blogsAdd;

</code></pre>



<p><strong>Third Step</strong>: The blog.js file needs to be adjusted. First we integrate middleware that allows us to parse incoming http requests with urlencoded payloads.</p>




<pre class="wp-block-code"><code>app.use(express.urlencoded({ extended: false }))

</code></pre>



<p>Then the two modules must be loaded into the blog.js file with the require() function. Therefore we require the files createUsers.js and createBlogs.js. </p>




<pre class="wp-block-code"><code>const createUsers = require('./modules/createUsers')
const createBlogs = require('./modules/createBlogs')

</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: I would like to explain the topic require() of modules again at this point. The files createBlogs.js and createUsers.js contain the usersAdd and blogsAdd functions which are each exported with module.exports. The module.exports instruction means that these functions can be called in other files of the application if the createBlogs.js and createUsers.js files are loaded with require(). usersAdd and blogsAdd have no return value and therefore the functions themselves are saved in the variables createBlogs and createUsers in blog.js. If e.g. createUsers(req, res) is called in a POST route, the usersAdd function is called and req an res are passed as parameters.</p>
</blockquote>



<p>As we can see in the code of the two modules above, the values for the initial creation or the addition of user- or blog objects are passed to the functions usersAdd or blogsAdd via the request body. For this, POST routes /createusers and /createblogs must be created in the blog.js file. These POST routes call the functions usersAdd or blogsAdd via createUsers() or createBlogs(). </p>




<p>The blog.js file contains the following code.</p>




<pre class="wp-block-code"><code>// blog.js

// load node http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }))

// load own modules
const createUsers = require('./modules/createUsers')
const createBlogs = require('./modules/createBlogs')

// define the routes
app.get('/', (req, res) =&gt; {
  res.send('Hello, this is the Blog Home Page.')
})

app.post('/createusers', (req, res) =&gt; {
  createUsers(req, res)
  res.send('User has been added successfully.')
})

app.post('/createblogs', (req, res) =&gt; {
  createBlogs(req, res)
  res.send('Blog has been added successfully.')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

</code></pre>



<p>Now we are able to add blogs and users. </p>




<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: Here in this application we have no User interface to enter the user variables like name, lastname, email or the blog variables like title, author and date. With this application you cannot enter the data in a form and then transfer it to the application routes /createusers or /createblogs using HTTP POST. This is where <a href="https://www.postman.com">Postman</a> comes in. With <a href="https://www.postman.com">Postman</a> you can simulate a form by manually entering this data in the HTTP body of the HTTP POST request. Postman check whether the POST request was successful or not. I will not go into Postman at this point. So pls. check the documentation on the <a href="https://www.postman.com">Postman website</a>.</p>
</blockquote>



<p>But there is still no way to remove data from the data.json. We now want to show this using the example of how to remove users.</p>




<p>The module which should take over exactly this functionality is the function usersRemove() which I will write in the file modules/usersRemove.js. To do this, we first create an empty file usersRemove.js in the modules directory.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-blog patrick$ pwd
/Users/patrick/Software/dev/node/myArticles/2020-03-08/express-blog
Patricks-MBP:express-blog patrick$ ls -l modules
total 24
-rw-r--r--  1 patrick  staff  2285 21 Mär 17:18 createBlogs.js
-rw-r--r--  1 patrick  staff  2319 21 Mär 17:17 createUsers.js
-rw-r--r--  1 patrick  staff   652 21 Mär 06:50 removeUsers.js

Patricks-MBP:express-blog patrick$ 

</code></pre>



<p>The <em>removeUsers.js</em> file contains the following code.</p>




<pre class="wp-block-code"><code>// modules/removeUsers.js

// load node fs module
const fs = require('fs')

const usersRemove = function(req, res) {
  var dataExp = fs.readFileSync('./data.json', 'utf8')
  var data = JSON.parse(dataExp)

  var delUserID = req.body.id - 0
  
  var elementFound = function(user) {
    return user.id === delUserID
  }

  var index = data.users.findIndex(elementFound)

  data.users.splice(index, 1)

  dataToFile = JSON.stringify(data)
 
  fs.writeFile('./data.json', dataToFile, function(err) {
    if (err) throw err;
    console.log('userID removed');
    });
 
}

module.exports = usersRemove;

</code></pre>



<p>We first load the node fs module again and then define the function usersRemove() which is then exported with module.exports at the end of the code. The function usersRemove() receive req and res as parameters, read the data.json file and parse the read data into a JavaScript object represented by the variable data. </p>




<p>To show you the data object I just run the script as follows.</p>




<pre class="wp-block-code"><code>// modules/removeUsers.js

// load node fs module
const fs = require('fs')

const usersRemove = function(req, res) {
  var dataExp = fs.readFileSync('./data.json', 'utf8')
  var data = JSON.parse(dataExp)
  
  console.log(data)

}

module.exports = usersRemove;

</code></pre>



<p>When you check your console you can see that we saved all the json-data from the file in the variable data. We see a JavaScript object that can be used in our code.</p>




<pre class="wp-block-code"><code>{
  blogs: [
    {
      title: 'First Title',
      author: 'First test author',
      date: '2020-03-28',
      id: 1
    },
    {
      title: 'Second Title',
      author: 'Second test author',
      date: '2020-03-28',
      id: 2
    }
  ],
  users: [
    {
      name: 'Oskar',
      lastname: 'Rottländer',
      email: 'o.rottlaender@test.com',
      id: 100
    },
    {
      name: 'Patrick',
      lastname: 'Rottländer',
      email: 'p.rottlaender@test.com',
      id: 200
    },
    {
      name: 'Carolin',
      lastname: 'Rottländer',
      email: 'c.rottlaender@test.com',
      id: 300
    }
  ]
}

</code></pre>



<p>In the code we take the userID from the req.body and store it into the variable delUserID. The value from the req.body is a string. As we operate with userIDs as numbers in our json data file we must convert the string into a number. This is done with &#8222;- 0&#8220;. Here we subtract the number value zero and tell JavaScript that the delUserID is a number instead of a string. </p>




<p>Then we must find the user object that need to be deleted. user objects are elements in the data.users array. Consequently we must find the user object with a userID from the req.body in the data.users array because this userID identify the user object that should then be deleted and therefore removed from the data.users array. </p>




<p>The callback function elementFound return true when the userID from the rep.body is equal to a userID of a user object in the data.users array. </p>




<p>The method data.users.findIndex() return the index of the first user object found in the data.users array where the userID from the rep.body is equal to the userID of the user object. </p>




<p>The data.users.splice(index, 1) remove one user object at the position of index. As the findIndex() method before returned the exact position of the user object that we want to delete the splice() method here ultimately removes the user object.</p>




<p>The complete data object consisting of blog objects and user objects is now reduced by one user object. the reduced data object is converted as a whole into a string and written again into the file data.json using the fs.writeFile() method.</p>




<p>The blogsRemove() function works identically to the usersRemove() function explained above. The file removeBlogs.js in the module directory therefore looks like this.</p>




<pre class="wp-block-code"><code>// modules/removeBlogs.js

// load node fs module
const fs = require('fs')

const blogsRemove = function(req, res) {
  var dataExp = fs.readFileSync('./data.json', 'utf8')
  var data = JSON.parse(dataExp)

  var delBlogID = req.body.id - 0

  var elementFound = function(blog) {
    return blog.id === delBlogID
  }

  var index = data.blogs.findIndex(elementFound)

  data.blogs.splice(index, 1)

  dataToFile = JSON.stringify(data)

  fs.writeFile('./data.json', dataToFile, function(err) {
    if (err) throw err;
    console.log('blogID removed');
    });
}

module.exports = blogsRemove;

</code></pre>



<p>In the code of the two removeUsers.js and removeBlogs.js modules above, the values for the to be removed user- or blog objects are passed to the functions usersRemove() or blogsRemove() via the request body. For this, POST routes /removeusers and /removeblogs must be created in the blog.js file. These POST routes call these functions usersRemove() or blogsRemove() which are defined in the modules removeUsers.js and removeBlogs.js. These modules must also be loaded into the blog.js file with the require() function.</p>




<p>The blog.js file now contains the following code.</p>




<pre class="wp-block-code"><code>// blog.js

// load node http module
const http = require('http')

// load third party Express module
const express = require('express')
const app = express()

// parse application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: false }))

// load own modules
const createUsers = require('./modules/createUsers')
const removeUsers = require('./modules/removeUsers')
const createBlogs = require('./modules/createBlogs')
const removeBlogs = require('./modules/removeBlogs')

// define the routes
app.get('/', (req, res) =&gt; {
  res.send('Hello, this is the Blog Home Page.')
})

app.post('/createusers', (req, res) =&gt; {
  createUsers(req, res)
  res.send('User has been added successfully.')
})

app.post('/removeusers', (req, res) =&gt; {
  removeUsers(req, res)
  res.send('User has been successfully removed.')
})


app.post('/createblogs', (req, res) =&gt; {
  createBlogs(req, res)
  res.send('Blog has been added successfully.')
})

app.post('/removeblogs', (req, res) =&gt; {
  removeBlogs(req, res)
  res.send('Blog has been successfully removed.')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

</code></pre>



<p><strong>Fourth step</strong>: Our application can currently create users and create blogs as well as delete blogs and users. Thats fine so far. Now another functionality should be added, namely the display of blogs. This is to be implemented in such a way that the GET request for one route sends different responses back to the server depending on the request parameters provided with the request.</p>




<p>What does this mean ? </p>




<p>For example: We have various blogs in our data file, all of them have been created for specific dates. We want to see all blogs, but also blogs from a certain year or month or blogs that were created on a specific date. </p>




<p>You want to access all blogs using this route.</p>




<pre class="wp-block-code"><code>app.get('/blog', (req, res) =&gt; {
  	....
  
  })
</code></pre>



<p>And you  want to access all blogs in 2020 using this route.</p>




<pre class="wp-block-code"><code>app.get('/blog/2020', (req, res) =&gt; {
  	....
  
  })
</code></pre>



<p>And so on.</p>




<p>We can do this much more elegantly using request parameter. We create the following GET route in our blog.js file.</p>




<pre class="wp-block-code"><code>// blog.js

.....

app.get('/blog/:year?/:month?/:day?', (req, res) =&gt; {
  	....
  
  })
  
.....
</code></pre>



<p>The question mark indicates that the parameters year, month and day are optional and can also be omitted when requesting.</p>




<p>We have defined a route that should give different responds depending on the request parameters. That is why we first have to evaluate the request parameters in the route definition and then define conditions as to how the different inquiries should be answered.</p>




<pre class="wp-block-code"><code>// blog.js

.....

app.get('/blog/:year?/:month?/:day?', (req, res) =&gt; {

  url = req.url
  year = req.params.year
  month = req.params.month
  day = req.params.day

    if (url == '/blog') {
      	...    
    }

    else if (url == '/blog/'+year) {
 		...
    }

    else if (url == '/blog/'+year+'/'+month) {
    	 ...
    }

    else if (url == '/blog/'+year+'/'+month+'/'+day) {
     	...
    }

    else {
      res.status(404).send('Not Found')
    }

})

.....

</code></pre>



<p>I will come back to the main application file blog.js later.</p>




<p>Our goal is to display blog posts. Depending on the route, we want to show  (1) all blogs or (2) all blogs of a year or (3) all blogs of a year and a month or (4) all blog posts from a specific day. </p>




<p>To collect all these data from our data.json file we basically need 4 new functions to perform those queries. Therefore I create a new queryModule in the modules directory. This queryModule contain all these 4 query functions and all of them will be made available within our application. The notation of the code in the queryModule file is slightly different than in the previous modules. </p>




<p>The reason is that we previously had one file as a module in the modules directory and only one function in that file. The export value was the function. These modules looked like this.</p>




<pre class="wp-block-code"><code>// modules/previousModule.js

var myFunction = function() {
	....
}
module.exports = myFunction

</code></pre>



<p>In our main application file we load the previousModule with the require function and store a function into the variable previousModule. We can call the function in the main application file directly with previousModule().</p>




<pre class="wp-block-code"><code>// mainApplicationFile.js

.....

const previousModule = require('./modules/previousModule')

.....

previousModule()

....


</code></pre>



<p>Here in the queryModule (which is our queryBlogs.js file in modules directory) we collect 4 functions but we do not export them. Instead we use module.exports to export an object with 4 keys. Each of these keys has exactly one value and that is the respective function.</p>




<pre class="wp-block-code"><code>// modules/queryModule.js

module.exports = {

	function1: function() {
				....	
			}
	
	function2: function() {
				....	
			}
			
	function3: function() {
				....	
			}
			
	function4: function() {
				....	
			}

}

</code></pre>



<p>In our main application file we load the queryModule with the require function and store an object into the variable queryModule. We can call the functions in the main application file via the object.key notation. </p>




<pre class="wp-block-code"><code>// mainApplicationFile.js

.....

const queryModule = require('./modules/queryModule')

.....

queryModule.function1()

queryModule.function2()

queryModule.function3()

queryModule.function4()

....

</code></pre>



<p>When we take a look at our new queryModule which is the queryBlogs.js file in the modules directory you see that we have an object with 4 different keys and those keys have a function() as values. </p>




<ul class="wp-block-list">
	<li>queryAllBlogs</li>
	<li>queryBlogsYear</li>
	<li>queryBlogsYearMonth</li>
	<li>queryBlogsYearMonthDay<br></li>
</ul>



<p>There is another important difference to the previous modules. Those functions in queryBlogs.js return each a value and this value can be used later in our main application file blog.js. This value is an array of blog posts that should be displayed when a certain route has been requested. </p>




<p>So the queryBlogs.js file looks as follows. I will explain the queryAllBlogs and the queryBlogsYear in detail. The other 2 functions queryBlogsYearMonth and queryBlogsYearMonthDay are schematically the same.</p>




<pre class="wp-block-code"><code>// modules/queryBlogs.js

// load node fs module
const fs = require('fs')

module.exports = {

  queryAllBlogs: function() {

    var allBlogs = []

    var dataExp = fs.readFileSync('./data.json', 'utf8')
    var data = JSON.parse(dataExp)

    for(i = 0; i &lt; data.blogs.length; i++) {
      var dataSet = {
        title: data.blogs[i].title,
        author: data.blogs[i].author,
        date: data.blogs[i].date
      }
      allBlogs.push(dataSet)
    }
      return allBlogs
  },

  queryBlogsYear: function(allBlogs) {
    var yearBlogs = []
    for (i = 0; i &lt; allBlogs.length; i++) {

      blogDate = allBlogs[i].date
      parsedBlogDate = new Date (Date.parse(blogDate))
      parsedBlogDateYear = parsedBlogDate.getFullYear()

      if (year == parsedBlogDateYear) {
        var dataset = {
          title: allBlogs[i].title,
          author: allBlogs[i].author,
          date: blogDate
        }
        yearBlogs.push(dataset)
      }
  }
  return yearBlogs
  },

  queryBlogsYearMonth: function(allBlogs) {
    var yearMonthBlogs = []
    for (i = 0; i &lt; allBlogs.length; i++) {

      blogDate = allBlogs[i].date
      parsedBlogDate = new Date (Date.parse(blogDate))
      parsedBlogDateMonth = (parsedBlogDate.getMonth() + 1)
      parsedBlogDateYear = parsedBlogDate.getFullYear()

      if (year == parsedBlogDateYear &amp;&amp; month == parsedBlogDateMonth) {
        var dataset = {
          title: allBlogs[i].title,
          author: allBlogs[i].author,
          date: blogDate
        }
        yearMonthBlogs.push(dataset)
      }
  }
  return yearMonthBlogs
  },

  queryBlogsYearMonthDay: function(allBlogs) {
    var yearMonthDayBlogs = []
    for (i = 0; i &lt; allBlogs.length; i++) {

      blogDate = allBlogs[i].date
      parsedBlogDate = new Date (Date.parse(blogDate))
      parsedBlogDateDay = parsedBlogDate.getDate()
      parsedBlogDateMonth = (parsedBlogDate.getMonth() + 1)
      parsedBlogDateYear = parsedBlogDate.getFullYear()

      if (year == parsedBlogDateYear &amp;&amp; month == parsedBlogDateMonth &amp;&amp; day == parsedBlogDateDay) {
        var dataset = {
          title: allBlogs[i].title,
          author: allBlogs[i].author,
          date: blogDate
        }
        yearMonthDayBlogs.push(dataset)
      }
    }
    return yearMonthDayBlogs
  }

}

</code></pre>



<p>Our data is stored as a string in a data.json file. Every operation of our application in relation to the data requires that we have access to a JavaScript data object. So if we want to search or select data to display only a part of it in the application, e.g. only all blog posts from a certain year, it is still necessary to have read the entire data from the data file in beforehand. In a larger project with a larger amount of data, this approach is definitely not the right one.</p>




<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: With larger amounts of data, it makes sense to use a database such as MongoDB because we can then search for specific values directly in the database and it is not necessary to retrieve all data from the database. I will show you the use of a MongoDB in a node.js project in another article at a later time. For simplicity&#8217;s sake, let&#8217;s stick here in this article to a file-based approach.</p>
</blockquote>



<p>Therefore the queryAllBlogs function serves as the basis for our other queries for displaying blogs. This function first defines an empty array allBlogs. Then, as already described above, the data is read completely from the data file with fs.readFileSync and converted into a JavaScript object with JSON.parse. Now we have access to blog posts with data.blogs. However, this is not entirely true. As we have already seen above, data.blogs is a key in the data object and this key has an array as value. Strictly speaking, this array contains elements that are the blog posts.</p>




<p>In any case, with the for loop we iterate through all elements of the array using data.blogs[i] so that each individual blog data record can be read from the data file and attached with the allBlogs.push() method as an dataSet object to the allBlogs array that was defined first. The function queryAllBlogs() then return allBlogs back to the caller. allBlogs contain all the dataSet objects. The allBlogs array looks like the following. </p>




<pre class="wp-block-code"><code>[
	{
	
	title: valueTitle,
	author: valueAuthor,
	data: valueDate
	
	},
	
	{
	
	...
	
	},
	
	...

]

</code></pre>



<p>The function queryBlogsYear take the allBlogs array as parameter (like any other function queryBlogs* defined in the module). This is because we want to select data from it in the queryBlogsYear function and therefore all blog posts must be available in the function scope. </p>




<p>First we create the empty array yearBlogs. This array will contain at the end of the function code all the data that should be returned later by the queryBlogsYear function. </p>




<p>Then we iterate through the allBlogs array using the for loop. </p>




<p>In the for loop, we first read at the value of the blog date with allblogs[i].date and store the value in the variable blogDate. In the data file the date is saved with ISO 8601 date format (YYYY-mm-dd). It is highly recommended to always save the date in this format since ISO 8601 is the date format preferred by JavaScript and so all other operations with the date are then made easier. In order to work well with the date string yyyy-mm-dd in JavaScript, this string date must first be converted into a number using. Then the number will be converted into a date object using the Date.parse() method. </p>




<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: Date.parse() receive the string date in ISO 8601 format yyyy-mm-dd and returns a number that represents the milliseconds between January 1st, 1970 00:00:00 UTC. This number is unique and is required to create the date object using the new Date() method. With this date object we have with JavaScript access to the year, month or day of the given date object. </p>
</blockquote>



<p>Therefore, in our code Date.parse() is passed directly as parameter into the new Date() method so that the required date object can be generated directly from the returned number. The date object is stored in the variable parsedBlogDate.</p>




<p>Because parsedBlogDate represents an object, the parsedBlogDate.getFullYear() method gives us access to the year passed with the date. The year is stored in the parsedBlogDateYear variable. </p>




<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: Later we will use parsedBlogDate.getMonth() to retrieve the month from the date object. This method returns 0 for January and 11 for December. It is therefore necessary to add &#8222;+1&#8220; to get the usual values 1 for January and 12 for December. And we will use parsedBlogDate.getDate() to retrieve the day of the month as 1 for the first day and max 31 for the last day of the respective month. </p>
</blockquote>



<p>Then we compare in the if loop the year passed in the request (year = req.params.year, variable year is defined in blog.js) with the years of all blog posts. We only generate a dataset where the year from the request matches the year of the blog post. The array yearBlogs created in this way is then returned to the caller as a return value.</p>




<p>I will now come back to main application file blog.js and the specific route definition where we evaluate the request parameters and provide different responses. With this route we send, depending on the request parameters, the following responses.</p>




<ul class="wp-block-list">
	<li>all blog posts  &#8211; res.send(allBlogs)</li>
	<li>blog posts of given year &#8211; res.send(blogsYear)</li>
	<li>blog posts of a give year and month &#8211; res.send(blogsYearMonth)</li>
	<li>blog posts of a given date &#8211; res.send(blogsYearMonthDay)<br></li>
</ul>



<pre class="wp-block-code"><code>// blog.js

....

const queryBlogs = require('./modules/queryBlogs')

....

app.get('/blog/:year?/:month?/:day?', (req, res) =&gt; {

  url = req.url
  year = req.params.year
  month = req.params.month
  day = req.params.day

  var allBlogs = queryBlogs.queryAllBlogs()

    if (url == '/blog') {
      if (allBlogs.length == 0) {
        res.send('no post found')
      } else {
        res.send(allBlogs)
      }
    }

    else if (url == '/blog/'+year) {

      const blogsYear = queryBlogs.queryBlogsYear(allBlogs)

      if(blogsYear.length == 0) {
        res.send('no post found for this year')
      } else {
        res.send(blogsYear)
      }

    }

    else if (url == '/blog/'+year+'/'+month) {

      const blogsYearMonth = queryBlogs.queryBlogsYearMonth(allBlogs)

      if(blogsYearMonth.length == 0) {
        res.send('no post found for this year and month')
      } else {
        res.send(blogsYearMonth)
      }

    }

    else if (url == '/blog/'+year+'/'+month+'/'+day) {

      const blogsYearMonthDay = queryBlogs.queryBlogsYearMonthDay(allBlogs)

      if(blogsYearMonthDay.length == 0) {
        res.send('no post found for this year and month and day')
      } else {
        res.send(blogsYearMonthDay)
      }

    }

    else {
      res.status(404).send('Not Found')
    }

})

....

</code></pre>



<p>We have defined a route that give different responds depending on the request parameters. We want to see all blogs when the request url == /blog, we see all blogs i.e. of a given year 2019 when the request url == /blog/2019 and so an.</p>




<p>First we load the queryBlogs module with the require() function and save the object in the variable queryBlogs (remember ? queryBlogs return an object of functions).</p>




<p>In the route definition we take the request parameters passed with the request and save them in the variables url, year, month and day. Then we access the queryAllBlogs() function defined in the queryBlogs module with queryBlogs.queryAllBlogs(). This function return all blog posts stored in an array. This array returned by the function will be stored in the variable allBlogs. </p>




<p>Then we create if and else if conditions to define the response behavior of our route definition in our application. </p>




<p>The <strong>first if condition</strong> is true in case the requested url is equal to /blog. The following if condition is true when the allBlogs array is empty. Then the response is no posts found res.send(&#8217;no post found&#8216;). When this if condition is true blog posts have been found in the allBlogs array and this array is not empty. In this case all the data from the allBlogs array will be send back to the caller with res.send(allBlogs). </p>




<p>The <strong>first else if condition</strong> is true in case the requested url is equal to /blog/year (i.e. /blog/2019). When this else if condition is true we call the queryBlogsYear() function in our queryBlogs module and pass the allBlogs array as parameter. We call the function using queryBlogs.queryBlogsYear(allBlogs) and store the returned value in the array variable blogsYear. </p>




<p>If the blogsYear array is empty the following if condition is true. This mean no blog posts have been found for the requested year in allBlogs. Then the response is no posts found for this year res.send(&#8217;no post found for this year&#8216;). When this if condition is false blog posts have been found for the requested year and the blogsYear array is not empty. Then all these data from the blogsYear array will be send back to the caller with res.send(blogsYear). </p>




<p>The <strong>second else if condition</strong> is true in case the requested url is equal to /blog/year/month (i.e. /blog/2019/04). When this else if condition is true we call the queryBlogsYearMonth() function in our queryBlogs module and pass the allBlogs array as parameter. We call the function using queryBlogs.queryBlogsYearMonth(allBlogs) and store the returned value in the array variable blogsYearMonth. </p>




<p>If the blogsYearMonth array is empty the following if condition is true. This mean no blog posts have been found for the requested year and month in allBlogs. Then the response is no posts found for this year and month res.send(&#8217;no post found for this year and month&#8216;). When this if condition is false blog posts have been found for the requested year and month and the blogsYearMonth array is not empty. Then all these data from the blogsYearMonth array will be send back to the caller with res.send(blogsYearMonth). </p>




<p>The <strong>third else if condition</strong> is true in case the requested url is equal to /blog/year/month/day (i.e. /blog/2019/04/10). When this else if condition is true we call the queryBlogsYearMonthDay() function in our queryBlogs module and pass the allBlogs array as parameter. We call the function using queryBlogs.queryBlogsYearMonthDay(allBlogs)and store the returned value in the array variable blogsYearMonthDay. </p>




<p>If the blogsYearMonthDay array is empty the following if condition is true. This mean no blog posts have been found for the requested date in allBlogs. Then the response is no posts found for this year and month and day res.send(&#8217;no post found for this year and month and day&#8216;). When this if condition is false blog posts have been found for the requested date and the blogsYearMonth array is not empty. Then all these data from the blogsYearMonthDay array will be send back to the caller with res.send(blogsYearMonthDay). </p>




<p>The <strong>first if condition</strong> is false in case the requested url does not match any of the defined request parameters (/blog/:year?/:month?/:day?). Then the <strong>else response</strong> is res.status(404).send(&#8218;Not Found&#8216;).</p>




<h3 class="wp-block-heading">Summary and Outlook</h3>



<p>In this Part 2 of my node.js series I explained some very important details of Express and how express can be used in web application development. Based on what we already learned about Express in <a href="https://digitaldocblog.com/webserver/nodejs-series-part-1-create-a-simple-web-server-with-nodejs/" title="Node.js series Part 1. Create a Simple Web-Server with Node.js">Part 1</a>, we looked at Express Routing in detail and how you can use middleware in Express relatively easily. With the knowledge we learned, we then built a simple blog web application and thus applied the knowledge.</p>




<p>This blog app has all the data in a file and no HTML frontend or no HTML template rendering is used. Express is ideally suited for the use of a database like MongoDB and also for the use of HTML templates to present the content. In the next <a href="https://digitaldocblog.com/webserver/nodejs-series-part-3-the-simple-express-blog-app-with-mongodb/" title="Node.js series Part 3. The Simple Express Blog App with MongoDB">Part 3</a> of my node.js series I will show you how our blog app uses a MongoDB database instead of the data file. Then we will continue to develop the blog app and implement HTML rendering.</p>




]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Node.js series Part 1. Create a Simple Web-Server with Node.js</title>
		<link>https://digitaldocblog.com/webserver/nodejs-series-part-1-create-a-simple-web-server-with-nodejs/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Sun, 08 Mar 2020 07:00:00 +0000</pubDate>
				<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Node.js]]></category>
		<category><![CDATA[NPM Node package manager]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=108</guid>

					<description><![CDATA[In this part 1 of my node.js series I briefly explain the creation of a simple node.js web-server that respond to various http requests. I explain the basics of Node.js&#8230;]]></description>
										<content:encoded><![CDATA[
<p>In this part 1 of my node.js series I briefly explain the creation of a simple node.js web-server that respond to various http requests. I explain the basics of Node.js and install the Express.js framework so that it can be used by the simple web-server.</p>




<h3 class="wp-block-heading">Chapter 1: Under the hood of the simple web-server</h3>



<p><a href="https://nodejs.org/en/"><strong>Node.js</strong></a> is based on JavaScript. JavaScript has always been known as a front-end programming language that is executed in the browser. The code runs in the browser. This has changed since node.js because with node.js JavaScript code is executed on the server side. The code no longer runs in the browser but on the server. Node.js provides the build in http module which make it easy to implement a web-server with basic functionalities. Node.js is a runtime environment in which server-side code can be executed. In this respect, node.js is the basic technology for frameworks like <em>Express.js</em>.</p>




<p><a href="https://expressjs.com/"><strong>Express.js</strong></a> is the most widespread node.js framework. In contrast to the pure node.js http module, <em>Express.js</em> offers extensive options for processing client http requests. <em>Express.js</em> also offers middleware functionalities that can be built in between the request and the response to execute the code build in routes depending on the middleware function. This includes authentication and authorization, logging and much more.</p>




<h3 class="wp-block-heading">Chapter 2: Install node.js and npm</h3>



<p>First of all, it must be ensured that <em>node.js</em> and also <em>npm.js</em> are installed on your system. npm is the node package manager and is absolutely necessary if we develop programs with node.<br/></p>




<p>We should definitely check whether node and npm are already installed on the system. To do this, please enter the following commands on the command line.</p>




<pre class="wp-block-code"><code>Patricks-MBP:node patrick$ node --version
v13.8.0

Patricks-MBP:node patrick$ npm --version
6.13.7

Patricks-MBP:node patrick$ 

</code></pre>



<p>In this case, node is already installed in version 13.8.0 and npm in version 6.13.7 and nothing further needs to be done.</p>




<p>I work with Mac OS and there is the standard for installing command line tools <a href="https://brew.sh/index_de">homebrew</a>. I wrote the article <a href="https://digitaldocblog.com/singleblog?article=1">node.js and npm on Mac OS</a> here on <a href="https://digitaldocblog.com">Digitaldocblog</a> that shows step by step the installation of node and npm on a Mac OS. Other ways to install node.js can be found on [nodejs.org] (https://nodejs.org/en/download/).</p>




<p>Especially if we work with Ubuntu linux, node and npm can be installed using the Advanced Packaging Tool (APT). While on Mac OS the installation of node using homebrew includes npm, node and npm are installed separately on Ubuntu. Simply enter the following commands on the command line on your Ubuntu system (but check before if it is already installed). </p>




<pre class="wp-block-code"><code>#: sudo apt install nodejs

#: sudo apt install npm

</code></pre>



<p>Of course, it is also a basic requirement to install a suitable editor. You can of course also work with text editors like <a href="https://www.nano-editor.org/">nano</a> or <a href="http://ex-vi.sourceforge.net/">vi</a>, but that is certainly very cumbersome. From my point of view, the <a href="https://atom.io/">Atom editor</a> is very suitable for node programming and my first choice.</p>




<p>After that, the following requirements should be met:</p>




<ul class="wp-block-list">
	<li>node and npm installed</li>
	<li>text editor installed (i.e. Atom)<br></li>
</ul>



<h3 class="wp-block-heading">Chapter 3: The basics of node.js</h3>



<p>I will briefly go over here to explain how node.js works. For this I will program a simple webserver with plain node.js and you can see what node already offers for creating a web app.</p>




<p>For this purpose, a separate directory for our first node web-app is created on the development system. This is the place we will store all files and the entire program code and called the <em>application root directory</em>. In my case this is the application root directory <em>node-basic</em>.</p>




<pre class="wp-block-code"><code>Patricks-MBP:2020-03-08 patrick$ mkdir node-basic

Patricks-MBP:2020-03-08 patrick$ cd node-basic

Patricks-MBP:node-basic patrick$ ls -al
total 0
drwxr-xr-x  2 patrick  staff  64 08 Mär 06:05 .
drwxr-xr-x  3 patrick  staff  96 08 Mär 06:05 ..

Patricks-MBP:node-basic patrick$ pwd
/Users/patrick/Software/dev/node/myArticles/2020-03-08/node-basic

Patricks-MBP:node-basic patrick$  

</code></pre>



<p>Change to the application root directory you just created, create a file <em>server.js</em> and open this file using your favorite editor. The file <em>server.js</em> is called the <em>application main file</em> and is the entry point of our node web-application.</p>




<pre class="wp-block-code"><code>Patricks-MBP:node-basic patrick$ touch server.js

Patricks-MBP:node-basic patrick$ ls -l
total 0

-rw-r--r--  1 patrick  staff  0 08 Mär 18:22 server.js

Patricks-MBP:node-basic patrick$ 

</code></pre>



<h3 class="wp-block-heading">3.1 Simple node web-server using the integrated &#8218;http&#8216; module</h3>



<p>Enter the following code in <em>server.js</em>. You can find the code on my <a href="https://github.com/prottlaender/node-part-1-simple-node-webserver-V1">GitHub Page</a>.</p>




<pre class="wp-block-code"><code>// server.js

// 1. load http module
const http = require('http')

// 2. create the server
const server = http.createServer(function (req, res) {

    // 3. check conditions for requests
    if (req.url == '/') {

        // 4.1 set response header
        res.writeHead(200, { 'Content-Type': 'text/html' });

        // 4.2 set response content
        res.write('&lt;html&gt;&lt;body&gt;&lt;p&gt;This is my home Page.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;');
        
        // 4.3 End response
        res.end();

    }
    else if (req.url == "/about") {

        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.write('&lt;html&gt;&lt;body&gt;&lt;p&gt;This is my about Page.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;');
        res.end();

    }
    else if (req.url == "/impressum") {

        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.write('&lt;html&gt;&lt;body&gt;&lt;p&gt;This is my Impressum Page.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;');
        res.end();

    }
   else {
      res.writeHead(404, { 'Content-Type': 'text/html' })
      res.write('&lt;html&gt;&lt;body&gt;&lt;p&gt;This path is not available. Invalid request&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;')
      res.end()
    }
});

// 5. server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')


</code></pre>



<p>A webserver is a relatively simple system that processes HTTP requests from a client. node.js contains the <em>http</em> module with which the developer can program http server but also http clients.<br/></p>




<p>So lets see what happens in the code above.</p>




<ol class="wp-block-list">
	<li>The <a href="https://www.w3schools.com/nodejs/obj_http_server.asp">http module</a> is loaded with the require() function and a reference is saved in the variable <em>http</em>. Various functions can be used on the http reference that are provided by the http module.</li>
	<li>On the http reference a server is created with the <a href="https://www.w3schools.com/nodejs/met_http_createserver.asp">http.createServer()</a> function. http.createServer() creates a server object and the reference is saved in the variable <em>server</em>. The server object can listen to ports and receives a <a href="https://www.w3schools.com/nodejs/func_http_requestlistener.asp">requestListener()</a> function as parameter. This function is always executed when the server receives a request from the client and expects 2 parameters such as request and response. These parameters are usually noted with <em>req</em> and <em>res</em>.</li>
	<li><strong>req</strong> is always the first parameter and represent the 	request object. The request object refer to the <a href="https://www.w3schools.com/nodejs/obj_http_incomingmessage.asp">IncomingMessage</a> object. This object has properies and methods like the property <em>url</em> that can be used with <em>req.url</em>.</li>
	<li><strong>res</strong> is always the second parameter and represent the response object. The response object refer to the <a href="https://www.w3schools.com/nodejs/obj_http_serverresponse.asp">ServerResponse</a> object. This object has properties and methods like the method <em>writeHead()</em> that can be used with <em>res.writeHead()</em>.</li>
	<li>In the <strong>if</strong> and <strong>else if</strong> block we compare the requested url <em>req.url</em> with the url we difined in our route (i.e. &#8222;/&#8220; or &#8222;/about&#8220;). For example, if the URL corresponds to &#8222;/about&#8220;, the server should return an about page to the client. If the requested url <em>req.url</em> does not match any of the conditions specified with <em>if</em> and <em>else if</em>, an error message is sent back to the client.</li>
	<li><strong>res.writeHead()</strong>: the function <em>writeHead()</em> sends the http-status code 200 (Ok) as the standard response for successful HTTP requests and the <em>response header</em> back to the client. The response header is an object with the key <em>content-type</em> and the value <em>text/html</em> as here in the example. In case the requested url <em>req.url</em> does not match any of the conditions specified with <em>if</em> and <em>else if</em> writeHead()* sends the http-status code 404 (Not Found).</li>
	<li><strong>res.write()</strong>: The function <em>write()</em> sends a text stream back to the client. In our example <em>write()</em> send HTML that can be parsed by the browser to display a website content.	</li>
	<li><strong>res.end()</strong>: the <em>end()</em> function tells the server that the request has ended.</li>
	<li>At the end of the program, the <em>listen()</em> function is used on the server, which expects the port as the parameter. The port in this example is port 3000. <em>listen()</em> starts the server on port 3000 at the localhost. </li>
	<li>The final statement log a message at the console that the server has been started. <br></li>
</ol>



<h3 class="wp-block-heading">3.2 Code encapsulation using self-developed modules</h3>



<p>In our webserver example we load the <em>node.js integrated http module</em> with the require() function and we used the method createServer() with a requestListener() function that contains the entire server logic.</p>




<p>We write all the code in the <em>server.js</em> application main file which make the code confusing especially when we program more complex applications. </p>




<p>Node offers the possibility to encapsulate code in a separate file. When we have code in a separate file then we speak of a <em>module</em>. Like the integrated http module we load this module into the application main file <em>server.js</em> using the <em>require()</em> function. </p>




<p>With the help of directories you can structure your modules effectively. In this example I want to collect all my modules in a directory called <em>modules</em>. In this directory I create the <em>logic.js</em> file that contain all the server logic.</p>




<p>You can find the code on my <a href="https://github.com/prottlaender/node-part-1-simple-node-webserver-V2">GitHub Page</a>. </p>




<pre class="wp-block-code"><code>Patricks-MBP:node-basic patrick$ ls -l
total 8
-rw-r--r--  1 patrick  staff  1282 08 Mär 05:58 server.js

Patricks-MBP:node-basic patrick$ mkdir modules

Patricks-MBP:node-basic patrick$ ls -l
total 8
drwxr-xr-x  2 patrick  staff    64 08 Mär 07:31 modules
-rw-r--r--  1 patrick  staff  1282 08 Mär 05:58 server.js

Patricks-MBP:node-basic patrick$ touch modules/logic.js

Patricks-MBP:node-basic patrick$ ls -l modules
total 0
-rw-r--r--  1 patrick  staff  0 08 Mär 07:31 logic.js

Patricks-MBP:node-basic patrick$ 

</code></pre>



<p>The server logic that is executed whenever the server receives a request should be in the <em>modules/logic.js</em> file. The <em>modules/logic.js</em> file contains the following code.</p>




<pre class="wp-block-code"><code>// modules/logic.js

const serverlogic = function(req, res) {
    // check conditions for requests
    if (req.url == '/') {

        // set response header
        res.writeHead(200, { 'Content-Type': 'text/html' });

        // set response content
        res.write('&lt;html&gt;&lt;body&gt;&lt;p&gt;This is my home Page.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;');
        // End response
        res.end();

    }
    else if (req.url == "/about") {

        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.write('&lt;html&gt;&lt;body&gt;&lt;p&gt;This is my about Page.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;');
        res.end();

    }
    else if (req.url == "/impressum") {

        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.write('&lt;html&gt;&lt;body&gt;&lt;p&gt;This is my Impressum Page.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;');
        res.end();

    }
    else {
      res.writeHead(404, { 'Content-Type': 'text/html' });
      res.write('&lt;html&gt;&lt;body&gt;&lt;p&gt;This path is not available. Invalid request&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;');
      res.end();
    }
  }

  module.exports = serverlogic;

</code></pre>



<p>The function with all instructions is assigned to the constant <em>serverlogic</em>. At the end of the file, the reference to the function is exported with <em>module.exports</em>. The function serverlogic() is now usable in other files of the application.</p>




<p>In order to integrate the new self-developed module into the <em>server.js</em> file, we use the require function again as follows.</p>




<pre class="wp-block-code"><code>// server.js

// load http integrated node module
const http = require('http')

// load self-developed module serverlogic from modules/logic.js
const serverlogic = require('./modules/logic');

// create the server
const server = http.createServer(serverlogic);

// 5. server listen for any incoming requests
server.listen(3000);

console.log('My node.js web server is alive and running at port 3000')

</code></pre>



<p>With the <em>require()</em> function we load the file <em>modules/logic</em> in the <em>server.js</em> application and assign the constant <em>serverlogic</em>. Now we can use the module in the createServer() method and the server runs exactly as before. </p>




<h3 class="wp-block-heading">3.3 Using third-party modules</h3>



<p>In our webserver example above we see how we can create a simple webserver using the node on-board resources. In our example we include the node.js <em>integrated http module</em>, which has been loaded with the require function and we used methods like createServer() that are provided by this integrated module. And we see how we can encapsulate the server logic to a separate file.</p>




<p>In general the scope of integrated node.js modules such as the <em>http module</em> is very minimal and is limited to the absolutely essential, which means that the node.js core is very compact and stable. The node.js core offers a very mature and stable runtime environment, but there is no comprehensive standard library to develop more complex web-applications easily. Therefore hundreds of thousands different <em>third party modules</em> from the node community are available. </p>




<p><em>Express.js</em> is one of the most used third-party modules to create web-applications. The advantage of a module like Express is that this module contains many methods optimized for the http request handling. When using Express, for example the functions such as writeHead(), write() and end() can be replaced by one single function res.send(). And the node community offers many other third party modules that are tailored to the use of Express.</p>




<p>In order to use a third party module in a web application, it must be installed in the application root directory. The installation is done by the node package manager in the application root directory. Third party modules are also called dependencies. This means the application that lives in the application root directory and is defined by the various js-files contains dependencies of the installed third party modules. </p>




<p>I will therefore first delete the self-developed module so that I only have the application main file server.js in the application root directory.</p>




<pre class="wp-block-code"><code>Patricks-MBP:node-basic patrick$ ls -al
total 8
drwxr-xr-x  4 patrick  staff  128 08 Mär 18:10 .
drwxr-xr-x  3 patrick  staff   96 08 Mär 06:05 ..
drwxr-xr-x  3 patrick  staff   96 08 Mär 07:31 modules
-rw-r--r--  1 patrick  staff  387 08 Mär 07:51 server.js

Patricks-MBP:node-basic patrick$ rm -r modules

Patricks-MBP:node-basic patrick$ ls -al
total 8
drwxr-xr-x  3 patrick  staff   96 08 Mär 06:07 .
drwxr-xr-x  3 patrick  staff   96 08 Mär 06:05 ..
-rw-r--r--  1 patrick  staff  387 08 Mär 07:51 server.js

Patricks-MBP:node-basic patrick$

</code></pre>



<p>The third party module Express can now be easily installed in the <em>application root directory</em> as follows.</p>




<pre class="wp-block-code"><code>Patricks-MBP:node-basic patrick$ pwd
/Users/patrick/Software/dev/node/myArticles/2020-03-08/node-basic

Patricks-MBP:node-basic patrick$ npm install express
npm WARN saveError ENOENT: no such file or directory, open '/Users/patrick/Software/dev/node/myArticles/2020-03-08/node-basic/package.json'
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN enoent ENOENT: no such file or directory, open '/Users/patrick/Software/dev/node/myArticles/2020-03-08/node-basic/package.json'
npm WARN node-basic No description
npm WARN node-basic No repository field.
npm WARN node-basic No README data
npm WARN node-basic No license field.

+ express@4.17.1
added 50 packages from 37 contributors and audited 126 packages in 2.215s
found 0 vulnerabilities

Patricks-MBP:node-basic patrick$ ls -al
total 40
drwxr-xr-x   5 patrick  staff    160 08 Mär 06:14 .
drwxr-xr-x   3 patrick  staff     96 08 Mär 06:05 ..
drwxr-xr-x  52 patrick  staff   1664 08 Mär 06:14 node_modules
-rw-r--r--   1 patrick  staff  14240 08 Mär 06:14 package-lock.json
-rw-r--r--   1 patrick  staff    387 08 Mär 07:51 server.js

Patricks-MBP:node-basic patrick$ 

</code></pre>



<p>The third party module Express.js in version 4.17.1 has been installed successfully in the application root directory. Express.js is now available locally in our application perimeter and can therefore be used in the application.</p>




<p>In the application root directory we see the following changes.</p>




<ul class="wp-block-list">
	<li><strong>node_modules</strong>: the <em>node_modules</em> directory is created during the installation. npm stores all locally installed packages there. If you look into the directory you will see that there is currently more than just the Express module installed. This is due to the fact that Express itself requires modules which in turn were taken into account during this installation and were also installed.</li>
	<li><strong>package-lock.json</strong>: The package-lock.json file is always generated automatically when npm changes the directory content in node_modules. The content of the package-lock.json shows the current complete directory tree of all modules and if you want to install an additional module or dependency it is ensured that the installation process creates exactly the directory tree at the end of the existing plus the directory of the newly installed module.<br></li>
</ul>



<p>It is possible to install several modules in this way in order to use them in the application. If you continue installing modules, you will quickly lose track of which modules are installed in which version.</p>




<p>It is then no longer easy to depoloy the application, for example, from a Mac OS developer system to a Linux production system and keep exactly the same version of the modules used. The goal must be to install all the modules used in development in exactly the same way on another system or in another application root directory. Here node offers a possibility to do this with the help of the <em>package.json</em> file.</p>




<p>The <em>package.json</em> file is in your application root directory and list all modules or packages that have been installed in your application root directory. In node it is said that modules are packages and that an application depends on packages. In this respect, the package.json file lists all the packages on which your application depends on. </p>




<p>In order to demonstrate how we use a package.json file when installing the Express package, I will delete the node_modules directorory again and create a <em>package.json</em> file.</p>




<pre class="wp-block-code"><code>Patricks-MBP:node-basic patrick$ pwd
/Users/patrick/Software/dev/node/myArticles/2020-03-08/node-basic

Patricks-MBP:node-basic patrick$ ls -l
total 40
drwxr-xr-x  52 patrick  staff   1664 08 Mär 06:14 node_modules
-rw-r--r--   1 patrick  staff  14240 08 Mär 06:14 package-lock.json
-rw-r--r--   1 patrick  staff    524 08 Mär 09:34 server.js

Patricks-MBP:node-basic patrick$ rm -r node_modules

Patricks-MBP:node-basic patrick$ ls -l
total 40
-rw-r--r--  1 patrick  staff  14240 08 Mär 06:14 package-lock.json
-rw-r--r--  1 patrick  staff    524 08 Mär 09:34 server.js

Patricks-MBP:node-basic patrick$ touch package.json

Patricks-MBP:node-basic patrick$ ls -l
total 40
-rw-r--r--  1 patrick  staff  14240 08 Mär 06:14 package-lock.json
-rw-r--r--  1 patrick  staff      0 08 Mär 07:19 package.json
-rw-r--r--  1 patrick  staff    524 08 Mär 09:34 server.js

Patricks-MBP:node-basic patrick$

</code></pre>



<p>With the following command using the <em>npm init &#8211;yes</em> I create a default package.json file as follows. This guarantees a correct <em>.json</em> format with a standard structure and <em>node</em> or <em>npm</em> will not throw any error messages with this package.json file when you install packages.</p>




<pre class="wp-block-code"><code>Patricks-MBP:node-basic patrick$ ls -l
total 48
-rw-r--r--  1 patrick  staff  14240 08 Mär 06:14 package-lock.json
-rw-r--r--  1 patrick  staff    106 08 Mär 07:39 package.json
-rw-r--r--  1 patrick  staff    526 08 Mär 07:40 server.js

Patricks-MBP:node-basic patrick$ npm init --yes
Wrote to /Users/patrick/Software/dev/node/myArticles/2020-03-08/node-
basic/package.json:

{
  "name": "node-basic",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" &amp;&amp; exit 1",
    "start": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}


Patricks-MBP:node-basic patrick$

</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: With the command <em>npm init &#8211;yes</em> default values are set in the <em>package.json</em> file. For example, the value of <em>name</em> is assigned to the directory name, the <em>version</em> is always 1.0.0 and <em>main</em> is usually index.js unless like in our case npm finds a server.js file that differs from index.js.</p>
</blockquote>



<p>This package.json file can now be adjusted with our preferred editor according to our preferences. My personal preference is to keep the package.json file as simple as possible, so the default content for me is as follows.</p>




<pre class="wp-block-code"><code>{
  "name": "simple_webserver",
  "version": "0.0.1",
  "main": "server.js",
  "author": "Patrick Rottlaender"
}

</code></pre>



<p>Then I install Express.js again using the command <code>npm install express --save</code>.</p>




<pre class="wp-block-code"><code>Patricks-MBP:node-basic patrick$ ls -l
total 48
-rw-r--r--  1 patrick  staff  14240 08 Mär 06:14 package-lock.json
-rw-r--r--  1 patrick  staff    256 08 Mär 07:44 package.json
-rw-r--r--  1 patrick  staff    526 08 Mär 07:40 server.js

Patricks-MBP:node-basic patrick$ npm install express --save
npm WARN node-basic@1.0.0 No description
npm WARN node-basic@1.0.0 No repository field.

+ express@4.17.1
added 50 packages from 37 contributors and audited 126 packages in 2.895s
found 0 vulnerabilities

Patricks-MBP:node-basic patrick$ ls -l
total 48
drwxr-xr-x  52 patrick  staff   1664 08 Mär 07:46 node_modules
-rw-r--r--   1 patrick  staff  14286 08 Mär 07:46 package-lock.json
-rw-r--r--   1 patrick  staff    306 08 Mär 07:46 package.json
-rw-r--r--   1 patrick  staff    526 08 Mär 07:40 server.js

Patricks-MBP:node-basic patrick$ 

</code></pre>



<p>As you can see, the <em>node_modules</em> directory has also been created, in which the express module and all of its dependencies can now be found. Let&#8217;s take a look at the content of the <em>package.json</em> file, we see that a new object <em>dependency</em> has been added and we see that express in version 4.17.1 exists there.</p>




<pre class="wp-block-code"><code>{
  "name": "simple_webserver",
  "version": "0.0.1",
  "main": "server.js",
  "author": "Patrick Rottlaender",
  "dependencies": {
    "express": "^4.17.1"
  }
}

</code></pre>



<p>A very important point is the spelling of the version of the installed dependency in the package.json file. In our example express was installed in version &#8222;^ 4.17.1&#8220;. The preceding &#8222;^ &#8230;&#8220; tells <em>npm</em> to install a version of at least version 4.17.1 or higher when calling <em>npm install</em>. This means that until the change to a new major version 5, <em>npm</em> would always update to the latest version. This can lead to problems in an application because when versions of dependencies are changed, the code may no longer be compatible and the code must be adapted. </p>




<p>Therefore I always recommend to remove the preceding &#8222;^ &#8230;&#8220; to tell <em>npm</em> that the exact version has to be kept when calling <em>npm install</em>. </p>




<p>The following is the content of the <em>package.json</em> file.</p>




<pre class="wp-block-code"><code>{
  "name": "simple_webserver",
  "version": "0.0.1",
  "main": "server.js",
  "author": "Patrick Rottlaender",
  "dependencies": {
    "express": "4.17.1"
  }
}

</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: It is also possible to tell <em>npm</em> at the time of the installation of a package that the latest version must be installed initially but the entry in the package.json file will be made without the preceding &#8222;^ &#8230;&#8220;. Then <em>npm install</em> does not update the version that is in the package.json file. </p>
</blockquote>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Using the example of the installation of express, the installation would be carried out with the following command: <em>npm install express &#8211;save &#8211;save-exact</em>.</p>
</blockquote>



<p>If we assume we want to install exactly the same packages as dependencies in another directory or even on a different computer, we would proceed as follows. This is exactly the scenario when we have developed and tested an application and now want to bring this application into the production environment.</p>




<ul class="wp-block-list">
	<li>we create a new target application root directory (new target directory)</li>
	<li>we copy the package.json file into the new target directory</li>
	<li>we copy the application files, in our case this is just the file <em>server.js</em> in the new target directory</li>
	<li>we change to the new target directory  </li>
	<li>We run the command <em>npm install</em> without any other option<br></li>
</ul>



<pre class="wp-block-code"><code>Patricks-MBP:node-basic patrick$ pwd
/Users/patrick/Software/dev/node/myArticles/2020-03-08/node-basic

Patricks-MBP:node-basic patrick$ ls -l
total 48
drwxr-xr-x  52 patrick  staff   1664 08 Mär 09:26 node_modules
-rw-r--r--   1 patrick  staff  14300 08 Mär 09:26 package-lock.json
-rw-r--r--   1 patrick  staff    171 08 Mär 09:26 package.json
-rw-r--r--   1 patrick  staff    526 08 Mär 07:40 server.js

Patricks-MBP:node-basic patrick$ cd ..

Patricks-MBP:2020-03-08 patrick$ ls -l
total 0
drwxr-xr-x  6 patrick  staff  192 08 Mär 09:26 node-basic

Patricks-MBP:2020-03-08 patrick$ mkdir express-basic

Patricks-MBP:2020-03-08 patrick$ ls -l
total 0
drwxr-xr-x  2 patrick  staff   64 08 Mär 09:39 express-basic
drwxr-xr-x  6 patrick  staff  192 08 Mär 09:26 node-basic

Patricks-MBP:2020-03-08 patrick$ cp node-basic/package.json express-basic
Patricks-MBP:2020-03-08 patrick$ cp node-basic/server.js express-basic

Patricks-MBP:2020-03-08 patrick$ cd express-basic

Patricks-MBP:express-basic patrick$ ls -l
total 16
-rw-r--r--  1 patrick  staff  163 08 Mär 10:03 package.json
-rw-r--r--  1 patrick  staff  526 08 Mär 09:40 server.js

Patricks-MBP:express-basic patrick$ cat package.json

{
  "name": "simple_webserver",
  "version": "0.0.1",
  "main": "server.js",
  "author": "Patrick Rottlaender",
  "dependencies": {
    "express": "4.17.1"
  }
}

Patricks-MBP:express-basic patrick$ npm install

npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN simple_webserver@0.0.1 No description
npm WARN simple_webserver@0.0.1 No repository field.
npm WARN simple_webserver@0.0.1 No license field.

added 50 packages from 37 contributors and audited 126 packages in 1.864s
found 0 vulnerabilities

Patricks-MBP:express-basic patrick$ ls -l
total 48
drwxr-xr-x  52 patrick  staff   1664 08 Mär 10:05 node_modules
-rw-r--r--   1 patrick  staff  14292 08 Mär 10:05 package-lock.json
-rw-r--r--   1 patrick  staff    163 08 Mär 10:03 package.json
-rw-r--r--   1 patrick  staff    526 08 Mär 09:40 server.js

Patricks-MBP:express-basic patrick$  

</code></pre>



<h3 class="wp-block-heading">Chapter 4: The simple webserver using the Express.js module</h3>



<p>We are now working in the following directory. You can find the code on my <a href="https://github.com/prottlaender/node-part-1-simple-express-webserver">GitHub Page</a>.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ pwd
/Users/patrick/Software/dev/node/myArticles/2020-03-08/express-basic

Patricks-MBP:express-basic patrick$ 
</code></pre>



<p>If we now want to use the Express module in our web application, we have to load the express module with the require() function. The express module returns a function and this function is stored in the constant <em>express</em>. When you call the express() function an <em>app object</em> will be returned. This app object will be now stored in the const <em>app</em> and can now be used in the <em>server.js</em> file. The code in server.js is now as follows.</p>




<pre class="wp-block-code"><code>// server.js

// load http module
const http = require('http')

// load the Express module
const express = require('express')

const app = express()

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My express web server is alive and running at port 3000')

</code></pre>



<p>As we can see in the code, we are now using the app as a parameter for the createServer() function. createServer() therefore creates and express web-server. Now lets run the code with node server.js and see what happens.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ node server.js
My express web server is alive and running at port 3000

</code></pre>



<p>Perfect! The express server is running on localhost port 3000. But if we enter <em>http://localhost:3000</em> in the browser we get an error message <em>Cannot GET /</em>. This is actually clear since we have not yet defined any routes in the code that tell the server how to respond to a request for the route &#8222;/&#8220;. </p>




<p>But there is something else interesting: the response of the server with the text <em>Cannot GET /</em> is already a website. That means Express already takes over for us the part of the error handling that we programmed without Express in the code above in a specific instruction.</p>




<p>This becomes even clearer when we issue an http request in the terminal with the help of the curl program.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ curl -i http://localhost:3000
HTTP/1.1 404 Not Found
Content-Security-Policy: default-src 'none'
X-Content-Type-Options: nosniff
Content-Type: text/html; charset=utf-8
Content-Length: 139
Date: Sun, 15 Mar 2020 06:15:11 GMT
Connection: keep-alive

&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
&lt;meta charset="utf-8"&gt;
&lt;title&gt;Error&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;pre&gt;Cannot GET /&lt;/pre&gt;
&lt;/body&gt;
&lt;/html&gt;

Patricks-MBP:express-basic patrick$ 

</code></pre>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p><strong>note</strong>: curl is a program to transfer data from and to a server. curl supports some protocols including http. The -i option include the HTTP response headers in the output. The HTTP response headers include i.e. HTTP version, Content-Type, Date etc. </p>
</blockquote>



<p>In the curl output above you can exactly see that the response from the server is a 404 Not Found http code and an html document to display an error HTML document in the browser. This is something that Express do for us. </p>




<p>So now we have to define routes to tell the server what to send in response when a particular route is requested. There are Express functions for certain http verbs like get(), post(), put() or delete() already available in Express. These functions are used on the app, which we referenced in our code with the constant app. So lets change our code as follows.</p>




<pre class="wp-block-code"><code>// server.js

// load http module
const http = require('http')

// load the Express module
const express = require('express')

const app = express()

// define the routes
app.get('/', (req, res) =&gt; {
  res.send('Hello, this is my home Page')
})

app.get('/about', (req, res) =&gt; {
  res.send('Hello, this is my about Page')
})

// create the server
const server = http.createServer(app);

// server listen for any incoming requests
server.listen(3000);

console.log('My express web server is alive and running at port 3000')


</code></pre>



<p>As you see I added a route definition for get(). The http verb functions like get() expect two parameters:</p>




<ol class="wp-block-list">
	<li>the route to be defined</li>
	<li>the http handler which is transferred as a callback function with the parameters req and res. This http handler will give the server the instructions it needs to respond to the request for this particular route. Other routes like /about require a separate route definition.<br></li>
</ol>



<p>We start <em>server.js</em> again and request the routes via the browser with <em>http://localhost:3000</em> and <em>http://localhost:3000/about</em> and find that the response defined in <em>res.send</em> is sent as HTML from the server. Thats good. </p>




<p>With curl and requesting route <em>/about</em> we get the following terminal output.</p>




<pre class="wp-block-code"><code>Patricks-MBP:express-basic patrick$ curl -i http://localhost:3000/about

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 28
ETag: W/"1c-qDZjsFWQH9PGrhmVTBEfY+9a0Fo"
Date: Sun, 15 Mar 2020 05:44:19 GMT
Connection: keep-alive

Hello, this is my about Page

Patricks-MBP:express-basic patrick$

</code></pre>



<p>The curl output now show 202 Ok http code and the html document content <em>Hello, this is my about Page</em>. So its all working fine so far.</p>




<h3 class="wp-block-heading">Summary and Outlook</h3>



<p>In this Part 1 I created a simple node.js web-server that respond to various http requests from a browser. I explained the basics of Node.js such as the installation of node and npm, the use of modules and configuration of the package.json file. Then I installed the Express.js framework as a dependency of my server application and configured it so that it can be used by the simple web-server.</p>




<p>In the following <a href="https://digitaldocblog.com/webserver/nodejs-series-part-2-create-a-simple-blog-app-with-expressjs/" title="Node.js series Part 2. Create a Simple Blog App with Express.js">Part 2</a> of this node.js series I will explain the Express.js framework much more in detail. We will learn something about web api(s) and we will understand how we can use Express as middleware in a web application. And we will create a small blog application. </p>




]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Initialization and Deployment of a firebase project</title>
		<link>https://digitaldocblog.com/webserver/initialization-and-deployment-of-a-firebase-project/</link>
		
		<dc:creator><![CDATA[admin]]></dc:creator>
		<pubDate>Thu, 19 Dec 2019 19:00:00 +0000</pubDate>
				<category><![CDATA[Webserver]]></category>
		<category><![CDATA[Firebase]]></category>
		<category><![CDATA[Google]]></category>
		<guid isPermaLink="false">https://digitaldocblog.com/?p=94</guid>

					<description><![CDATA[The use of Firebase Services requires the global installation of the Firebase Command Line Tools (Firebase CLI) on your computer. Note: I will not go into the installation of the&#8230;]]></description>
										<content:encoded><![CDATA[
<p>The use of Firebase Services requires the global installation of the Firebase Command Line Tools (Firebase CLI) on your computer.<br></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Note: I will not go into the installation of the Firebase CLI here. How this is installed on the computer is well described in <a href="https://www.fullstackfirebase.com/firebase-console/walkthrough">Full-Stack Firebase</a> and in the <a href="https://firebase.google.com/docs/cli">Google documentation</a>.</p></blockquote>



<h4 class="wp-block-heading">Overview</h4>



<p>Before I start a firebase project, I first think of a suitable name for the project and create one project for development <em>(Firebase Project Dev)</em> and another different one for production <em>(Firebase Project Prod)</em> in the <a href="https://console.firebase.google.com/">Firebase Console</a>. As a result, 2 projects with the following names are created in the Firebase Console <em>(see the Browser side chapter below)</em>.</p>



<ul class="wp-block-list"><li>myproject-Dev</li><li>myproject-Prod<br></li></ul>



<p>In addition, 2 directories must be created on the computer. One directory as <em>Directory Dev</em> and another as <em>Directory Prod</em>. Ideally, these directories have the same names as the projects in the Firebase Console <em>(see the Command line side chapter below)</em>.</p>



<ul class="wp-block-list"><li>myproject-Dev</li><li>myproject-Prod<br></li></ul>



<p>As shown in the figure above, the directories on the computer and the Firebase projects logically each represent a system. So in my example I have a <em>Dev System</em> and a <em>Prod System</em>. The two systems Prod and Dev are completely separated from each other.</p>



<p>The system development takes place on the computer. The files and directories for development are located on the computer directly in the <code>myproject-Dev</code> directory. The public subdirectory in the myproject-Dev directory contains all the files and directories relevant to the dev depolyment. The <em>Dev deploy</em> process copies these relevant files and directories for dev deployment there <em>(see the Dev deployment chapter below)</em>.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Note: The system development is not part of this tutorial.</p></blockquote>



<p>The files and directories for production are in the public subdirectory in the <code>myproject-Prod</code> directory. The process <em>Prod deploy</em> copies these relevant files and directories for the Prod deployment there <em>(see the chapter Prod deployment below)</em>.</p>



<p>The directories and files for Firebase hosting are copied from the relevant public directory and are performed by the Firebase CLI function <code>firebase deploy</code>. In our example here we have hosting of the <em>Dev System</em> and hosting of the <em>Prod System</em>. The <em>Dev System</em> is used for testing under production conditions. The <em>Prod System</em> represents the actual live system on the web.</p>



<h4 class="wp-block-heading">Browser side</h4>



<p>The following activities are carried out in the browser.</p>



<p>How a Firebase project is created in the firebase console can be read in detail very well on <a href="https://www.fullstackfirebase.com/firebase-console/walkthrough">Full-Stack Firebase</a>. I will only go into the basic steps of creating a project here.</p>



<p>First we go to the <a href="https://console.firebase.google.com/">Firebase Console</a> in the browser. When initially creating a firebase project in the Firebase Console, the following steps are carried out. With this procedure we create the two projects <code>myproject-Dev</code> and <code>myproject-Prod</code> in the Firebase Console.</p>



<ol class="wp-block-list"><li>Click Add Project</li><li>Assign a project name</li><li>Switch on Google Analytics (default) and configure Analytics account<br></li></ol>



<p>After the initial creation of the project in the Firebase Console, you go directly to the project overview in the project console of the newly created project. Here a Firebase app must be added to the project. In order to fully understand this tutorial, a web app is added to the project to create a website (iOS or Android mobile apps can also be added here later).</p>



<ol class="wp-block-list"><li>Click Add Web-App</li><li>Register the Alias name for the Web-App <code>myproject-Dev-WebApp</code></li><li>Firebase hosting is to be set up for this web app</li><li>Then follow 3 steps for information that are simply noted (add Firebase SDK, install Firebase CLI, provide Firebase hosting)</li><li>Then go back to the project console project overview<br></li></ol>



<p>In the project overview, click on the settings of the web app <code>myproject-Dev-WebApp</code> in the top left of the main window. The following configurations must be made here in the General area:</p>



<ol class="wp-block-list"><li>Google Cloud Platform (GCP) resource location</li><li>Support Email Address<br></li></ol>



<p>When setting the default location for the cloud resources, it is important to know that this can only be done once here at this point. I choose here as the location of the cloud resources <code>europe-west3</code>. At this point, of course, any other location can be selected depending on which is meaningful or necessary for the developer.</p>



<p>This process must also be repeated for <code>myproject-Prod</code>.</p>



<h4 class="wp-block-heading">Command line side</h4>



<p>The following activities are carried out in the terminal or on the command line of the computer. The computer I use is a Mac. As mentioned above these steps require the installation of the Firebase CLI.</p>



<p>First you log on to the Firebase Console. To do this, the <code>firebase login</code> command must be entered in the command line. Then all other commands can be entered in the command line.</p>



<p>With the command <code>firebase projects:list</code> in the command line the projects created in the firebase console can now be listed.</p>



<p>|Project Display Name   |Project ID            |Resource Location ID |</p>



<p>|&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;|&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-|&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;|</p>



<p>|myproject-Prod 	      | myproject-prod-19842 | europe-west3        |<br>|myproject-Dev 	  	    | myproject-dev-ace74  | europe-west3        |<br></p>



<p>Then I create two folders locally on my computer, one for development and one for production and initialize firebase in each of the folders.</p>



<p>Later in this article I&#8217;ll go into how depolyment from development to production takes place.</p>



<p>In order to initialize the Firebase project on the computer the following steps have to be followed.</p>



<p><strong>Step 0</strong>: Create Folders on the computer</p>



<p>In the initial situation, I have already created 2 folders on my computer: One for development and a different one for production. These folders are:</p>



<ul class="wp-block-list"><li>myproject-Dev</li><li>myproject-Prod<br></li></ul>



<pre class="wp-block-code"><code>Patricks-MBP:firebase-test patrick$ ls -l
total 0
drwxr-xr-x  2 patrick  staff  64 17 Dez 17:26 myproject-Dev
drwxr-xr-x  2 patrick  staff  64 17 Dez 17:25 myproject-Prod
Patricks-MBP:firebase-test patrick$

</code></pre>



<p>First I initialize firebase for <code>myproject-Dev</code>. The Steps 1 to the final step must be repeated in the same way for <code>myproject-Prod</code>.</p>



<p>To do this, I change to the myproject-Dev directory on my computer and enter the command <code>firebase init</code> on the command line of my computer.</p>



<pre class="wp-block-code"><code>Patricks-MBP:firebase-test patrick$ cd myproject-Dev
Patricks-MBP:myproject-Dev patrick$ firebase init

</code></pre>



<p><em>firebase init</em>: The command <code>firebase-init</code> links the development project previously created in the Firebase Console to the <code>myproject-Dev</code> directory. After entering the command, the developer has to complete different stepts to setup the project. Once the process has been completed, various files are created in <code>myproject-Dev</code>, which I will explain in parts below.</p>



<p>The first step to be completed by the developer relates to the Firebase services to be used. I will use firestore, hosting and storage in this example.</p>



<p><strong>Step 1</strong>: Initialize a Firebase project</p>



<pre class="wp-block-code"><code>Patricks-MBP:myproject-Dev patrick$ firebase init

     ######## #### ########  ######## ########     ###     ######  ########
     ##        ##  ##     ## ##       ##     ##  ##   ##  ##       ##
     ######    ##  ########  ######   ########  #########  ######  ######
     ##        ##  ##    ##  ##       ##     ## ##     ##       ## ##
     ##       #### ##     ## ######## ########  ##     ##  ######  ########

You're about to initialize a Firebase project in this directory:

  /Users/patrick/Sites/dev/tutorials/firebase-test/myproject-Dev

? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices.
 ◯ Database: Deploy Firebase Realtime Database Rules
 ◉ Firestore: Deploy rules and create indexes for Firestore
 ◯ Functions: Configure and deploy Cloud Functions
 ◉ Hosting: Configure and deploy Firebase Hosting sites
❯◉ Storage: Deploy Cloud Storage security rules
 ◯ Emulators: Set up local emulators for Firebase features

</code></pre>



<p><em>Firestore</em>: Modern <code>json</code> database. This database can be accessed via the JavaScript SDK offered by Google in the JavaScript of the application. Access rights to the database are controlled via security rules that can be configured locally and loaded into the project using the deployment process.</p>



<p><em>Hosting</em>: Self-explanatory. The project can be hosted on a Google webserver.</p>



<p><em>Storage</em>: File server that can also be accessed via the JavaScript SDK in the application&#8217;s JavaScript. Access rights to the cloud storage are controlled via security rules that can be configured locally and loaded into the project using the deployment process.</p>



<p>After step 1 has been completed, the project is connected to the current folder on the computer (the directory <code>myproject-Dev</code> we are in).</p>



<p>After the first step has been completed with Enter, the second step is to connect the project directory we are in on the computer <code>myproject-Dev</code> to the Firebase Project <code>myproject-Dev</code>, which was previously created in the Firebase Console.</p>



<p><strong>Step 2</strong>: Project Setup</p>



<pre class="wp-block-code"><code>=== Project Setup

First, let's associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we'll just set up a default project.

? Please select an option: Use an existing project
? Select a default Firebase project for this directory:  
❯ myproject-dev-ace74 (myproject-Dev)
  myproject-prod-19842 (myproject-Prod)

</code></pre>



<p>Steps 3 to 5 configure the services previously selected in Step 1.</p>



<p><strong>Step 3</strong>: Firestore Setup</p>



<pre class="wp-block-code"><code>=== Firestore Setup

Firestore Security Rules allow you to define how and when to allow
requests. You can keep these rules in your project directory
and publish them with firebase deploy.

? What file should be used for Firestore Rules? firestore.rules

Firestore indexes allow you to perform complex queries while
maintaining performance that scales with the size of the result
set. You can keep index definitions in your project directory
and publish them with firebase deploy.

? What file should be used for Firestore indexes? firestore.indexes.json

</code></pre>



<p><strong>Step 4</strong> Hosting Setup</p>



<pre class="wp-block-code"><code>=== Hosting Setup

Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build's output directory.

? What do you want to use as your public directory? public
? Configure as a single-page app (rewrite all urls to /index.html)? Yes
&#x2714;  Wrote public/index.html

</code></pre>



<p><strong>Step 5</strong>: Storage Setup</p>



<pre class="wp-block-code"><code>=== Storage Setup

Firebase Storage Security Rules allow you to define how and when to allow
uploads and downloads. You can keep these rules in your project directory
and publish them with firebase deploy.

? What file should be used for Storage Rules? storage.rules

</code></pre>



<p><strong>Final Step</strong>: Writing Files</p>



<pre class="wp-block-code"><code>i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...
i  Writing gitignore file to .gitignore...

&#x2714;  Firebase initialization complete!

</code></pre>



<p>The directory <code>myproject-Dev</code> now has the following content.</p>



<pre class="wp-block-code"><code>Patricks-MBP:myproject-Dev patrick$ ls -l
total 32
-rw-r--r--  1 patrick  staff  379 18 Dez 07:30 firebase.json
-rw-r--r--  1 patrick  staff  563 18 Dez 07:27 firestore.indexes.json
-rw-r--r--  1 patrick  staff  132 18 Dez 07:27 firestore.rules
drwxr-xr-x  3 patrick  staff   96 18 Dez 07:29 public
-rw-r--r--  1 patrick  staff  138 18 Dez 07:30 storage.rules

</code></pre>



<p>Here you can see that the files for controlling access and indexes to the firestore and the files for controlling access to storage were created. Rules and indexes are transferred with the <code>firebase deploy</code> command. It is therefore important to enter any changes to the rules that may have been made during development in the Firebase Console here.</p>



<p>In addition, the public directory was created. The application is deployed from this directory with the <code>firebase deploy</code> command.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Note: I use a local apache web server for development on my Mac. This web server is configured so that websites in a local directory within my user directory are displayed as private websites (localhost). The directory <code>myproject-Dev</code> is created under localhost. In this respect, html files, css and Javascript files etc. can be created in <code>myproject-Dev</code> directory and displayed from there via the local web server in the browser by entering the url <em>localhost/~user/myproject-Dev</em>. This helps the developer to see his results and to do some debugging where necessary. A very good description of how to set up a local web server on a Mac can be found at <a href="https://websitebeaver.com/set-up-localhost-on-macos-high-sierra-apache-mysql-and-php-7-with-sslhttps">Website Beaver</a>.</p></blockquote>



<p>As an example for a typical web application, the following directories and files must be created in the <code>myproject-Dev</code> directory.</p>



<ul class="wp-block-list"><li>css Directory<br><ul>
			<li>mystyles.css File</li>
		</ul></li><li>script Directotry<br><ul>
			<li>main.js File</li>
		</ul></li><li>images Directory<br><ul>
			<li>.png or jpg. Files</li>
		</ul></li><li>dev-versions Directory</li><li>index.html File</li><li>deploy-dev File</li><li>deploy-prod File<br></li></ul>



<p>The directories <code>css</code>, <code>script</code> and <code>images</code> and their files are created for web application development. The <code>dev-versions</code> directory is is used for versioning the web application development results.</p>



<p>The files <code>deploy-dev</code> and <code>deploy-prod</code> each contain a script for performing the respective deployment either in the Dev or Prod.</p>



<p>A typical directory structure in <code>myproject-Dev</code> looks like this.</p>



<pre class="wp-block-code"><code>Patricks-MBP:myproject-Dev patrick$ ls -l
total 48
drwxr-xr-x  3 patrick  staff   96 18 Dez 11:09 css
-rwxr--r--  1 patrick  staff  251 18 Dez 15:00 deploy-dev
-rwxr--r--  1 patrick  staff  240 18 Dez 14:54 deploy-prod
drwxr-xr-x  3 patrick  staff   96 18 Dez 15:01 dev-versions
-rw-r--r--  1 patrick  staff  379 18 Dez 07:30 firebase.json
-rw-r--r--  1 patrick  staff   10 18 Dez 12:31 firestore.indexes.json
-rw-r--r--  1 patrick  staff   31 18 Dez 11:05 firestore.rules
drwxr-xr-x  4 patrick  staff  128 18 Dez 12:32 images
-rw-r--r--  1 patrick  staff  386 18 Dez 11:31 index.html
drwxr-xr-x  8 patrick  staff  256 18 Dez 15:01 public
drwxr-xr-x  3 patrick  staff   96 18 Dez 11:10 script
-rw-r--r--  1 patrick  staff   31 18 Dez 11:05 storage.rules

</code></pre>



<p>The Firebase initialization for the development system is then completed.</p>



<p>As already mentioned above, this process must be repeated for <code>myproject-Prod</code> from step 1 in order to also initialize the production system accordingly. After initialization, only the <code>prod-versions</code> directory has to be created as subdirectory of the <code>myproject-Prod</code> directory. This directory is is used for versioning the web application.</p>



<p>A typical directory structure in <code>myproject-Prod</code> looks like this.</p>



<pre class="wp-block-code"><code>
Patricks-MBP:myproject-Prod patrick$ ls -l
total 32
-rw-r--r--  1 patrick  staff  27 18 Dez 18:45 firebase.json
-rw-r--r--  1 patrick  staff  28 18 Dez 18:45 firestore.indexes.json
-rw-r--r--  1 patrick  staff  21 18 Dez 18:46 firestore.rules
drwxr-xr-x  2 patrick  staff  64 18 Dez 15:15 prod-versions
drwxr-xr-x  2 patrick  staff  64 18 Dez 18:35 public
-rw-r--r--  1 patrick  staff  19 18 Dez 18:46 storage.rules

</code></pre>



<h4 class="wp-block-heading">Dev-Deployment</h4>



<p>To deploy the application to the Dev-System in Firebase, all the necessary files must  be copied to the public directory under <code>myproject-Dev</code>. The bash script <code>deploy-dev</code> does this.</p>



<pre class="wp-block-code"><code>Patricks-MBP:myproject-Dev patrick$ cat deploy-dev
#!/bin/bash

date=$(date +"%Y.%m.%d_%H.%M.%S")

mkdir dev-versions/dev-ver_$date

cp -Rip public dev-versions/dev-ver_$date

rm -R public/*

cp -ip index.html public
cp -Rip css public
cp -Rip script public
cp -Rip images public

</code></pre>



<p><code>deploy-dev</code> stores the current date and time in the variable <code>date</code>. Then a subdirectory named like <code>dev-ver_$date</code> is created under the directory <code>dev-versions</code> and the entire <code>public</code> directory is copied there. The <code>public</code> directory is then emptied and the new application files are copied to the <code>public</code> directory.</p>



<p>Then the command <code>firestore deploy</code> must be called on the command line and the deployment from the current public directory to the dev system is started.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Note: The command <code>firestore deploy</code> could also be added to the deploy-dev script. I prefer to run this command separately afterwards.</p></blockquote>



<p>After the Firebase Deployment is complete, you get a project url in the command line with which you can access via the web the Dev-System that has just been published. Now the tests can be performed using the Dev-System url.</p>



<h3 class="wp-block-heading">Prod-Deployment</h3>



<p>After the tests have all been successfully completed, the application can be published in the Prod system.</p>



<p>We are still in the <code>myproject-Dev</code> directory.</p>



<p>The goal is to copy all files from the <code>public</code> directory in <code>myproject-Dev</code> into the <code>public</code> directory of <code>myproject-Prod</code>. The bash script <code>deploy-prod</code> does this.</p>



<pre class="wp-block-code"><code>Patricks-MBP:myproject-Dev patrick$ cat deploy-prod
#!/bin/bash

date=$(date +"%Y.%m.%d_%H.%M.%S")

mkdir ../myproject-Prod/prod-versions/prod-ver_$date

cp -Rip ../myproject-Prod/public ../myproject-Prod/prod-versions/prod-ver_$date

rm -R ../myproject-Prod/public/*

cp -Rip public/* ../myproject-Prod/public

</code></pre>



<p><code>deploy-prod</code> stores the current date and time in the variable <code>date</code>. Then on <code>myproject-Prod</code> a subdirectory named like <code>prod-ver_$date</code> is created under the directory <code>prod-versions</code> and the entire directory public is copied there. The public directory on myproject-Prod is then emptied.</p>



<p>The new application files are copied from the <code>public</code>directory in <code>myproject-Dev</code>to the <code>public</code> directory of <code>myproject-Prod</code>.</p>



<p>Then the command <code>firestore deploy</code> must be called on the command line and the deployment from the public directory to the prod system is started.</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"><p>Note: The command <code>firestore deploy</code> could also be added to the deploy-prod script. I prefer to run this command separately afterwards.</p></blockquote>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
