<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Qovery</title>
    <description>The latest articles on DEV Community by Qovery (@qovery).</description>
    <link>https://dev.to/qovery</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1855%2Fa89bdfe5-e387-45ce-acbd-007da49da898.png</url>
      <title>DEV Community: Qovery</title>
      <link>https://dev.to/qovery</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/qovery"/>
    <language>en</language>
    <item>
      <title>The importance of SemVer for your applications</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Sun, 23 Jan 2022 09:43:09 +0000</pubDate>
      <link>https://dev.to/qovery/the-importance-of-semver-for-your-applications-1p6d</link>
      <guid>https://dev.to/qovery/the-importance-of-semver-for-your-applications-1p6d</guid>
      <description>&lt;p&gt;For some developers, SemVer can look just cosmetic, nice to have, or simply useless. &lt;strong&gt;But SemVer format is mandatory&lt;/strong&gt; to make reliable software.&lt;/p&gt;

&lt;p&gt;I'll explain how over one year, we encountered 2 issues related to SemVer. The first one was critical and led to a production outage, while the other was a lot of trouble for several companies to upgrade a managed service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is SemVeR?
&lt;/h2&gt;

&lt;p&gt;First of all, let's remind us what SemVer is: SemVer (for Semantic Versioning) is the process of assigning either "uniqueversion' names or "uniqueVersion" numbers to unique states of computer software. Within a given version number category (e.g., major, minor), these numbers are generally assigned in increasing order and correspond to new developments in the software. [Wikipedia]&lt;/p&gt;

&lt;h1&gt;
  
  
  1st issue: Patch version
&lt;/h1&gt;

&lt;p&gt;To get a bit of context, we are using EKS (Kubernetes on AWS) for Qovery production, and we wanted to remove the SSH access (remote) from our Kubernetes nodes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Config change
&lt;/h2&gt;

&lt;p&gt;On Terraform, it was just a few lines to remove:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="s"&gt;"aws_eks_node_group"&lt;/span&gt; &lt;span class="s"&gt;"eks-cluster-workers"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;cluster_name&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="py"&gt;.eks_cluster.name&lt;/span&gt;
 &lt;span class="o"&gt;...&lt;/span&gt;
    &lt;span class="c1"&gt;// lines removed&lt;/span&gt;
   &lt;span class="n"&gt;remote_access&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="n"&gt;ec2_ssh_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"qovery"&lt;/span&gt;
     &lt;span class="n"&gt;source_security_group_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;aws_security_group&lt;/span&gt;&lt;span class="py"&gt;.eks_cluster_workers.id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
 &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The applying workflow for those changes was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Force the deployment of new EKS nodes (EC2 instances behind it)&lt;/li&gt;
&lt;li&gt;Move pods (a set of containers) from old nodes to fresh new nodes&lt;/li&gt;
&lt;li&gt;Delete old nodes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before running the changes, we checked that every pod on the cluster was running smoothly; there was no issue. So we decided to apply the change as everything looked OK and the operation "as usual".&lt;/p&gt;

&lt;h2&gt;
  
  
  Outage
&lt;/h2&gt;

&lt;p&gt;The rollout occurred, and all pods moved from the old nodes to the new ones. Pods were able to start without problems...instead of one! Our Qovery Engine (written in Rust), is dedicated to infrastructure deployments.&lt;/p&gt;

&lt;p&gt;The pod was crashing 2s after starting during the initialization phase and was in CrashloopBackOff status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;2022-01-16T16:52:09Z DEBUG app::utils: message: Requesting deployment task at engine.local.infrastructure                                                    
thread &lt;span class="s1"&gt;'tokio-warp-http'&lt;/span&gt; panicked at &lt;span class="s1"&gt;'called `Result::unwrap()` on an `Err` value: Other("Failed to parse patch version")'&lt;/span&gt;, /usr/local/cargo/registry/src/git
hub.com-1ecc6299db9ec823/procfs-0.9.1/src/lib.rs:303:34
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've got an unwrap() here (non caught error) on a Result. So this issue was voluntary, not handled, or definitively not expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Investigation
&lt;/h2&gt;

&lt;p&gt;Looking deeper into our Cargo dependencies (Cargo.toml), we did not use this "procfs" library directly. Meaning it's a library dependency, so we dug into the Cargo.lock to find the dependency and bingo! Prometheus library was using it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[package]]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"prometheus"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.12.0"&lt;/span&gt;
&lt;span class="py"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"registry+https://github.com/rust-lang/crates.io-index"&lt;/span&gt;
&lt;span class="py"&gt;checksum&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"5986aa8d62380092d2f50f8b1cdba9cb9b6731ffd4b25b51fd126b6c3e05b99c"&lt;/span&gt;
&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
 &lt;span class="s"&gt;"cfg-if 1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;"fnv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;"lazy_static"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;"libc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;"memchr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;"parking_lot 0.11.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;"procfs 0.9.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;"protobuf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
 &lt;span class="s"&gt;"thiserror"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright, we see the procfs version is 0.9.1 as described in the error message. So looking at the code in &lt;a href="https://github.com/eminence/procfs/blob/v0.9.1/src/lib.rs#L302"&gt;src/lib.rs&lt;/a&gt; line 303 we got this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt; &lt;span class="cd"&gt;/// The version of the currently running kernel.&lt;/span&gt;
    &lt;span class="cd"&gt;///&lt;/span&gt;
    &lt;span class="cd"&gt;/// This is a lazily constructed static.  You can also get this information via&lt;/span&gt;
    &lt;span class="cd"&gt;/// [KernelVersion::new()].&lt;/span&gt;
    &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;ref&lt;/span&gt; &lt;span class="n"&gt;KERNEL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KernelVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;KernelVersion&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see inside current() that it reads a file (the kernel version):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;    &lt;span class="cd"&gt;/// Returns the kernel version of the currently running kernel.&lt;/span&gt;
    &lt;span class="cd"&gt;///&lt;/span&gt;
    &lt;span class="cd"&gt;/// This is taken from `/proc/sys/kernel/osrelease`;&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;current&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ProcResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;read_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/proc/sys/kernel/osrelease"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the function in charge of this is splitting on dot and ASCII digits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;from_str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.find&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sc"&gt;'.'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="nf"&gt;.is_ascii_digit&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="nf"&gt;.split_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;s&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;kernel_split&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kernel&lt;/span&gt;&lt;span class="nf"&gt;.split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sc"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="nf"&gt;.parse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"Failed to parse patch version"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;major&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here comes the fun fact! Let's take a look at the kernel version on AWS EC2 instances deployed by EKS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;4.14.256-197.484.amzn2.x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we expected to have this struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;major&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;minor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So here is the problem in the kernel number (4.14.256-197.484.amzn2.x86_64).&lt;/p&gt;

&lt;p&gt;256: we expected to have a patch in u8, whereas &lt;a href="https://doc.rust-lang.org/book/ch03-02-data-types.html#scalar-types"&gt;the max of a u8 is 255&lt;/a&gt;, so we needed at least a u16. (&lt;a href="https://github.com/eminence/procfs/pull/140"&gt;https://github.com/eminence/procfs/pull/140&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It's not common to have such a high number, but not rare. We can see it on &lt;a href="https://en.wikipedia.org/wiki/Google_Chrome_version_history"&gt;Chrome, vanilla Kernel&lt;/a&gt;, and many other big projects. After the patch version corresponds to a build change, it's also common to find the "-xxx" version.&lt;/p&gt;

&lt;p&gt;Unfortunately, this library wasn't expecting to have numbers higher than 255, and SemVer discussion about this topic doesn't look to be simple: &lt;a href="https://github.com/semver/semver/issues/304"&gt;https://github.com/semver/semver/issues/304&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solutions and fix
&lt;/h2&gt;

&lt;p&gt;At this moment, we had 3 solutions in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Temporarily remove Prometheus lib&lt;/li&gt;
&lt;li&gt;Fix by ourselves those issues report them upstream. It can take some time, depending on the number of changes and impact behind it (primarily for lib retro-compatibility)&lt;/li&gt;
&lt;li&gt;Look into commits, and try to move to newer versions to see if those issues were fixed&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Temporarily remove Prometheus lib
&lt;/h3&gt;

&lt;p&gt;We decided not to remove Prometheus lib, because at Qovery, it's not only used for observability purposes but also to manage the Kubernetes Horizontal Pod Autoscaler (HPA). It helps us to absorb a load of infrastructure changes automatically and scale accordingly to the number of &lt;a href="https://github.com/Qovery/engine"&gt;Engines&lt;/a&gt; for deployment requests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0E1I-Beo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.prismic.io/qovery/85012930-2930-4986-b9cb-e9359e6bc2c7_engine_deployments.png%3Fauto%3Dcompress%2Cformat" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0E1I-Beo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://images.prismic.io/qovery/85012930-2930-4986-b9cb-e9359e6bc2c7_engine_deployments.png%3Fauto%3Dcompress%2Cformat" alt="engine infra HPA" width="880" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Fix by ourselves those issues
&lt;/h3&gt;

&lt;p&gt;We wanted to go fast. Making a fork, patching libs, and later on, making a PR to the official project repository sounds a good solution, but it would take time. So before investing this time, we needed to be sure that newer commits of profs and Prometheus were not embedding fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Move to newer versions
&lt;/h3&gt;

&lt;p&gt;Finally, "procfs" in the latest released version had the bug fixed! Prometheus lib was not up to date with this latest release of procfs version, but the main branch was up to date.&lt;/p&gt;

&lt;p&gt;So thanks to Rust and Cargo, it's effortless to point to git commit. We updated the Cargo.toml file like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[dependencies]&lt;/span&gt;
&lt;span class="nn"&gt;prometheus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;git&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"https://github.com/tikv/rust-prometheus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;rev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ac86a264223c8d918a43e739ca3c48bb4aaedb90"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;features&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;["process"]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And finally, we were able to quickly release and deploy a newer Engine version with those fixes.&lt;/p&gt;

&lt;h1&gt;
  
  
  2nd issue: AWS Elasticache and Terraform
&lt;/h1&gt;

&lt;p&gt;This one was a pain for a lot of companies. AWS decided to change the version format of their Elasticache service. Before version 6, SemVer was present:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5.0.0&lt;/li&gt;
&lt;li&gt;4.0.10&lt;/li&gt;
&lt;li&gt;3.2.10&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When version 6 was released, they decided to use 6.x version format! (&lt;a href="https://web.archive.org/web/20210819001915/https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/SelectEngine.html"&gt;https://web.archive.org/web/20210819001915/https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/SelectEngine.html&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;As you can imagine comparing 2 versions with 2 different types (int vs. string), would fail on a lot of software like Terraform. You can easily find all related issues due to this change on the "Terraform AWS provider" GitHub issue page: &lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues?q=elasticache+6.x"&gt;https://github.com/hashicorp/terraform-provider-aws/issues?q=elasticache+6.x&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this time, it was a headache for people using Terraform (like Qovery) who wanted to upgrade from a previous version and "Terraform AWS provider" developers. AWS understood their mistake as they recently removed 6.x and switched back to SemVer format (&lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/SelectEngine.html"&gt;https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/SelectEngine.html&lt;/a&gt;). And, so...here we go again on the "Terraform AWS provider" on GitHub: &lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/22385"&gt;https://github.com/hashicorp/terraform-provider-aws/issues/22385&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;Big corporations like AWS or small libraries maintained by a few developers can easily have a considerable impact when SemVer rules are not strictly respected with the correct type. Versioning your applications is not "cosmetic" at all, as you can see.&lt;/p&gt;

&lt;p&gt;Following some rules and good practices is essential if we make reliable software in our tech industry. SemVer is definitively one of them!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>terraform</category>
      <category>rust</category>
      <category>aws</category>
    </item>
    <item>
      <title>Deploy JHipster with PostgreSQL in less than a minute</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Tue, 19 May 2020 17:56:36 +0000</pubDate>
      <link>https://dev.to/qovery/deploy-jhipster-with-postgresql-in-less-than-a-minute-4oha</link>
      <guid>https://dev.to/qovery/deploy-jhipster-with-postgresql-in-less-than-a-minute-4oha</guid>
      <description>&lt;p&gt;JHipster is one of the most popular Java frameworks. Build on top of Spring Boot, it is very convenient to bootstrap a web project. And now it's possible to deploy it in production in less than a minute. &lt;/p&gt;

&lt;p&gt;ps: I can't copy/paste the generated markdown due to custom components used&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.qovery.com/guides/tutorial/deploy-jhipster-with-postgresql/"&gt;Read more&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jhipster</category>
    </item>
    <item>
      <title>A month after switching from Slack to Discord in a remote-first tech startup</title>
      <dc:creator>Patryk Jeziorowski</dc:creator>
      <pubDate>Thu, 14 May 2020 18:02:36 +0000</pubDate>
      <link>https://dev.to/qovery/a-month-after-switching-from-slack-to-discord-in-a-remote-first-tech-startup-3f1l</link>
      <guid>https://dev.to/qovery/a-month-after-switching-from-slack-to-discord-in-a-remote-first-tech-startup-3f1l</guid>
      <description>&lt;p&gt;Nowadays, Slack is often the default choice for an online communication tool in the tech industry. Startups, enterprises, open-source projects - they all use Slack to communicate within their organization but sometimes also with their users and community. Our startup was no different. In this article, I explain what made us look for Slack alternative and what are our impressions after a month of switching to Discord, so you can learn from those before you make your decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  The background
&lt;/h2&gt;

&lt;p&gt;Qovery is a remote-first startup founded at the end of the last year. I was hired as the first full-time employee. We are all distributed and work remotely. All our communication happens online. Meetings, plannings, brainstorming sessions, talks with founders, technical discussions, and all the rest - it all happens online without exception.&lt;/p&gt;

&lt;p&gt;Our product is focused on solving the developers' problems. We help them in deploying their applications on the cloud. We work with all types of programmers - passionate hobbyists, innovative startup guys, serious enterprise architects but also programming newbies. This is why we work with many different kinds of applications and need to be there to answer various community questions.&lt;/p&gt;

&lt;p&gt;This all makes it crucial for us to have a tool that allows communicating flawlessly with our community but also internally within our startup, as all our communication happens online.&lt;/p&gt;

&lt;h2&gt;
  
  
  What made us switch
&lt;/h2&gt;

&lt;p&gt;Slack is not a bad tool. In fact, there's a reason why it's so popular. But, like all tools, it's not perfect.&lt;/p&gt;

&lt;p&gt;What we didn't like was primarily the message history limit. On Slack, you have to pay per user. If you want to preserve the chat history for a growing number of community users, this can get pretty expensive quickly. In this regard, it doesn't really support our needs. Don't get me wrong - we are willing to pay for the tools we use, but Slack pricing model is not well suited for a community communication tool.&lt;/p&gt;

&lt;p&gt;We talk (voice) a lot. We think it's important to communicate effectively using our tools, especially when that's the only channel of our verbal communication. Slack was not doing its job well in this regard. Sound, connection issues, and noticeable delay of sound despite decent internet connection made the experience at least not perfect. It's not only Slack issue - we tried many other tools, too, and they all often face similar problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution?
&lt;/h2&gt;

&lt;p&gt;Discord? It's a tool for online gamers, isn't it? Maybe, but... "On paper" it looked like it could fix all the problems we had with Slack. No history limit, focus on great voice communication... We decided to ignore the "it's a tool for gamers only' stereotype and took a bold move to give it a try in our organization. What's the result?&lt;/p&gt;

&lt;h3&gt;
  
  
  What was expected to be better is in fact better
&lt;/h3&gt;

&lt;p&gt;In practice, all promises Discord gave us, in real day-to-day usage, turned out to be true: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pricing model - we no longer have to pay per user to keep our chat history. You could use it, in fact, for free. We pay less than 80$ per month now for even better audio quality and HD video calls. The price won't change with our growing number of community users.&lt;/li&gt;
&lt;li&gt;Voice / Video chats. This is where Discord shines. The audio quality is excellent, screen sharing works flawlessly, the latency between you speaking and your listeners hearing it is much lower than on Slack and other solutions. Also, what I really like is you do not have to call somebody directly or prepare a group calls - you can just enter a voice channel (Teamspeak and Ventrilo nostalgic memories...).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What was expected to be worse, is in fact worse
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Integrations - we were aware that as a mainly gamers' tool, Discord lacks some of the integrations we used on Slack. Slack has a huge advantage in this space. However, thanks to webhooks combined with Zapier, we were able to configure all integrations we had before also Discord.&lt;/li&gt;
&lt;li&gt;Threaded messages - we were aware of the fact it's missing in Discord, and in fact, it's painful. I do miss it. I wish it was there.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What surprised positively
&lt;/h2&gt;

&lt;p&gt;What surprised me from the first second I launched Discord app was the smoothness and responsiveness of the app. It runs truly, noticeably, SMOOTHER and FASTER than Slack. Using Slack in comparison to Discord feels like being in a slow mode for me now. I completely didn't expect this (in fact I was not aware that Slack was so clunky). It's simply a pleasure to use Discord.&lt;/p&gt;

&lt;p&gt;Another thing that surprised me is that Discord seems to grow really fast. In just a month of our usage, the added noise suppression, which eliminates random noises from your background and an option to turn on the webcam during talks on group channels.&lt;/p&gt;

&lt;h2&gt;
  
  
  What surprised negatively
&lt;/h2&gt;

&lt;p&gt;What surprised me negatively the most is a very inconvenient upload of very long texts (e.g. application logs). If your copy-paste a very long text, it won't be displayed in the chat in any way - it appears as a text file to download instead.&lt;/p&gt;

&lt;p&gt;Another thing - file upload size limits are a bit restrictive. There are no limits for the workspace like on Slack, but for a single file upload, the limits are pretty harsh (8MB / 50MB/ 100MB depending on your plan). This means you'll have to share your files elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  My final thoughts after one month of switching to Discord
&lt;/h2&gt;

&lt;p&gt;I'm selfish. I wish Discord would switch focus from gamers to professionals. It would be then the perfect tool for any IT organization. But for now, these are just my dreams - the reality is that Discord is much better for voice conversations. The app works much smoother and feels more responsive &amp;amp; modern. Chat is worse due to the lack of threaded messages.&lt;/p&gt;

&lt;p&gt;In my opinion, you should use Discord if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you value quality voice conversations&lt;/li&gt;
&lt;li&gt;you communicate a lot with your users/community&lt;/li&gt;
&lt;li&gt;you don't mind using gamers focused tool (the UI is less suited for enterprise usage than Slack)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think Discord may gradually turn into a more business-oriented tool. They add features that are useful for IT teams. There's yet a bit to be done, but I hope it will naturally go in this direction. There are a lot of &lt;a href="https://discord.com/open-source"&gt;open-source communities using Discord&lt;/a&gt; to build their communities at the moment (including Rust, Django, Hasura, Typescript, and many others) and I believe Discord's popularity will grow in our industry.&lt;/p&gt;

&lt;p&gt;And, it the end, &lt;a href="https://blog.discord.com/why-discord-is-switching-from-go-to-rust-a190bbca2b1f"&gt;they use Rust&lt;/a&gt; so no wonder why Discord works so smooth! :)&lt;/p&gt;

&lt;p&gt;P.S. If you want to try Discord or ask me or our team any question, feel free to join &lt;a href="https://discord.qovery.com"&gt;our Discord server&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>career</category>
      <category>startup</category>
    </item>
    <item>
      <title>Productive and simple way to deploy Dockerized applications </title>
      <dc:creator>Patryk Jeziorowski</dc:creator>
      <pubDate>Wed, 06 May 2020 18:51:22 +0000</pubDate>
      <link>https://dev.to/qovery/productive-and-simple-way-to-deploy-dockerized-applications-56f4</link>
      <guid>https://dev.to/qovery/productive-and-simple-way-to-deploy-dockerized-applications-56f4</guid>
      <description>&lt;p&gt;In recent years, Docker has been becoming more and more popular tool used to deploy web applications. &lt;a href="https://www.datadoghq.com/docker-adoption/" rel="noopener noreferrer"&gt;According to Datadog&lt;/a&gt; in 2018, the adoption of Docker in large organizations reached about 47 percent and almost 20 percent in small organizations. This report it two years old - no doubt Docker is even more common now.&lt;/p&gt;

&lt;p&gt;In my opinion, knowing Docker basics is an essential tool in the toolbox of every software engineer, especially in the web development ecosystem. In this article, I'll demonstrate the easiest way to Dockerize and deploy a simple application. Before we dive deep into practical steps, let's first answer two essential questions - "What is Docker" and "Why should I use it" in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docker in a nutshell
&lt;/h2&gt;

&lt;p&gt;Docker is a tool that makes it easy to build and deploy your applications, typically to the cloud environment. It allows you to package your application in a container that contains your app with all of the things it needs, such as libraries and other dependencies. Then, this package can be run on any machine with a Docker engine installed, no matter the underlying configuration or system distribution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why should I use Docker?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;It works on my machine&lt;/code&gt; sentence has become a meme in the software world. You can even get a sticker on your laptop:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz2jt18liia6u0k52qb0u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fz2jt18liia6u0k52qb0u.jpg" alt="It works on my machine"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Making applications run consistently in various environments is one of the issues addressed very well by Docker.&lt;/p&gt;

&lt;p&gt;Docker makes sure that your containerized applications run in the same way on your machine, on your friend's machine, and on the AWS server (and anywhere else where Docker engine is installed). It is truly a superpower. As a developer, you no longer need to worry about the underlying system. After you Dockerize your app, you can be sure that it behaves in the same manner in your development, testing, and production environments, as well as on your local machine. It makes building and testing applications way more comfortable than it was before.&lt;/p&gt;

&lt;p&gt;Another reason why you should be interested in Docker is the popularization of cloud, microservices, and Kubernetes. Docker is the first-class citizen in the cloud-native world, so if you want to take the full advantage of scalable, cloud-native application architectures, Docker is the way to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to deploy Docker containers
&lt;/h2&gt;

&lt;p&gt;Let's move on to the practical application and usage of Docker. We'll now build a very simple web application that responds to HTTP requests, dockerize it and deploy to &lt;a href="https://docs.qovery.com/docs/getting-started/what-is-qovery/" rel="noopener noreferrer"&gt;Qovery - a scalable Container as a Service platform&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a simple application
&lt;/h3&gt;

&lt;p&gt;For the sake of simplicity, we'll create a simple Node.js application that returns a &lt;code&gt;Hello, World&lt;/code&gt; text in response to HTTP requests. I choose Node.js here because it's simple and popular technology, but you can use Docker with basically any language and framework.&lt;/p&gt;

&lt;p&gt;Let's create an empty folder for our new application and initialize an empty Git repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;deploying-docker
&lt;span class="nb"&gt;cd &lt;/span&gt;deploying-docker
git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, create &lt;code&gt;app.js&lt;/code&gt; file with the source code of our server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createServer&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;statusCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;text/plain&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server running at http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;hostname&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a very simple server that returns "Hello World" text on its root endpoint. After it's done, we want to make this app run in a Docker container. To do so, we need to create a Dockerfile.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is Dockerfile?
&lt;/h3&gt;

&lt;p&gt;Besides containers, Docker uses the concept of &lt;strong&gt;Images&lt;/strong&gt;. Image is a template used to create and run containers. Dockerfile describes the steps required to build the image. Later on, this image is used as a template to run containers with your application.&lt;/p&gt;

&lt;p&gt;You can think about images and containers as a good analogy to classes and objects (instances of a given class) in the Object-Oriented Programming world.&lt;/p&gt;

&lt;p&gt;Create a Dockerfile that will allow us to run our Node.js app in a container. Create a file named Dockerfile with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:13-alpine&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /usr/src/app

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /usr/src/app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 3000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; node app.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's discuss all lines of the Dockerfile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM node:13-alpine&lt;/code&gt; specifies the base of our Docker image. It's a base used to get started with building an image.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN mkdir -p /usr/src/app&lt;/code&gt; creates a new empty folder in &lt;code&gt;/usr/src/app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;WORKDIR /usr/src/app&lt;/code&gt; defines the &lt;em&gt;working directory&lt;/em&gt; of our container&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY . .&lt;/code&gt; adds the contents of our application to the container&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;EXPOSE 3000&lt;/code&gt; informs Docker that the container listens on the specified network port at runtime &lt;/li&gt;
&lt;li&gt;and, finally: &lt;code&gt;CMD node app.js&lt;/code&gt; is the command that starts our application.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we got all basic things we need to run our application in a Docker container! Let's try it out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Build Docker image of the app using &lt;code&gt;docker build testing/docker .&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run a container with our application by executing &lt;code&gt;docker run -p 3000:3000 testing/docker&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;the &lt;code&gt;-p 3000:3000&lt;/code&gt; flag makes container port &lt;code&gt;3000&lt;/code&gt; accessible on your &lt;code&gt;localhost:3000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Great&lt;/strong&gt;! The container is up. Run &lt;code&gt;docker ps&lt;/code&gt; to see the list of running containers and confirm that it is indeed running.&lt;/p&gt;

&lt;p&gt;Now open a browser at &lt;code&gt;http://localhost:3000&lt;/code&gt; to see that the application in a container responded with &lt;code&gt;Hello, World&lt;/code&gt; message.&lt;/p&gt;

&lt;p&gt;Did it work? Great. Our app works well in the Docker container. It's adorable, but we want to share our app with the world - running applications only on our own machine won't make us millionaires!&lt;/p&gt;

&lt;h3&gt;
  
  
  Container as a Service
&lt;/h3&gt;

&lt;p&gt;To deploy our Dockerized application, we'll use Qovery. It's a Container as a Service platform that allows us to deploy Dockerized apps without any efforts. Qovery is free up to three applications (and databases!) in the community version.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install Qovery CLI
&lt;/h3&gt;

&lt;p&gt;To Sign Up and install the CLI, you can follow the steps described in &lt;a href="https://docs.qovery.com/docs/using-qovery/interface/cli/#sign-up" rel="noopener noreferrer"&gt;this link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After you have access to Qovery, it's time to deploy the application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy the docker container
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;qovery init&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Choose application name, e.g., &lt;code&gt;node-app&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Choose project name, e.g., &lt;code&gt;testing-docker&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Commit and push your changes to Github: &lt;code&gt;git add . ; git commit -m "Initial commit" ; git push -u origin master"&lt;/code&gt; (create an empty repository beforefor your application on Github before if it's not done yet)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Voila! That's all. Your Dockerized application is being deployed as a Docker container. To deploy a Docker container on Qovery, all you need is a Dockerfile that describes containers with your application + running &lt;code&gt;qovery init&lt;/code&gt; command to initialize Qovery. From now on, Qovery will build and deploy your Dockerized application after you make any changes in your repository to scalable Kubernetes clusters as a Docker container.&lt;/p&gt;

&lt;p&gt;To check that your application is in fact deploying, run &lt;code&gt;qovery status&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;BRANCH NAME       | STATUS  | ENDPOINTS                            | APPLICATIONS    | DATABASES
master            | running | https://some.url.qovery.io           | node-app  | 

APPLICATION NAME  | STATUS  | DATABASES
node-app          | running | 

DATABASE NAME     | STATUS  | TYPE       | VERSION | ENDPOINT | PORT     | USERNAME | PASSWORD | APPLICATIONS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this guide, you learned the essential basics of Docker. You also learned why you should be interested in using it, and how to deploy your application to the cloud as a Docker container. This is all you need to know to improve your development experience and deploy your application to the cloud with ease! If you have any questions or feedback please do let me know in the comments or join &lt;a href="https://discord.qovery.com" rel="noopener noreferrer"&gt;Qovery Discord server&lt;/a&gt; and feel free to speak your mind.&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Heroku vs Qovery</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Wed, 15 Apr 2020 08:46:30 +0000</pubDate>
      <link>https://dev.to/qovery/heroku-vs-qovery-1d5a</link>
      <guid>https://dev.to/qovery/heroku-vs-qovery-1d5a</guid>
      <description>&lt;p&gt;The first question we always get from developers before trying out our product is - “&lt;strong&gt;What’s the difference between your platform and Heroku?&lt;/strong&gt;”. We think it’s very important to answer your questions clearly and this article is meant to be our answer.‍&lt;/p&gt;

&lt;h1&gt;
  
  
  At Qovery, applications, databases, storage, and brokers are first-class citizens
&lt;/h1&gt;

&lt;p&gt;Heroku provides three managed services to all customers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Heroku Postgres&lt;/li&gt;
&lt;li&gt;Heroku Redis&lt;/li&gt;
&lt;li&gt;Apache Kafka on Heroku&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All other integrations are made as third-party, paid add-ons. &lt;strong&gt;They’re not treated on pair with your applications&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At Qovery, we have taken a different approach. &lt;strong&gt;Applications, storage, databases, and message brokers are treated as first-class citizens&lt;/strong&gt;. We treat them all in a uniform way. They have the same pricing and limits of storage, CPU or memory no matter if it's your backend application, MongoDB, PostgreSQL or RabbitMQ. Their configuration is also simplified - connecting your backend to the database is very declarative. **You declare what you need, and we make it available to your application¨¨. This speeds up the development and simplifies the management of services in production a lot.&lt;/p&gt;

&lt;p&gt;The list of officially supported services by Heroku is very limited. &lt;strong&gt;Our goal at Qovery is to make any service offered by cloud providers effortlessly accessible in your application&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nwnfPau1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e9577adb13e2d1db1f355cd_tWGR2W5smBgWhewQp0a1j62R3ZopTgaSo_32u6_wOGtbI-GPNrVFo-85pePKtR9s8vAHtCJVRUWZGpCUgIBHl4zBmEnQ6LGE4YYBIqocf7XNMk7ErQgbdsspyRAXW2dzQ5kKwjrH.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nwnfPau1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e9577adb13e2d1db1f355cd_tWGR2W5smBgWhewQp0a1j62R3ZopTgaSo_32u6_wOGtbI-GPNrVFo-85pePKtR9s8vAHtCJVRUWZGpCUgIBHl4zBmEnQ6LGE4YYBIqocf7XNMk7ErQgbdsspyRAXW2dzQ5kKwjrH.png" alt="All services provided by Qovery"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s an example of how easy it is to add PostgreSQL to your application - just add those few lines of configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;databases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;11.5"&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-postgresql-db&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s all - you'll get a managed PostgreSQL to use in your app.&lt;/p&gt;

&lt;h1&gt;
  
  
  Qovery is built for teams
&lt;/h1&gt;

&lt;p&gt;One of &lt;strong&gt;the superpowers&lt;/strong&gt; of Qovery is the concept of &lt;strong&gt;isolated environments&lt;/strong&gt;. Have you ever had a situation when you could not test your changes in a deployed environment because it would block your teammate from testing his own feature? This is a common problem of sharing testing and staging environments. Qovery is a remedy for this issue. &lt;strong&gt;With Qovery, you have the ability to have an isolated environment for each of your developers. This will greatly speed up the development cycle and the productivity of your team.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C-ra8Yww--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e95785219faa716275aa4d1_LqW-Z8aHFfyca7issv1rAA4nEDKb2RlITxzUGEtWWuZ5dHXgS43e73cIljwanDdRfS3S86vm7VoNM7YVimiQXZSi7ur3lX-qbnMFY7dsMjon2pVwe7NFTZlOVFEuTLnFCHlO7AUg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C-ra8Yww--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e95785219faa716275aa4d1_LqW-Z8aHFfyca7issv1rAA4nEDKb2RlITxzUGEtWWuZ5dHXgS43e73cIljwanDdRfS3S86vm7VoNM7YVimiQXZSi7ur3lX-qbnMFY7dsMjon2pVwe7NFTZlOVFEuTLnFCHlO7AUg.png" alt="Each branch has its environment"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Creating a new remote branch results in a clean, remote environment, where you can freely test your code. These are not empty words - &lt;strong&gt;you get a fully functional copy of your environment. This includes all data, database replication and even traffic mirroring between environments&lt;/strong&gt;. This way, you’ll be independent, save your precious time, avoid frustration and introduce fewer bugs to your project. &lt;strong&gt;Having a chance to test new code on a production-like environment before going to production is invaluable&lt;/strong&gt;.&lt;br&gt;
‍&lt;br&gt;
All of this happens automatically, without you having to configure anything at all. Every application, database or broker has replicated automagically. &lt;strong&gt;This is the superpower brought to you by Qovery, that other platforms do not provide.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Docker, Dynos &amp;amp; language support
&lt;/h1&gt;

&lt;p&gt;Another important difference is the runtime of applications and language support. On Heroku, &lt;strong&gt;the number of supported languages and frameworks is limited&lt;/strong&gt;. You can use languages officially supported by Heroku in containers called Dynos. If you need to use something else, you can provide a Docker image. However, the support for Docker containers on Heroku is somewhat troublesome. Using Docker on Heroku requires a lot more effort and configuration than on Qovery. Besides this, Docker containers on Heroku are affected by the same limitations as Dynos, as in fact they're run in Dynos. &lt;/p&gt;

&lt;p&gt;At Qovery, we have taken a different, opinionated approach of running your applications. &lt;strong&gt;Docker integration with Qovery is effortless&lt;/strong&gt; - all you have to do is to create a Dockerfile in your repository. &lt;strong&gt;Qovery takes care of the rest - building your Docker image, deploying it and integrating with your Git workflow.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xuBjIRds--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e958d86b13e2d76fefeecf1_DCKR-QOV.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xuBjIRds--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e958d86b13e2d76fefeecf1_DCKR-QOV.png" alt="Qovery with Docker"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This way, we support all languages and frameworks you can imagine &lt;strong&gt;with minimal effort for you as a developer&lt;/strong&gt;. All your applications are built and deployed in the same way, no matter what technology or language you use.&lt;/p&gt;

&lt;h1&gt;
  
  
  Different approach to open source and side projects
&lt;/h1&gt;

&lt;p&gt;At Qovery, we highly value passionate developers and open-source. &lt;strong&gt;We provide a generous free tier, in which you can deploy up to three applications (e.g. frontend application, backend application, and a database) and set it up with your custom domain.&lt;/strong&gt;&lt;br&gt;
‍&lt;br&gt;
Unlike Heroku, &lt;strong&gt;we never turn your applications off&lt;/strong&gt;, even on a free tier! If you’re an author of an open-source project, please contact us - we are glad to give you &lt;strong&gt;up to 100% discounts for running your open source projects.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discord.qovery.com/"&gt;Join the community now&lt;/a&gt;&lt;/p&gt;

</description>
      <category>docker</category>
      <category>productivity</category>
    </item>
    <item>
      <title>6 benefits of Kotlin for building server-side applications</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Thu, 09 Apr 2020 18:52:15 +0000</pubDate>
      <link>https://dev.to/qovery/6-benefits-of-kotlin-for-building-server-side-applications-ean</link>
      <guid>https://dev.to/qovery/6-benefits-of-kotlin-for-building-server-side-applications-ean</guid>
      <description>&lt;p&gt;&lt;a href="https://kotlinlang.org/" rel="noopener noreferrer"&gt;Kotlin&lt;/a&gt; is a programming language over the JVM (like Java). It is well known for being the official programming language for Android. But Kotlin is a reliable and powerful programming language that can also be used for server-side applications.&lt;/p&gt;

&lt;p&gt;I have been using Kotlin for 4 years to develop server-side applications. Here are the 6 reasons why it is a great choice for building your next backend:&lt;/p&gt;

&lt;h1&gt;
  
  
  Java Ecosystem
&lt;/h1&gt;

&lt;p&gt;Java is one of the vastest ecosystems in the programming world. Kotlin is 100% interoperable with Java, which allows it to benefit from all its libraries/frameworks without having to recreate new ones (&lt;a href="https://www.scala-lang.org/old/faq/4.html" rel="noopener noreferrer"&gt;which was difficult with Scala&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Interoperability is what made its great success on Android, the interface with existing APIs is painless. It is the same observation on the backend side. Would you like to use a framework like Spring Boot in Kotlin? Not a problem, it's even officially supported.&lt;/p&gt;

&lt;p&gt;Then, Kotlin starts with the advantages of thousands of existing Java libraries, frameworks and potential dev adopters.&lt;/p&gt;

&lt;h1&gt;
  
  
  Concise
&lt;/h1&gt;

&lt;p&gt;There's no need to argue the point, it makes sense. You just have to see the declaration of a class in Kotlin vs Java&lt;/p&gt;

&lt;p&gt;Kotlin example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Java example&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;User&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getFirstName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setFirstName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getLastName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setLastName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getClass&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;hashCode&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;firstName&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"User{"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;"firstName='"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sc"&gt;'\''&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s"&gt;", lastName='"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;lastName&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sc"&gt;'\''&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="sc"&gt;'}'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In these two examples, the bytecode will be the same. Except that in Kotlin the compiler does much more than in Java. This improves code maintainability and readability, meaning engineers can write, read, and change code more effectively and efficiently. Features such as type inference, smart casts, data classes, and properties help achieve conciseness.&lt;/p&gt;

&lt;p&gt;Note: Java 14 includes the concept of Record - Which is more or less the concept of data class in Kotlin. Let's keep an eye out to see how it evolves over the next few years.&lt;/p&gt;

&lt;h1&gt;
  
  
  Null Safety
&lt;/h1&gt;

&lt;p&gt;Kotlin brings the "optional" concept used in functional languages with the "?" operator. It prevents crashes when a value can be potentially null and therefore takes into account the case where this would be the case.&lt;/p&gt;

&lt;p&gt;When designing a RESTful API we can receive null data, if the case has not been handled, then it is a possible crash. These errors are greatly reduced with Kotlin. Goodbye NullPointerException (&lt;a href="https://en.wikipedia.org/wiki/Tony_Hoare#Apologies_and_retractions" rel="noopener noreferrer"&gt;the billion dollar mistake&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://kotlinlang.org/docs/reference/null-safety.html" rel="noopener noreferrer"&gt;See the null safety page&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Data Transformation
&lt;/h1&gt;

&lt;p&gt;The functional programming concepts are present natively in Kotlin. This makes it possible to chain actions intuitively. Do you need to transform a list of X objects into Y objects? It' s as simple as...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;listOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;X&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;X&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toY&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sometimes I can chain up to 10 operations without having to create a single intermediate variable. When you get a taste for it, it's tough to live without it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Reliability and performance
&lt;/h1&gt;

&lt;p&gt;Kotlin is Open Source and was initially developed by Jetbrains (the company behind the IntelliJ IDE). Its success is due to its &lt;a href="https://medium.com/rsq-technologies/comparative-evaluation-of-selected-constructs-in-java-and-kotlin-part-1-dynamic-metrics-2592820ce80" rel="noopener noreferrer"&gt;performance and reliability&lt;/a&gt; close to what you would have in Java. The final code is also ByteCode and contrary to Groovy which had a very bad reputation for performance, here no magic, no "dynamic invoke" in all directions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Growing community
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e88cf524fbaea56881d2e8f_IMG_20191205_090708-min-1024x467.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e88cf524fbaea56881d2e8f_IMG_20191205_090708-min-1024x467.jpg" alt="Kotlin conf'19"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The community around Kotlin keeps growing month over month. Companies such as &lt;a href="https://kodein.net/" rel="noopener noreferrer"&gt;Kodein Koders&lt;/a&gt; and &lt;a href="https://jetbrains.com/" rel="noopener noreferrer"&gt;Jetbrains&lt;/a&gt; are doing their best to animate it and make Kotlin one of the top 5 programming languages used by developers in the coming years.&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>backend</category>
    </item>
    <item>
      <title>How to build and deploy realtime GraphQL APIs in a few minutes</title>
      <dc:creator>Patryk Jeziorowski</dc:creator>
      <pubDate>Thu, 09 Apr 2020 09:18:50 +0000</pubDate>
      <link>https://dev.to/qovery/how-to-build-and-deploy-realtime-graphql-apis-in-a-few-minutes-5bap</link>
      <guid>https://dev.to/qovery/how-to-build-and-deploy-realtime-graphql-apis-in-a-few-minutes-5bap</guid>
      <description>&lt;p&gt;Before we dive deep into details, I'll &lt;strong&gt;quickly describe the tools I used&lt;/strong&gt; to build and deploy a realtime GraphQL API and tell you &lt;strong&gt;why you should fall in love with GraphQL&lt;/strong&gt; and all the tools I used.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, why to use GraphQL?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GraphQL&lt;/strong&gt; is a query language for APIs and a runtime for fulfilling those queries with existing data. GraphQL provides a &lt;strong&gt;schema&lt;/strong&gt; that describes the API and allows clients (e.g. your frontend or mobile application) to easily fetch data they want and nothing more.&lt;/p&gt;

&lt;p&gt;Here is what you get from using GraphQL instead of standard RESTful APIs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GraphQL queries get exactly what you need, nothing more and nothing less&lt;/li&gt;
&lt;li&gt;Instead of making multiple requests to fetch required data, you make just one request to one endpoint&lt;/li&gt;
&lt;li&gt;GraphQL schema is typed, what makes the contract between frontend and backend clear and understandable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you are a frontend engineer, &lt;strong&gt;you will not like to consume other APIs than GraphQL after trying it out&lt;/strong&gt;. It makes your life so much more pleasurable and easy.&lt;/p&gt;

&lt;p&gt;You don't need to know GraphQL to follow this article. All you need to know is that GraphQL allows you to define contract between frontend and backend and do operations on the data you are interested in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Productivity boosting tools
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Hasura&lt;/strong&gt; is an open source engine that connects to your databases &amp;amp; microservices and auto-generates a production-ready GraphQL backend. By using Hasura in conjunction with &lt;strong&gt;Qovery&lt;/strong&gt; (platform that combines the power of Kubernetes, the reliability of AWS and the simplicity of Heroku to allow developers deploy their apps with pleasure), you get a &lt;strong&gt;blazing fast, auto-scallable and extensible solution to quickly build your applications&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Hasura?
&lt;/h3&gt;

&lt;p&gt;Consuming GraphQL APIs is a pleasure. We'd like to have more GraphQL APIs. But those APIs do not come out of nowhere. Somebody has to implement them first - the data won't just flow out of the database through the schema to your frontend automagically, right? Well... with Hasura it will!&lt;/p&gt;

&lt;p&gt;Hasura allows you to bootstrap a realtime GraphQL API in seconds by simply modeling your data. Hasura will do the hard work of translating your needs into queries to the database and translating them to GraphQL schema. Thanks to this, all you need to do is to define the data you want to store in the database - Hasura will do the rest.&lt;/p&gt;

&lt;p&gt;This is unbelievable how much time it saves. If you don't believe, try implementing a GraphQL server yourself - with all the featuers and options that Hasura offers.&lt;/p&gt;

&lt;p&gt;If you have doubts about flexibility - you don't have to worry. If you need to perform a very custom business logic, you can implement this part in any language you want and connect it to Hasura engine. This way you will not only save a lot of time, but also have flexibility to write your custom code if needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Qovery?
&lt;/h3&gt;

&lt;p&gt;Managing infrastructure is hard and takes time. &lt;strong&gt;Developers want to focus on building their apps instead of wasting time on managing servers or databases&lt;/strong&gt;. Qovery is tool that does it all for you - all you have to do is to write your application code. It's *&lt;em&gt;powered by Docker and Kubernetes *&lt;/em&gt; underneath, so you get all the benefits of using those modern tools without the complexity and costs of learning and managing them.&lt;/p&gt;

&lt;p&gt;Qovery is also a great fit for Hasura - its &lt;strong&gt;free plan allows you to deploy Hasura and database for free, without any limits, performance degradations or putting your app to sleep&lt;/strong&gt; like it's done on other platforms.&lt;/p&gt;

&lt;p&gt;If you have any questions regarding this post or other things, feel free to reach me on &lt;a href="https://discord.qovery.com" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hasura deployment on Qovery
&lt;/h2&gt;

&lt;p&gt;Deploying Hasura on Qovery is really easy. All you have to do is to bootstrap a project using Qovery CLI in a Git repository &amp;amp; export environment variables required by Hasura.&lt;/p&gt;

&lt;p&gt;1/ Bootstrap a project with Qovery CLI (the script will ask you for project and application name, which you can choose as you like)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qovery template hasura
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2/ Point Hasura to your Postgres database and enable Hasura Console using environment variables&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;qovery application &lt;span class="nb"&gt;env &lt;/span&gt;add HASURA_GRAPHQL_DATABASE_URL &lt;span class="s1"&gt;'$QOVERY_DATABASE_MY_POSTGRESQL_DATABASE_CONNECTION_URI'&lt;/span&gt;
qovery application &lt;span class="nb"&gt;env &lt;/span&gt;add HASURA_GRAPHQL_ENABLE_CONSOLE &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3/ Commit and push your changes&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git add .
git commit -m "Deploy Hasura on Qovery"
git push -u origin master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Well done!&lt;/strong&gt; After pushing changes, Postgres and Hasura deployment will start. You can use &lt;code&gt;‍qovery status --watch&lt;/code&gt; to track the progress. Once the deployment is done, you’ll see your Hasura application URL in the status:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9e3c75a7e46396d483d9_status.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9e3c75a7e46396d483d9_status.png" alt="Qovery Status"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Creating realtime GraphQL APIs
&lt;/h2&gt;

&lt;p&gt;Navigate to your Hasura application URL and choose Data tab in the console:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9e5e4788f066d395e4ac_data-tab.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9e5e4788f066d395e4ac_data-tab.png" alt="Hasura Console"&gt;&lt;/a&gt;&lt;br&gt;
In this section we'll configure our data model. Now, click on the &lt;strong&gt;Create Table&lt;/strong&gt; button.&lt;/p&gt;

&lt;p&gt;You’ll see the table creator. We are going to create a simple "Todo" items table. We'll name it "todos" and the table will contain three columns:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. id - unique identifier of given "Todo" item
2. title
3. description - optional description of "Todo" item
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Fill the form as in the screenshots below to prepare the table.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9eccfd0cf4040e3017ff_table-name.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9eccfd0cf4040e3017ff_table-name.png" alt="Table"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9edd3f6105fd73982190_table-columns.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9edd3f6105fd73982190_table-columns.png" alt="Table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At the end, we should specify our id column as a Primary Key.‍&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9ef4d87c6c421133c2e7_primary-key.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9ef4d87c6c421133c2e7_primary-key.png" alt="Primary Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The table is ready to be created. Click &lt;strong&gt;Add Table&lt;/strong&gt; button at the bottom of the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Voila!&lt;/strong&gt; The table has been created in Postgres and Hasura has exposed GraphQL APIs to interact with our data.&lt;/p&gt;
&lt;h2&gt;
  
  
  Testing GraphQL APIs
&lt;/h2&gt;

&lt;p&gt;To test the GraphQL API, navigate to the GraphiQL tab and run the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8da0edcf123c19f5918594_graphiql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8da0edcf123c19f5918594_graphiql.png" alt="GraphiQL"&gt;&lt;/a&gt;&lt;br&gt;
As you can see, Hasura returned an empty array of "Todo" items. Let’s add a "Todo" item by executing the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;insert_todos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My first TODO"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"It's very important TODO item"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;affected_rows&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you run the above query, in the response you'll see information about one affected row. Congrats! You have created a first "Todo" item. Let's now move further to a more interesting topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL realtime APIs
&lt;/h2&gt;

&lt;p&gt;It's time to use a realtime GraphQL APIs - &lt;strong&gt;GraphQL Subscriptions&lt;/strong&gt;. Subscription allows you to fetch data and get updates about any changes that occur in data you are interested in.&lt;/p&gt;

&lt;p&gt;In the GraphiQL, run the following query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;subscription&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;todos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the response in the right-hand of console you'll see a "Todo" item you have created previously. That's great. Let's now test if the subscription really works - open one more Hasura console in a separate browser tab and navigate to GraphiQL.&lt;/p&gt;

&lt;p&gt;Execute the following query a few times:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;insert_todos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Another TODO to test subscriptions"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Subscriptions!"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;affected_rows&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At the same time, keep an eye at the subscription. Each and every newly created "Todo" item automagically appears in the subscription response!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9de17e6837d53983c32f_subscription.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuploads-ssl.webflow.com%2F5de176c0d41c9b4a1dbbb0aa%2F5e8d9de17e6837d53983c32f_subscription.gif" alt="Realtime GraphQL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;By following this article you quickly deployed a realtime GraphQL backend using Qovery, Hasura and Postgres database.&lt;/p&gt;

&lt;p&gt;Using this stack saves you tons of time. Deploying it on Qovery is extremely easy. We take care of your application and your database. With Qovery and Hasura all you have to do to expose quality, realtime GraphQL backend is just a few clicks. After minutes your application is ready - define your data schema and expose GraphQL API to the world!&lt;/p&gt;

</description>
      <category>graphql</category>
      <category>javascript</category>
      <category>showdev</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to choose the right database?</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Tue, 07 Apr 2020 23:44:13 +0000</pubDate>
      <link>https://dev.to/qovery/how-to-choose-the-right-database-40l</link>
      <guid>https://dev.to/qovery/how-to-choose-the-right-database-40l</guid>
      <description>&lt;p&gt;Hello the community, &lt;/p&gt;

&lt;p&gt;I'm hosting an online event called "&lt;strong&gt;As a developer, how to choose the right database?&lt;/strong&gt;" - From my experience as an SRE and backend developer, I've noticed that choosing a database is not necessarily easy for everyone. With my colleague Pierre (CTO @ Qovery, ex SRE Criteo &amp;amp; Red Hat), we propose a small webinar to give you an overview of what exists in SQL and NoSQL databases. We will cover MongoDB, PostgreSQL, Cassandra, Elasticsearch, Redis and others... If you are interested in participating you can &lt;a href="https://qovery.typeform.com/to/nL4kIs"&gt;register here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>database</category>
      <category>sql</category>
      <category>nosql</category>
    </item>
    <item>
      <title>Free hosting for Open Source projects</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Tue, 31 Mar 2020 10:48:13 +0000</pubDate>
      <link>https://dev.to/qovery/free-hosting-for-open-source-projects-31bf</link>
      <guid>https://dev.to/qovery/free-hosting-for-open-source-projects-31bf</guid>
      <description>&lt;p&gt;&lt;a href="https://www.qovery.com"&gt;Qovery&lt;/a&gt; combines the power of Kubernetes, the reliability of AWS and the simplicity of Heroku to deploy modern application seamlessly!&lt;/p&gt;

&lt;p&gt;I am pleased to announce that Qovery supports individual developers / open source and non profit projects to &lt;strong&gt;host up to 3 applications (database included) for free&lt;/strong&gt;. Our business model is based on Enterprise hosting, which gives us the possibility to offer generous free plans.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.qovery.com"&gt;Start using Qovery now&lt;/a&gt; and &lt;a href="https://discord.qovery.com"&gt;join the community&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>hosting</category>
      <category>aws</category>
      <category>heroku</category>
    </item>
    <item>
      <title>Why we moved from Slack to Discord?</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Wed, 18 Mar 2020 16:08:45 +0000</pubDate>
      <link>https://dev.to/qovery/why-we-moved-from-slack-to-discord-288j</link>
      <guid>https://dev.to/qovery/why-we-moved-from-slack-to-discord-288j</guid>
      <description>&lt;h1&gt;
  
  
  Why we moved from Slack to Discord?
&lt;/h1&gt;

&lt;p&gt;Today, at &lt;a href="https://www.qovery.com"&gt;Qovery&lt;/a&gt; we have moved from &lt;a href="https://www.slack.com"&gt;Slack&lt;/a&gt; to &lt;a href="https://discordapp.com"&gt;Discord&lt;/a&gt;. We are a software company solving developer problems - application deployment. Solving developer problems require to have a close contact with the developers community. For team communication, we used a dedicated Slack workspace, for community communication - another one. Discord has been a real lifesaver for us. Let's start to tell you why..&lt;/p&gt;

&lt;h1&gt;
  
  
  Slack limitations
&lt;/h1&gt;

&lt;p&gt;Slack was created to meet the needs of companies. It's a wonderful communication tool that has successfully found its place in companies at all stages. From R&amp;amp;D to marketing to finance, Slack is omnipresent. However, like all tools, it is not perfect. In teamwork it is perfectible, and in community management it is totally non-existent.&lt;/p&gt;

&lt;p&gt;Here are the 3 limitations that pushed us to look for something else (from the most important to the least-important):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 10,000 message limit - you have to pay per user to avoid losing your message history, it's truly restrictive! You have to pay per user on your Slack to make it disappear. Do the math, for a community of 1000+ developers it's pretty expensive.&lt;/li&gt;
&lt;li&gt;We are a "remote first" company - we need to be able to talk to each other quickly and to be able to share our screen very often, with slack it's a real pain in...&lt;/li&gt;
&lt;li&gt;There is no member hierarchy - It's impossible to know who's in the staff, who's an active member, who's a beta tester. It's hard to know who is who and who does what. How do you give visibility to new members? It's simply not possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After testing several solutions such as Tandem, Zoom, Google Meet, Slack, each solution was able to satisfy one or two points, but never all of them... Until we found Discord.&lt;/p&gt;

&lt;h1&gt;
  
  
  Discord
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Built for online community
&lt;/h2&gt;

&lt;p&gt;Discord is a communication tool that has been created for online gamers. Online gamers have high expectations. Written and voice communication is ubiquitous and must be flawless for them. It is in this context that Discord was made. The funny part is, it is also perfectly suited for team communication in a company. And even more so when this company targets developers, which is essentially a world of enthusiasts. The community can meet and talk on Discord in a very natural way. The experience is intuitive.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_h43oHDN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e7220a6ed23acd570b2dbfc__general_-_Discord.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_h43oHDN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e7220a6ed23acd570b2dbfc__general_-_Discord.jpg" alt="Qovery Discord server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Advantages over Slack
&lt;/h1&gt;

&lt;p&gt;Here is the advantages of using Discord:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No more 10,000 message limit - you don't even have to pay, Discord won't limit you. All the features are accessible without paying 1 cent. It's quite incredible considering the quality of the product.&lt;/li&gt;
&lt;li&gt;Video and Voice conversations - Discord is great at supporting video and audio communication. There are no limits on this side either. Want to organize a quick meeting? Just go to a voice channel and it's done.&lt;/li&gt;
&lt;li&gt;Hierarchy - In a company, as in a community, not everyone has the same status. Discord has perfectly understood this and materializes it through the role system. Each member can have zero to many roles (R&amp;amp;D Engineer, Marketing, Finance, Founder, Contributor...). Each channel will then be accessible or not according to its roles. This is very practical to make only some channels accessible to the community and the rest to the staff.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Discord sets the standard very high! The tool was designed with the idea of improving teamwork - obviously on the gaming side. But this is perfectly transposable to the needs of companies. In fact, today online games are taken very seriously and are considered a real business activity. Pricing is another very important point, let's talk about it...&lt;/p&gt;

&lt;h1&gt;
  
  
  Interesting pricing model
&lt;/h1&gt;

&lt;p&gt;At Qovery, we have no problem paying for the tools we use. The problem is that we can't afford a tool that charges us a per-user license fee! This is where Discord stands out from its competitors by offering pricing to the global "server" rather than to the user. By the way, if you don't want to do HD voice communication no need to pay. It's a very virtuous and collaborative pricing model. Because your users can help you pay the bill at their convenience. In our case we pay $74.85/month for +150 developers. And even if tomorrow we're at 100,000 developers it won't change anything on the bill! Incredible isn't it?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--DJ81Hudx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e712241044a8f9afd38d7fa_Screenshot%25202020-03-17%2520at%252020.15.46.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--DJ81Hudx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e712241044a8f9afd38d7fa_Screenshot%25202020-03-17%2520at%252020.15.46.png" alt="$74.85/month for unlimited users and excellent audio call quality + HD video call"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Missing features from Slack :'(
&lt;/h1&gt;

&lt;p&gt;Telling you that being in Discord is heaven on earth would be a lie. It lacks some important features to make Discord the uncontested leader :)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrations: Discord was created for gamers, which means you won't find all the integrations you have for Slack. That's really the big negative! However, you can manage your integrations yourself thanks to webhooks. Thanks to this we were able to (almost) upload all the integrations we had on Slack (Stripe, Intercom, Mailchimp, Typeform and Gitlab) - few of them are still in progress.&lt;/li&gt;
&lt;li&gt;Thread messages: Discord does not allow threaded chat, even if the community requests access to this feature, there are no plans at this time. On the other hand, there is the possibility to "quote" a message.&lt;/li&gt;
&lt;li&gt;Gaming oriented: As I said above, Discord was made for gamers. Which means that sometimes certain usage options are not appropriate. Nothing too serious, but it can be confusing if you're planning to get people from other departments than R&amp;amp;D in there.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What's next?
&lt;/h1&gt;

&lt;p&gt;At Qovery we are really pleased with this change, especially in these bad times due to the Covid-19. Discord is for us very appropriate for our default remote office needs. On the community side we lack sufficient hindsight to tell you this, but the first feedback are positive.  We will keep you informed of how the product evolves. We secretly hope that Discord will take into account that their product can be an alternative to Slack for business.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://discord.gg/nHSckkw"&gt;Join us now on Discord&lt;/a&gt; 🚀&lt;/p&gt;

</description>
      <category>devlive</category>
    </item>
    <item>
      <title>Create a URL shortener API with Kotlin, the micro-framework Ktor and PostgreSQL</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Sun, 15 Mar 2020 11:29:24 +0000</pubDate>
      <link>https://dev.to/qovery/create-a-url-shortener-api-with-kotlin-the-micro-framework-ktor-and-postgresql-ip5</link>
      <guid>https://dev.to/qovery/create-a-url-shortener-api-with-kotlin-the-micro-framework-ktor-and-postgresql-ip5</guid>
      <description>&lt;p&gt;The source code for this post can be found on this &lt;a href="https://github.com/evoxmusic/ktor-url-shortener"&gt;github repo&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://ktor.io/"&gt;Ktor&lt;/a&gt; is a brand new micro-framework created by the Jetbrains team and running over the JVM. Jetbrains are the authors of &lt;a href="https://kotlinlang.org/"&gt;Kotlin&lt;/a&gt; - which is now the official programming language for Android, and one of the most popular programming language on the JVM. Kotlin is gaining popularity on server-side and multi-platform application development.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Ktor is a framework for building asynchronous servers and clients in connected systems using the powerful Kotlin programming language.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article you will learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to design a simple URL shortener.&lt;/li&gt;
&lt;li&gt;How to use the Ktor micro-framework with Kotlin&lt;/li&gt;
&lt;li&gt;How to deploy a Ktor application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I personally have +4 years of experience using Spring and I wanted to give a try to Ktor which seems promising. Creating a URL shortener is a good way to start.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is a URL shortener?
&lt;/h1&gt;

&lt;p&gt;A URL shortener is a simple tool that takes a long URL and turns it into a very short one&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fjd96bOr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e655859bc2ae5c7371efa36_urlshortener%2520image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fjd96bOr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e655859bc2ae5c7371efa36_urlshortener%2520image.png" alt="Flow of URL shortening - from original URL to short URL"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is commonly used for 3 reasons: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tracking clicks&lt;/li&gt;
&lt;li&gt;Make URL much more concise.&lt;/li&gt;
&lt;li&gt;Hide original URL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One famous freemium provider is bit.ly (see &lt;a href="https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e655a34bc2ae5452b1f124b_bitly.gif"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In this article we will make a basic bit.ly like URL shortener. Let’s go&lt;/p&gt;

&lt;h1&gt;
  
  
  Ktor principles
&lt;/h1&gt;

&lt;p&gt;Before starting I want to introduce the 3 main principles of Ktor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kotlin
&lt;/h2&gt;

&lt;p&gt;Kotlin is the language used to develop on Ktor. It is an object-oriented and functional language. It is very stable and runs on the JVM. Kotlin is 100% interoperable with Java and allows you to benefit from its ecosystem (libraries, build system, etc.).&lt;/p&gt;

&lt;h2&gt;
  
  
  Functional programming
&lt;/h2&gt;

&lt;p&gt;Ktor leverages the power of Kotlin and has a very functional approach. When writing code, everything seems obvious. It's very similar to what you can see on NodeJS. For me, coming from the Spring world, I find it very efficient to read and use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asynchronous
&lt;/h2&gt;

&lt;p&gt;Kotlin provides asynchronous code execution, thanks to &lt;a href="https://kotlinlang.org/docs/reference/coroutines-overview.html"&gt;coroutines&lt;/a&gt;. Ktor exploits this feature to its full potential, and even if you have the impression that you are writing code in a blocking manner, this is not the case. Ktor makes your life easier.&lt;/p&gt;

&lt;h1&gt;
  
  
  HTTP Server
&lt;/h1&gt;

&lt;p&gt;Here is a complete and simple example of how to expose an HTTP server (&lt;a href="http://localhost:8080"&gt;http://localhost:8080&lt;/a&gt;) with Ktor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;):&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ktor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;EngineMain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JvmOverloads&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;routing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello World"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contentType&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ContentType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Plain&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  URL Encoder
&lt;/h1&gt;

&lt;p&gt;The URL encoder will translate an incoming address into a smaller URL. The idea is to provide an ID that will identify the final URL. Using a hash function is perfect for this operation. However, the operation is non-reversible, meaning you can’t retrieve the final URL by the generated identifier.&lt;/p&gt;

&lt;p&gt;Function to transform a long URL into a shorter URL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// String extension&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeToID&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// hash String with MD5&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hashBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageDigest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MD5"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toByteArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Charsets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UTF_8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;// transform to human readable MD5 String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hashString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%032x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashBytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;// truncate MD5 String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;truncatedHashString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// return id&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;truncatedHashString&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We expose the function through the REST API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Request object&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;toResponse&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeToID&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Response object&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;originalURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;shortURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8080/$id"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JvmOverloads&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContentNegotiation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;jackson&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SerializationFeature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;INDENT_OUTPUT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;propertyNamingStrategy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PropertyNamingStrategy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SNAKE_CASE&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Hash Table Response object by ID&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;responseByID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableMapOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="nf"&gt;routing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v1/encode"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Deserialize JSON body to Request object&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;// find the Response object if it already exists&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseByID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeToID&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// cache hit&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cache hit $retrievedResponse"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@post&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retrievedResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// cache miss&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;responseByID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeToID&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cache miss $response"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;// Serialize Response object to JSON body&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handle identifier collision
&lt;/h2&gt;

&lt;p&gt;Using a hash function makes no guarantee that it is not already being used. If it is in use, then you need to change it to another one. Note: even if the probability to have a collision is very low, you should handle this case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// String extension (function signature has changed)&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeToID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;truncateLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// hash String with MD5&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hashBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MessageDigest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MD5"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toByteArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Charsets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;UTF_8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;// transform to human readable MD5 String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;hashString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%032x"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigInteger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashBytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;// truncate MD5 String&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;truncatedHashString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashString&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;truncateLength&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// return id&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;truncatedHashString&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//...&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JvmOverloads&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="c1"&gt;// Hash Table Response object by id&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;responseByID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mutableMapOf&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;truncateLength&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encodeToID&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseByID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retrievedResponse&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;originalURL&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// collision spotted !&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;getIdentifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;truncateLength&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;routing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v1/encode"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Deserialize JSON body to Request object&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;// find the Response object if it already exists&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseByID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// cache hit&lt;/span&gt;
                &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cache hit $retrievedResponse"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@post&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retrievedResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// cache miss&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toResponse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;responseByID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cache miss $response"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;// Serialize Response object to JSON body&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  URL Decoder
&lt;/h1&gt;

&lt;p&gt;Decoding the URL is the process of returning the original URL from the short URL. This is the reverse operation made by the URL Encoder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;shortURL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getShortURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;responseByID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;shortURL&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;retrievedResponse&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="n"&gt;originalURL&lt;/span&gt; &lt;span class="c1"&gt;// return original URL or null&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Redirect
&lt;/h1&gt;

&lt;p&gt;When a user clicks on a short URL, the user is redirected to the final URL. HTTP protocol allows to do this naturally by returning a 302 status code and a redirection URL.&lt;/p&gt;

&lt;p&gt;With Ktor the redirection is as simple as calling a method with the final URL as a parameter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://www.qovery.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What we expect is that when the user visits &lt;a href="http://localhost:8080/fbc951"&gt;http://localhost:8080/fbc951&lt;/a&gt; he is redirected to &lt;a href="https://www.qovery.com"&gt;https://www.qovery.com&lt;/a&gt;. If the URL is incorrect then redirect to &lt;a href="https://www.google.com"&gt;https://www.google.com&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JvmOverloads&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nf"&gt;routing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{id}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;responseByID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNullOrBlank&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@get&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://www.google.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redirect to: $retrievedResponse"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respondRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retrievedResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;originalURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Stats: clicks over time
&lt;/h1&gt;

&lt;p&gt;Something that is really useful on products like bit.ly is the stats provided (click over time, referrers, country of visitors). Here is how to store click over time and make them available through the API&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// added&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;clicksOverTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;MutableList&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mutableListOf&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;// Response object (modified with Stat)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;originalURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Stat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Stat&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;shortURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"http://localhost:8080/$id"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JvmOverloads&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;install&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ContentNegotiation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;jackson&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
            &lt;span class="c1"&gt;// add this line to return Date object as ISO8601 format&lt;/span&gt;
            &lt;span class="nf"&gt;disable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SerializationFeature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;WRITE_DATES_AS_TIMESTAMPS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nf"&gt;routing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
        &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/api/v1/url/{id}/stat"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;let&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;responseByID&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isNullOrBlank&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;retrievedResponse&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="nd"&gt;@get&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpStatusCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;NoContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retrievedResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Try the API
&lt;/h1&gt;

&lt;p&gt;Run the application&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./gradlew run
//...
2020-03-12 09:28:08.150 &lt;span class="o"&gt;[&lt;/span&gt;main] INFO  Application - No ktor.deployment.watch patterns specified, automatic reload is not active
2020-03-12 09:28:08.606 &lt;span class="o"&gt;[&lt;/span&gt;main] INFO  Application - Responding at http://0.0.0.0:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then execute the commands&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# generate a short URL&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"url": "https://www.qovery.com"}'&lt;/span&gt; &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-type: application/json"&lt;/span&gt; &lt;span class="s2"&gt;"http://localhost:8080/api/v1/encode"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"original_url"&lt;/span&gt;: &lt;span class="s2"&gt;"https://www.qovery.com"&lt;/span&gt;,
  &lt;span class="s2"&gt;"stat"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"clicks_over_time"&lt;/span&gt;: &lt;span class="o"&gt;[]&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"short_url"&lt;/span&gt;: &lt;span class="s2"&gt;"http://localhost:8080/fbc951"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# generate 4 fake clicks&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'http://localhost:8080/fbc951'&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'http://localhost:8080/fbc951'&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'http://localhost:8080/fbc951'&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'http://localhost:8080/fbc951'&lt;/span&gt;

&lt;span class="c"&gt;# show stat&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="s1"&gt;'http://localhost:8080/api/v1/url/fbc951/stat'&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"clicks_over_time"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"2020-03-11T21:10:52.354+0000"&lt;/span&gt;,
    &lt;span class="s2"&gt;"2020-03-11T21:10:54.093+0000"&lt;/span&gt;,
    &lt;span class="s2"&gt;"2020-03-11T21:12:34.987+0000"&lt;/span&gt;,
    &lt;span class="s2"&gt;"2020-03-11T21:12:37.223+0000"&lt;/span&gt;
  &lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Connect to a PostgreSQL database with Exposed
&lt;/h1&gt;

&lt;p&gt;By storing the data in memory, we lose all the data every time the application restart. Which is problematic for running in production. To make the data persistent we will store it in a PostgreSQL database. We will have to add 1 new dependency - &lt;a href="https://github.com/JetBrains/Exposed"&gt;Exposed&lt;/a&gt;. Exposed (with &lt;a href="https://github.com/brettwooldridge/HikariCP"&gt;Hikari Connection Pool&lt;/a&gt;) is a lightweight SQL library on top of JDBC driver for Kotlin. With exposed it is possible to access databases in two flavours: typesafe SQL wrapping DSL and lightweight Data Access Objects (DAO).&lt;/p&gt;

&lt;p&gt;Add the dependencies to your build.gradle (or POM.xml)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;repositories&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;jcenter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Connection Pool and PostgreSQL driver&lt;/span&gt;
  &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.zaxxer:HikariCP:3.4.2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.postgresql:postgresql:42.2.11"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// Exposed&lt;/span&gt;
  &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.jetbrains.exposed:exposed-core:0.22.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.jetbrains.exposed:exposed-dao:0.22.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.jetbrains.exposed:exposed-jdbc:0.22.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"org.jetbrains.exposed:exposed-java-time:0.22.1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to have 2 distincts tables, one containing all the final URLs with their correspond identifier&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ResponseTable&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"response"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;originalURL&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"original_url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2048&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PrimaryKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PrimaryKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a second one with all the clicking points&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;ClickOverTimeTable&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"click_over_time"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;autoIncrement&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;clickDate&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"click_date"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"response_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onDelete&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ReferenceOption&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;refColumn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ResponseTable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;primaryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PrimaryKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PrimaryKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to create the tables as defined above programmatically&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;initDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HikariConfig&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;jdbcUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jdbc:postgresql://127.0.0.1:5432/exposed"&lt;/span&gt;
        &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"exposed"&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"exposed"&lt;/span&gt;
        &lt;span class="n"&gt;driverClassName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.postgresql.Driver"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HikariDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// create tables if they do not exist&lt;/span&gt;
        &lt;span class="nc"&gt;SchemaUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createMissingTablesAndColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RequestTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ClickOverTimeTable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="n"&gt;kotlin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jvm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;JvmOverloads&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;initDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have to replace the Hash Table used to store the data by the PostgreSQL database (see the final code &lt;a href="https://github.com/evoxmusic/ktor-url-shortener/blob/with_postgresql/src/Application.kt"&gt;here&lt;/a&gt;)&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploy in the Cloud with Qovery
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.qovery.com"&gt;Qovery&lt;/a&gt; is going to help us to deploy the final application in the Cloud without the need to configure the CI/CD, network, security, load balancing, database and all the DevOps tasks&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Qovery is a Container as a Service platform for developer - developers can deploy their application in the Cloud in just a few seconds&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pre-requisites:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have an account on Qovery (register &lt;a href="https://www.qovery.com/"&gt;here&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Your code need to be hosted on Github&lt;/li&gt;
&lt;li&gt;You need to &lt;a href="https://github.com/apps/qovery/installations/new"&gt;accept the Qovery Github app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ktor.io/quickstart/quickstart/docker.html"&gt;Package your Ktor application to build and run it on Docker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To deploy on Qovery 2 files are mandatories&lt;/p&gt;

&lt;p&gt;.qovery.yml - a very simple way to declare the dependencies that you need (e.g: PostgreSQL) and where you want to run it (here on AWS and eu-west-3 / Paris)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;application&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
  &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;url-shortener&lt;/span&gt;
  &lt;span class="na"&gt;cloud_region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws/eu-west-3&lt;/span&gt;
  &lt;span class="na"&gt;publicly_accessible&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;databases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
    &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;11.5"&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-pql-db&lt;/span&gt;
&lt;span class="na"&gt;routers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;application_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dockerfile - to build and run your application on Qovery&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;FROM openjdk:8-jre-alpine

ENV APPLICATION_USER ktor
RUN adduser &lt;span class="nt"&gt;-D&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="nv"&gt;$APPLICATION_USER&lt;/span&gt;

RUN &lt;span class="nb"&gt;mkdir&lt;/span&gt; /app
RUN &lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$APPLICATION_USER&lt;/span&gt; /app

USER &lt;span class="nv"&gt;$APPLICATION_USER&lt;/span&gt;

COPY ./build/libs/ktor-url-shortener.jar /app/ktor-url-shortener.jar
WORKDIR /app

CMD &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"java"&lt;/span&gt;, &lt;span class="s2"&gt;"-server"&lt;/span&gt;, &lt;span class="s2"&gt;"-XX:+UnlockExperimentalVMOptions"&lt;/span&gt;, &lt;span class="s2"&gt;"-XX:+UseCGroupMemoryLimitForHeap"&lt;/span&gt;, &lt;span class="s2"&gt;"-XX:InitialRAMFraction=2"&lt;/span&gt;, &lt;span class="s2"&gt;"-XX:MinRAMFraction=2"&lt;/span&gt;, &lt;span class="s2"&gt;"-XX:MaxRAMFraction=2"&lt;/span&gt;, &lt;span class="s2"&gt;"-XX:+UseG1GC"&lt;/span&gt;, &lt;span class="s2"&gt;"-XX:MaxGCPauseMillis=100"&lt;/span&gt;, &lt;span class="s2"&gt;"-XX:+UseStringDeduplication"&lt;/span&gt;, &lt;span class="s2"&gt;"-jar"&lt;/span&gt;, &lt;span class="s2"&gt;"ktor-url-shortener.jar"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Connect to PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Qovery add dynamically all required environment variables to connect to the database at the runtime of the container.&lt;/p&gt;

&lt;p&gt;To list all of them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;qovery application &lt;span class="nb"&gt;env &lt;/span&gt;list
  SCOPE    | KEY                                                          | VALUE
  BUILT_IN | QOVERY_JSON_B64                                              | &amp;lt;&lt;span class="nb"&gt;base64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  BUILT_IN | QOVERY_BRANCH_NAME                                           | with_postgresql
  BUILT_IN | QOVERY_IS_PRODUCTION                                         | &lt;span class="nb"&gt;false
  &lt;/span&gt;BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_NAME                               | my-pql-db
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_TYPE                               | POSTGRESQL
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_VERSION                            | 11.5
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_CONNECTION_URI                     | &amp;lt;hidden&amp;gt;
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_CONNECTION_URI_WITHOUT_CREDENTIALS | &amp;lt;hidden&amp;gt;
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_HOST                               | &amp;lt;hidden&amp;gt;
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_FQDN                               | &amp;lt;hidden&amp;gt;
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_PORT                               | &amp;lt;hidden&amp;gt;
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_USERNAME                           | &amp;lt;hidden&amp;gt;
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_PASSWORD                           | &amp;lt;hidden&amp;gt;
  BUILT_IN | QOVERY_DATABASE_MY_PQL_DB_DATABASE                           | postgres
  BUILT_IN | QOVERY_APPLICATION_API_HOSTNAME                              | &amp;lt;hidden&amp;gt;
  BUILT_IN | QOVERY_APPLICATION_API_HOST                                  | &amp;lt;hidden&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To use them&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;initDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HikariConfig&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;jdbcUrl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jdbc:${System.getenv("&lt;/span&gt;&lt;span class="nc"&gt;QOVERY_DATABASE_MY_PQL_DB_CONNECTION_URI_WITHOUT_CREDENTIALS&lt;/span&gt;&lt;span class="s"&gt;")}"&lt;/span&gt;
        &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"QOVERY_DATABASE_MY_PQL_DB_USERNAME"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"QOVERY_DATABASE_MY_PQL_DB_PASSWORD"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;driverClassName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.postgresql.Driver"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;Database&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HikariDataSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="nf"&gt;transaction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// create tables if they do not exist&lt;/span&gt;
        &lt;span class="nc"&gt;SchemaUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createMissingTablesAndColumns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RequestTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;ClickOverTimeTable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy
&lt;/h2&gt;

&lt;p&gt;Deploying your app with Qovery is as simple as commit and push your code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git add .qovery.yml Dockerfile
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add .qovery.yml and Dockerfile files"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the public URL&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;qovery status
  BRANCH NAME     | STATUS  | ENDPOINTS                                   | APPLICATIONS | DATABASES | BROKERS | STORAGE
  with_postgresql | running | https://qavcgggy6g6dlkbj-main-gtw.qovery.io | 1            | 1         | 0       | 0

  APPLICATION NAME | STATUS  | ENDPOINT                                                | DATABASES | BROKERS | STORAGE
  api              | running | https://ete6bq97amj9n82c-qavcgggy6g6dlkbj-app.qovery.io | 1         | 0       | 0

  DATABASE NAME | STATUS  | TYPE       | VERSION | ENDPOINT | PORT     | USERNAME | PASSWORD | APPLICATIONS
  my-pql-db     | running | POSTGRESQL | 11.5    | &amp;lt;hidden&amp;gt; | &amp;lt;hidden&amp;gt; | &amp;lt;hidden&amp;gt; | &amp;lt;hidden&amp;gt; | api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;We have seen that creating an URL shortener API with Ktor and Kotlin is extremely simple. The connection of the application with the PostgreSQL database is done in a very easy way with the Exposed library. In just a few lines of code the service is fully functional, and can be deployed in production very quickly with the help of Qovery. In the next part we will see how to create a web interface connecting to this API to convert our URLs without using the curl command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2: bind a web interface to the API&lt;/strong&gt; - [link coming soon]&lt;/p&gt;

</description>
      <category>kotlin</category>
      <category>ktor</category>
      <category>postgres</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>3 ways of cloning an application and a database per git branch</title>
      <dc:creator>Romaric P.</dc:creator>
      <pubDate>Thu, 27 Feb 2020 22:52:21 +0000</pubDate>
      <link>https://dev.to/qovery/3-ways-of-cloning-an-application-and-a-database-per-git-branch-40d4</link>
      <guid>https://dev.to/qovery/3-ways-of-cloning-an-application-and-a-database-per-git-branch-40d4</guid>
      <description>&lt;p&gt;Back in the early days of software development, having multiple developers working on the same application was a tough challenge. That’s why VCS (Version Control System) like Git was created and methodology like Feature Branching was introduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  1 feature = 1 branch
&lt;/h2&gt;

&lt;p&gt;The basic idea of working per git branch (also known as &lt;a href="https://martinfowler.com/bliki/FeatureBranch.html"&gt;Feature Branching&lt;/a&gt;) is that when you start to work on a feature, you take a branch of your repository (e.g: git) to work on that feature.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--oXiOfG0u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e563f656725fdc63f304ecb_feature%2520branches.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--oXiOfG0u--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e563f656725fdc63f304ecb_feature%2520branches.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The advantage of feature branching is that each developer can work on their own feature and be &lt;strong&gt;isolated&lt;/strong&gt; from changes going on elsewhere. This concept has been initiated for stateless application. However, most applications rely on databases (also known as “stateful application”).&lt;/p&gt;

&lt;p&gt;The question is: How to use the concept of Feature Branch with a stateful application?&lt;/p&gt;

&lt;p&gt;Let’s take a concrete example:&lt;/p&gt;

&lt;p&gt;We have a NodeJS application that connects to a PostgreSQL database, for which we have 3 distinct branches: master, staging and feature_1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4916wxEX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e56460bb6294c8c040b1a34_applications%2520from%2520different%2520branches%2520connected%2520to%2520the%2520same%2520db.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4916wxEX--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e56460bb6294c8c040b1a34_applications%2520from%2520different%2520branches%2520connected%2520to%2520the%2520same%2520db.jpg" alt="applications from different branches connected to the same database"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;No matter which branch we are working on, we are always connected to the same database. If our application writes, modifies or deletes data in the database, while we are on the "feature_1" branch, all branches will also be impacted by these changes. Meaning, we violate the main Feature Branch principle - isolation. There is nothing more stressful than having to keep in mind that we can lose data and break everything at any time.&lt;/p&gt;

&lt;p&gt;so -&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to be compliant to the feature branch principle (isolation) with a database?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One possible solution is to have one copy of the database per branch. Each database can be modified without the risk of modifying the others.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OrO_482N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e5647f224830b3fd70af82e_applications%2520from%2520different%2520branches%2520connected%2520to%2520the%2520their%2520specific%2520db.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OrO_482N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e5647f224830b3fd70af82e_applications%2520from%2520different%2520branches%2520connected%2520to%2520the%2520their%2520specific%2520db.jpg" alt="branches have their own database"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to have one database per branch?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;3 main choices are possible (manual, partially automated and fully automated) with all their advantages and disadvantages.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manual
&lt;/h2&gt;

&lt;p&gt;Manual means that you need to install all your required services (DNS, databases, VPC...) manually. This approach is fast enough to bootstrap, but it is hard to maintain over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fast to bootstrap&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to configure system and network services (DNS, network, security...)&lt;/li&gt;
&lt;li&gt;You need to manually create the databases according to the number of branches&lt;/li&gt;
&lt;li&gt;You need to manually synchronize the data between the databases&lt;/li&gt;
&lt;li&gt;You need to setup observability, monitoring and alerting&lt;/li&gt;
&lt;li&gt;Hard to maintain over time&lt;/li&gt;
&lt;li&gt;Error prone&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Partially automated
&lt;/h2&gt;

&lt;p&gt;Partially automated means that you will spend time to set up a complete system that is provisioning your required services for your project. This type of architecture required times and effort from experienced DevOps. It's a good choice for large corporations that can support the cost, but most of the time a really bad for smaller one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic system and network configuration + database provisioning with tools like &lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt; (&lt;a href="https://en.wikipedia.org/wiki/Infrastructure_as_code"&gt;Infrastructure as Code&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Automatic data synchronization between databases (with a custom script)&lt;/li&gt;
&lt;li&gt;Perfectly fit your need&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to manually create the databases according to the number of branches&lt;/li&gt;
&lt;li&gt;Required months to fully set up&lt;/li&gt;
&lt;li&gt;Expensive maintenance over time (experienced DevOps engineer required)&lt;/li&gt;
&lt;li&gt;You need to setup observability, monitoring and alerting&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fully automated (with Qovery)
&lt;/h2&gt;

&lt;p&gt;Fully automated means that all the required resources by the developer will be deployed no matter his needs. With Qovery, all resources are automatically provided and the developer don't even have to change their habits to deploy their application. Feature Branching is supported out of the box. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Qovery gives to any developer the power to clone an application and a database without having to change their habits&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's take the example of our 3 branches with a NodeJS application and a PostgreSQL database&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LmB68W6K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e564821c87b5cc14f6fcdf8_one%2520nodejs%2520%252B%2520postgresql%2520per%2520environment.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LmB68W6K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://uploads-ssl.webflow.com/5de176c0d41c9b4a1dbbb0aa/5e564821c87b5cc14f6fcdf8_one%2520nodejs%2520%252B%2520postgresql%2520per%2520environment.png" alt="1 branch = 1 isolated environment with Qovery"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the commands necessary to have a fully isolated application and database on each branch&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;
~/my-nodejs-project 

&lt;span class="c"&gt;# Github, Bitbucket, Gitlab seamless authentication&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;qovery auth
Opening your browser, waiting &lt;span class="k"&gt;for &lt;/span&gt;your authentication...
Authentication successful!

&lt;span class="c"&gt;# Wizard to generate .qovery.yml&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;qovery init

&lt;span class="nv"&gt;$ &lt;/span&gt;git add .qovery.yml
&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"add .qovery.yml file"&lt;/span&gt;

&lt;span class="c"&gt;# Deploy master environment&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin master

&lt;span class="c"&gt;# Show master environment information&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;qovery status

&lt;span class="c"&gt;# Create branch staging&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; staging
&lt;span class="c"&gt;# Deploy staging environment!&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin staging

&lt;span class="c"&gt;# Show staging environment information&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;qovery status

&lt;span class="c"&gt;# Create branch feature_1&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git checkout &lt;span class="nt"&gt;-b&lt;/span&gt; feature_1
&lt;span class="c"&gt;# Deploy feature_1 environment!&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;git push &lt;span class="nt"&gt;-u&lt;/span&gt; origin feature_1

&lt;span class="c"&gt;# Show feature_1 environment information&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;qovery status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Advantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accessible to any developer&lt;/li&gt;
&lt;li&gt;No set up time&lt;/li&gt;
&lt;li&gt;Programming language agnostic&lt;/li&gt;
&lt;li&gt;Integrated to git (no other dependencies required)&lt;/li&gt;
&lt;li&gt;Compliant to Feature Branching concept&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disadvantages&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Know how to create a Dockerfile&lt;/li&gt;
&lt;li&gt;Deployment only available on AWS, GCP and Azure&lt;/li&gt;
&lt;li&gt;Only integrated to Github, Gitlab and Bitbucket&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we have seen that the purpose of the Feature Branch is to be able to develop a feature without being impacted by changes that can be made to other branches.&lt;/p&gt;

&lt;p&gt;However, the Feature Branch concept is difficult to apply when our application needs to access a database. Because each application (coming from several branches) access to the same database. This is contrary to the Feature Branch isolation principle and brings serious data safety problems.&lt;/p&gt;

&lt;p&gt;Qovery allows application and database to be seamlessly duplicated from one branch to another. And thus to respect the isolation principles of the Feature Branch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.qovery.com"&gt;Try Qovery now!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Useful links: &lt;a href="https://martinfowler.com/bliki/FeatureBranch.html"&gt;Feature Branching&lt;/a&gt; and &lt;a href="https://martinfowler.com/bliki/FeatureBranch.html"&gt;Continuous Integration&lt;/a&gt; from Martin Fowler - &lt;a href="https://softwareengineering.stackexchange.com/questions/391070/stateful-vs-non-stateful-app"&gt;Stateless vs Stateful&lt;/a&gt; from StackOverflow&lt;/p&gt;

</description>
      <category>git</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
