<?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>Forem: Adam Katora</title>
    <description>The latest articles on Forem by Adam Katora (@adamkatora).</description>
    <link>https://forem.com/adamkatora</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%2Fuser%2Fprofile_image%2F629688%2Ff286e81e-37bd-43e0-9e42-b359912aaf4c.jpg</url>
      <title>Forem: Adam Katora</title>
      <link>https://forem.com/adamkatora</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/adamkatora"/>
    <language>en</language>
    <item>
      <title>How to use Burp Suite through a socks5 proxy with proxychains and chisel</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Wed, 29 Mar 2023 20:33:02 +0000</pubDate>
      <link>https://forem.com/adamkatora/how-to-use-burp-suite-through-a-socks5-proxy-with-proxychains-and-chisel-507e</link>
      <guid>https://forem.com/adamkatora/how-to-use-burp-suite-through-a-socks5-proxy-with-proxychains-and-chisel-507e</guid>
      <description>&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; For this example I used HTB's Dante Pro Labs. I don't show any exploits or attack vectors, but if you're working through the labs on your own and don't want to see anything that could even remotely be considered a spoiler, you've been warned.&lt;/p&gt;




&lt;p&gt;Recently, I've been prepping for the OSCP and one of the major focus areas of the Penetration Testing with Kali course materials is understanding how to effectively pivot into internal subnets.&lt;/p&gt;

&lt;p&gt;My go-to method for pivoting is through a &lt;a href="https://github.com/jpillora/chisel" rel="noopener noreferrer"&gt;chisel&lt;/a&gt; socks5 proxy. I won't go into detail here about how to set that up, but if you want a walkthrough, &lt;a href="https://ap3x.github.io/posts/pivoting-with-chisel/" rel="noopener noreferrer"&gt;Ap3x Security's writeup&lt;/a&gt; on chisel is a fantastic resource. For the rest of this article, I'm assuming you've used the setup and configuration options from that guide.&lt;/p&gt;

&lt;p&gt;In the lab environment I'll be working in, I've already setup my pivot, which included transfering a copy of the &lt;code&gt;chisel&lt;/code&gt; binary onto the victim. The image below shows the victim host making a connect back to our reverse proxy, and the chisel server output confirms that connection.&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%2Fuploads%2Farticles%2Fabsjtmbeotn9y1a2hlld.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabsjtmbeotn9y1a2hlld.png" alt="Setup chisel proxy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that pivot in place, we can now access the private network in the &lt;code&gt;172.16.1.0/24&lt;/code&gt; range. We're going to skip the internal network enumeration step, and assume that we've found an internal host with &lt;code&gt;HTTP&lt;/code&gt; open on port &lt;code&gt;80&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;One way to access that web service is to configure &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/foxyproxy-standard/" rel="noopener noreferrer"&gt;FoxyProxy&lt;/a&gt; to route through our &lt;code&gt;socks5&lt;/code&gt; proxy on &lt;code&gt;localhost:1080&lt;/code&gt;. In my FoxyProxy setup, I have 2 proxies configured, one is the default setup for Burp Suite, the other is for proxychains. Take note that the Burp Suite proxy uses &lt;code&gt;HTTP&lt;/code&gt;, for the &lt;code&gt;Proxy Type&lt;/code&gt; field and proxychains uses &lt;code&gt;SOCKS5&lt;/code&gt;.&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%2Fuploads%2Farticles%2F7qkyx2w7evoemd69zv0y.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qkyx2w7evoemd69zv0y.png" alt="FoxyProxy Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without FoxyProxy, we can't access the internal host:&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%2Fuploads%2Farticles%2Fm69tw4uuq39dur5j33x7.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm69tw4uuq39dur5j33x7.png" alt="No access to internal host HTTP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Using FoxyProxy to route traffic through our &lt;code&gt;localhost:1080&lt;/code&gt; proxy, we can access the internal webserver: (note that the FoxyProxy icon is green indicating the proxy is active)&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhatt276g12h3ql91alb0.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhatt276g12h3ql91alb0.png" alt="Pivot working - access to HTTP"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this approach does allow us to interact with the internal webserver in our browser, the major drawback to this approach is that we're unable to use Burp Suite through the pivot.&lt;/p&gt;

&lt;p&gt;Fortunately, the solution is fairly simple. Burp Suite has a feature called Upstream Proxies, which allows us to both proxy traffic through both Burp Suite and our &lt;code&gt;socks5&lt;/code&gt; tunnel.&lt;/p&gt;

&lt;p&gt;To use an upstream proxy, we'll first start by switching our FoxyProxy back to using the default Burp Suite setup.&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%2Fuploads%2Farticles%2Finjz1s5qsolxbo37k5m9.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finjz1s5qsolxbo37k5m9.png" alt="FoxyProxy - Activate Burp Suite proxy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, you'll need to open up the "Settings" window inside Burp Suite. You can find the button to open that in the upper right-hand corner of the application.&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%2Fuploads%2Farticles%2Fyjovrjrlbuesvcamcy5b.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjovrjrlbuesvcamcy5b.png" alt="Burp Suite Settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the settings window opens up, with the "All" tab / button highlighted, select the "Network" option from the left sidebar dropdown, and in the main window scroll to the bottom of the page until you see the &lt;code&gt;SOCKS Proxy&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;Once you've reached the &lt;code&gt;SOCKS Proxy&lt;/code&gt; section, we can now configure Burp to work with our chisel tunnel. Before doing so, I recommend selecting the option &lt;code&gt;Override options for this project only&lt;/code&gt;. That way if you quit Burp Suite, and open up a new project later, you avoid the headache of forgetting that the socks proxy is on and not being able to connect to any websites.&lt;/p&gt;

&lt;p&gt;Toggling the &lt;code&gt;Override options for this project only&lt;/code&gt;, will clear out the host and port fields, which is the reason I reccommend selecting that option before entering your proxy config.&lt;/p&gt;

&lt;p&gt;The actual settings to configure are fairly straight forward. Type in &lt;code&gt;127.0.0.1&lt;/code&gt; for the host and &lt;code&gt;1080&lt;/code&gt; for the port (if you followed the chisel setup from &lt;code&gt;Ap3x&lt;/code&gt;). Finally check the &lt;code&gt;Use SOCKS proxy&lt;/code&gt; box, and our setup is all configured.&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%2Fuploads%2Farticles%2Few7s9gz38fpzwse9qjja.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Few7s9gz38fpzwse9qjja.png" alt="Configure socks proxy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that setup out of the way, we can return to our browser, and try accessing the webserver on the internal host again. If everything is setup correctly, you should be able to access the website in your browser, but this time around you should also be able to see the request in Burp Suite's proxy window too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;note that foxyproxy option is set to Burp suite this time&lt;/strong&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl23orgbfnegrqks1r0vh.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl23orgbfnegrqks1r0vh.png" alt="FoxyProxy using Burp Suite proxy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back in Burp Suite we can see that the request is being logged. &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%2Fuploads%2Farticles%2Fcj4r5aqvzirl00cfafb8.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcj4r5aqvzirl00cfafb8.png" alt="Burp Suite intercepting requests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have our upstream proxy setup, we can use Burp Suite against any HTTP webservers in the internal network, just like we would against ones in a public subnet.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>security</category>
    </item>
    <item>
      <title>HTTP - File Transfers for CTFs and Red-Teamers Pt. 1</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Sun, 22 Jan 2023 21:40:32 +0000</pubDate>
      <link>https://forem.com/adamkatora/http-file-transfers-for-ctfs-and-red-teamers-pt-1-2582</link>
      <guid>https://forem.com/adamkatora/http-file-transfers-for-ctfs-and-red-teamers-pt-1-2582</guid>
      <description>&lt;p&gt;&lt;strong&gt;Quick Disclaimer:&lt;/strong&gt; These are some of the techniques and tips I've picked up from ethical hacking and CTFs. But remember, performing these actions against networks or hosts you don't have explicit permission to do so is illegal. Don't break the law.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;Opsec Considerations&lt;/li&gt;
&lt;li&gt;
File Transfer Scenarios

&lt;ul&gt;
&lt;li&gt;Linux Attacker -&amp;gt; Linux Victim&lt;/li&gt;
&lt;li&gt;Linux Victim -&amp;gt; Linux Attacker&lt;/li&gt;
&lt;li&gt;Linux Attacker -&amp;gt; Windows Victim&lt;/li&gt;
&lt;li&gt;Windows Victim -&amp;gt; Linux Attacker&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;HTTP Server Options:

&lt;ul&gt;
&lt;li&gt;Linux HTTP Servers&lt;/li&gt;
&lt;li&gt;Windows HTTP Servers&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;HTTP Client Options:

&lt;ul&gt;
&lt;li&gt;Linux HTTP Clients&lt;/li&gt;
&lt;li&gt;Windows HTTP Clients&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Potential Firewall Issues&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overview &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;In the course of CTFs or Red Team engagements, there will come a point where you'll need to either infiltrate data (get files or programs &lt;strong&gt;&lt;em&gt;on to&lt;/em&gt;&lt;/strong&gt; the compromised host) or exfiltrate data (get files or programs &lt;strong&gt;&lt;em&gt;off of&lt;/em&gt;&lt;/strong&gt; the compromised host).&lt;/p&gt;

&lt;p&gt;One of the simplest methods to achieve this is via HTTP, the same technology that powers websites.&lt;/p&gt;

&lt;p&gt;It's important to note that HTTP is &lt;strong&gt;unidirectional&lt;/strong&gt;, meaning that a client can download files from an HTTP server, but the server can't download files from the client. Additionally, the client can't upload files to the HTTP server. This is in contrast to file share specific protocols, like SMB or FTP, where clients can easily download &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt; upload files to the server.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Side note:&lt;/strong&gt; You &lt;strong&gt;&lt;em&gt;technically can&lt;/em&gt;&lt;/strong&gt; upload files via HTTP, typically using a &lt;code&gt;POST&lt;/code&gt; request. However, that requires the HTTP server to also be running an application that has been programmed to include that functionality. Because of that, I'd consider that to be a potential web-app vulnerability, instead of being a reliable option for data infil/exfiltration.&lt;/p&gt;




&lt;p&gt;To visualize a simple HTTP data transfer, I've included the below graphic. In this scenario, the attacker has compromised the victim, &lt;code&gt;10.10.10.1&lt;/code&gt;, and has shell access on that machine, meaning they can execute system commands. The attacker needs to transfer a file, &lt;code&gt;file.txt&lt;/code&gt;, onto the victim. On the attacker's machine, &lt;code&gt;10.10.10.2&lt;/code&gt;, they start an HTTP server with &lt;code&gt;python -m http.server 80&lt;/code&gt; from the directory containing &lt;code&gt;file.txt&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On the victim's machine, the attacker runs the command &lt;code&gt;wget http://10.10.10.2/file.txt&lt;/code&gt;, which downloads &lt;code&gt;file.txt&lt;/code&gt; from the attacker's HTTP server onto the victim's compromised machine.&lt;/p&gt;

&lt;p&gt;Since the victim machine isn't running an HTTP server, however, the attacker can't download files from the victim back to the attacker's machine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftth0d00qlheupe307uz5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftth0d00qlheupe307uz5.jpg" alt="HTTP Unidirectional" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Opsec Considerations &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Before moving on to the scenarios, it's important to remember that the methods I'm describing here don't have any safeguards against unauthorized file access. Meaning, if you start an HTTP server on either your machine or a victim's, &lt;strong&gt;&lt;em&gt;anyone&lt;/em&gt;&lt;/strong&gt; on the same network as you will be able to access those files as well.&lt;/p&gt;

&lt;p&gt;Additionally, depending on what directory you host the HTTP server from, &lt;strong&gt;&lt;em&gt;all files and subdirectories of that folder&lt;/em&gt;&lt;/strong&gt; will be accessible to anyone on the same network as you.&lt;/p&gt;

&lt;p&gt;For example, if you're on a Linux host and run the &lt;code&gt;python -m http.server 80&lt;/code&gt; while &lt;strong&gt;in your home directory&lt;/strong&gt;, that will make all the files in your home directory publicly accessible, &lt;strong&gt;including any private keys in &lt;code&gt;.ssh/&lt;/code&gt;!!!&lt;/strong&gt;. Before starting an HTTP server, it's always good practice to ensure you know what directory it will be serving files from. Better yet, create a new empty directory, and start the file server there. This applies both to when you're hosting the webserver on your attacking machine and when doing so from a compromised victim.&lt;/p&gt;

&lt;h1&gt;
  
  
  File Transfer Scenarios &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;In each of the scenarios I'll be describing, I've randomly picked an HTTP client and HTTP server from the list of potential options to use. However, any HTTP client will be able to request files from any HTTP server. You'll need to find what client / server to use based off what you're most comfortable with using on your host, and what is available to you on the compromised victim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux Attacker -&amp;gt; Linux Victim &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The first scenario we'll look at is when you have shell access on a compromised Linux host, and need to download a file &lt;strong&gt;on to&lt;/strong&gt; that compromised machine. This is the same scenario we looked at earlier, except in the below image, our attacking machine is using Apache for the HTTP server instead of Python.&lt;/p&gt;

&lt;p&gt;Since the HTTP Server is being hosted from our attacking machine, that means that virtually any of the below Linux HTTP Server Options are available. On our own machine, we have full permissions to install any software needed, and run programs as &lt;code&gt;sudo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;From the shell on the victim, however, we'll need to find &lt;code&gt;which&lt;/code&gt; binaries already exist on the victim that we can use to download files. Fortunately, that's easy in Linux using the &lt;code&gt;which &amp;lt;binary name&amp;gt;&lt;/code&gt; command. For example, if you'd want to see if &lt;code&gt;wget&lt;/code&gt; is already installed on the host you would just need to run the command &lt;code&gt;which wget&lt;/code&gt;. If the command returns a file path, the program is installed. If it doesn't return anything, you'll need to find another program.&lt;/p&gt;

&lt;p&gt;Looking back at the scenario, the attacker has shell access on the victim, and found that &lt;code&gt;wget&lt;/code&gt; is installed. The attacker wants to transfer &lt;code&gt;file.txt&lt;/code&gt; onto the victim, so the attacker starts an HTTP server on his own host with &lt;code&gt;sudo service apache2 start&lt;/code&gt;. From the shell access on the victim, the attacker runs &lt;code&gt;wget http://10.10.10.2/file.txt&lt;/code&gt; which downloads the file from the attacker, onto the victim.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22o2ke76afitfp6amv2u.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F22o2ke76afitfp6amv2u.jpg" alt="File Transfer: Linux Attacker to Linux Victim" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux Victim -&amp;gt; Linux Attacker &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This second scenario is the reverse of the first we looked at. This is when you have shell access on a compromised Linux host, and need to download a file &lt;strong&gt;from&lt;/strong&gt; that compromised host. Much like how we looked to see if &lt;code&gt;wget&lt;/code&gt; was available in scenario 1, in this case we'll need to check if one of binaries from Linux HTTP Server Options is available for us to use on the victim.&lt;/p&gt;

&lt;p&gt;In the scenario below, the victim host has &lt;code&gt;Python 3&lt;/code&gt; installed. So, from shell access on the victim, the attacker starts a &lt;code&gt;Python 3&lt;/code&gt; HTTP server in the directory that contains, &lt;code&gt;file.txt&lt;/code&gt;. Then, on the attackers own machine, they use &lt;code&gt;wget&lt;/code&gt; to download &lt;code&gt;file.txt&lt;/code&gt; from the victim, back to the attacker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;note that the attacker and victim have switched sides in this image&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqz08ff1akkl3ndityx3z.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqz08ff1akkl3ndityx3z.jpg" alt="Linux victim to Linux attacker" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux Attacker -&amp;gt; Windows Victim &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This third scenario invovles the attacker having shell access on a Windows machine, and needing to transfer a file from their attcking Linux machine onto the compromised Windows host.&lt;/p&gt;

&lt;p&gt;In this scenario, the attacker is running the webserver with &lt;code&gt;SimpleHTTPServer&lt;/code&gt; module, and using the Windows binary, &lt;code&gt;certutil.exe&lt;/code&gt; to download &lt;code&gt;file.txt&lt;/code&gt; from the attacking host.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;certutil.exe&lt;/code&gt; is installed by default on Windows versions since Vista/Server 2008, but if we wanted to check for it's existence we could use the &lt;code&gt;where certutil.exe&lt;/code&gt; command from a cmd terminal, or &lt;code&gt;(get-command certutil.exe).Path&lt;/code&gt; in Powershell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft060m4ukldn4l6id8t0s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft060m4ukldn4l6id8t0s.jpg" alt="Linux Attacker to Windows Victim" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Windows Victim -&amp;gt; Linux Attacker &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The fourth and final scenario we'll look at, exfiltrating data &lt;strong&gt;from&lt;/strong&gt; a compromised Windows host, is not going to be viable in most CTFs or real-world situtations. This is because Windows Defender Firewall blocks all inbound traffic by default. That means that unless we can add a new firewall rule, or can take advantage of a misconfigured firewall, our attacking host won't actually be able to connect to the HTTP server on the compromised Windows host.&lt;/p&gt;

&lt;p&gt;For this example, I'm assuming those rules are already in place, and that the attacker has the necessary privilege to start and stop IIS.&lt;/p&gt;

&lt;p&gt;On the compromised Windows host, the attacker starts the IIS server with the &lt;code&gt;iisreset /start&lt;/code&gt; command. Then, from the attacking Linux host, the attacker uses &lt;code&gt;curl&lt;/code&gt; to download &lt;code&gt;file.txt&lt;/code&gt; from the victim machine back to the attacker.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiy7dpc06o3jz688wqayb.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiy7dpc06o3jz688wqayb.jpg" alt="Windows Victim to Linux Attacker" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Linux HTTP Server Options: &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The default port for HTTP traffic is port 80. When running a webserver on Port 80, most HTTP clients will assume that default port is being used, so it does not have to be specified. For example, &lt;code&gt;http://10.10.10.1/file.txt&lt;/code&gt; == &lt;code&gt;http://10.10.10.1:80/file.txt&lt;/code&gt;. But, &lt;code&gt;http://10.10.10.1/file.txt&lt;/code&gt; &lt;strong&gt;!=&lt;/strong&gt; &lt;code&gt;http://10.10.10.1:8000/file.txt&lt;/code&gt;. If you're using the non-default port, which in some instances will be unavoidable, just remember to specify the port in your HTTP client.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Python 3&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;Python 3&lt;/code&gt; we can use the built-in &lt;code&gt;http.server&lt;/code&gt; module to quickly and easily spin up an HTTP server. The syntax for the command is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; http.server
&lt;span class="c"&gt;# or python3 depending on how python is installed&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; http.server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;http.server&lt;/code&gt; module will default to using &lt;code&gt;port 8000&lt;/code&gt; and serve files from the current directory. If you want &lt;code&gt;http.server&lt;/code&gt; to use &lt;code&gt;port 80&lt;/code&gt;, or specify a directory to serve files from, you can use the &lt;code&gt;--directory&lt;/code&gt; flag, and supply an integer value for port, which is the only positional argument the module supports:&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;# Will serve files from /var/www/html on Port 80&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; http.server 80 &lt;span class="nt"&gt;--directory&lt;/span&gt; /var/www/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Python 2&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Similar to the &lt;code&gt;Python 3 http.server&lt;/code&gt; module, &lt;code&gt;Python 2&lt;/code&gt; has a module, &lt;code&gt;SimpleHTTPServer&lt;/code&gt; that can do the same thing. The sytax for that is:&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;# Will serve files from the current directory on Port 80&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; SimpleHTTPServer 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike the &lt;code&gt;http.server&lt;/code&gt; module, &lt;code&gt;SimpleHTTPServer&lt;/code&gt; does not support a flag to specify the directory to serve files from. If you want to achieve that result, you'll have to use &lt;a href="https://www.geeksforgeeks.org/pushd-command-in-linux-with-examples/" rel="noopener noreferrer"&gt;pushd&lt;/a&gt; and &lt;a href="https://www.geeksforgeeks.org/popd-command-in-linux-with-examples/?ref=gcse" rel="noopener noreferrer"&gt;popd&lt;/a&gt;:&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;# Will serve files from /var/www/html on Port 80&lt;/span&gt;
&lt;span class="nb"&gt;pushd&lt;/span&gt; /var/www/html&lt;span class="p"&gt;;&lt;/span&gt; python &lt;span class="nt"&gt;-m&lt;/span&gt; SimpleHTTPServer 80&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;popd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Apache&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Unlike many of the other options here, Apache can be and oftentimes is used as a production webserver. Because of this, Apache has many more features, but the configuration for those features is stored in separate files, mainly &lt;code&gt;/etc/apache2/apache2.conf&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Using Apache as an HTTP server for data exfil/infiltration, however, comes with the huge caveat that in most cases starting and stopping the service requires &lt;code&gt;sudo&lt;/code&gt;, aka root access. When you're serving files from your attacking host, that's not an issue, but if you're trying to exfiltrate data from a victim you might not have root access.&lt;/p&gt;

&lt;p&gt;Even if you don't have permissions to start/stop apache on a victim, you might still be able to use Apache to exfiltrate files via HTTP if you have write access to the directory that Apache is serving files from. By default this will be the &lt;code&gt;/var/www/html/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting Apache on Kali:&lt;/strong&gt;&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;# Will start an apache server with the options from /etc/apache2/apache2.conf&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;service apache2 start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stopping Apache on Kali:&lt;/strong&gt;&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;# Will stop an already running apache server&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;service apache2 stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Nginx&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Like Apache, Ngninx is also a production webserver that has a large market share. The default webroot for Nginx will vary depending on the OS and version that is installed, but some common Nginx webroots are as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/var/www/html
/usr/share/nginx/www
/usr/share/nginx/html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nginx for data exfiltration comes with the same caveats as Apache. If you're trying start or make changes to a running Nginx server on a compromised victim, you'll likely need root access. Otherwise, if you have write permissions to the webroot of an already running Nginx instance on a victim, you can still exfil data that way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Starting Nginx on Kali:&lt;/strong&gt;&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;# Will start an nginx server&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;service nginx start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Stopping Nginx on Kali:&lt;/strong&gt;&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;# Will stop an already running nginx server&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;service nginx stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;simple-http-server&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/TheWaWaR/simple-http-server" rel="noopener noreferrer"&gt;simple-http-server&lt;/a&gt; is a cli based HTTP server written in Rust that has pre-compiled binaries for Linux, Windows and MacOS hosts. It's highly unlikely that this binary will be pre-installed on a compromised victim, but since it has no dependencies, if you can infiltrate the binary onto a victim host you can easily use it to run an HTTP server.&lt;/p&gt;

&lt;p&gt;If you use one of the pre-compiled binaries, the start command will match the filename, otherwise the general syntax is as follows.&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;# Will start an http server on port 80&lt;/span&gt;
./simple-http-server &lt;span class="nt"&gt;-p&lt;/span&gt; 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Linux HTTP Client Options: &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The list I'm including here is by no means comprehensive. While I'm only listing some of the most commonly available methods / programs, there might be some instances where none of these programs are available, or their usage is restricted.&lt;/p&gt;

&lt;p&gt;A great resource for finding programs that might grant you download capabilites via programs unbeknowst to sys admins is &lt;a href="https://gtfobins.github.io/#+file%20download" rel="noopener noreferrer"&gt;GTFOBins - File Downloads Filter&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;wget&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Many Linux systems will come pre-installed with &lt;code&gt;wget&lt;/code&gt;, making it a great choice for downloading files on Linux over HTTP. Downloading files with &lt;code&gt;wget&lt;/code&gt; is very easy:&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;# Will download file.txt and save as file.txt&lt;/span&gt;
wget http://10.10.10.2:8000/file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optionally, you can the &lt;code&gt;-O&lt;/code&gt; (capital letter O not zero) flag to specify the output filename.&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;# Will download file.txt and save as output-file.txt&lt;/span&gt;
wget &lt;span class="nt"&gt;-O&lt;/span&gt; output-file.txt http://10.10.10.2/file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;curl&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Another program that's very commonly found on Linux systems is &lt;code&gt;curl&lt;/code&gt;. Unlike &lt;code&gt;wget&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt; defaults to displaying the output in the terminal instead of downloading files, however, you can easily save files with the &lt;code&gt;--output&lt;/code&gt; flag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
curl http://10.10.10.2/file.txt &lt;span class="nt"&gt;--output&lt;/span&gt; file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Windows HTTP Server Options: &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;As mentioned above in Windows Victim -&amp;gt; Linux Attacker, using HTTP for data exfil on Windows isn't generally a great choice, but these options are included for general awareness.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;IIS&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;IIS, Internet Information Services, is a web server created by Microsoft for use with Windows hosts. Whenever you encounter Windows hosts serving HTTP, it's highly likely that they'll be using IIS. The default webroot for IIS is &lt;code&gt;C:\inetpub\wwwroot\&lt;/code&gt;. In order to start / stop an IIS server, you will need to be running as an Administrator, aka &lt;code&gt;NT Authority\System&lt;/code&gt; user.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Start IIS - cmd:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;iisreset /start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Start IIS - powershell:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Start-IISSite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Default Web Site"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Python 3&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;python 3&lt;/code&gt; is installed, you can use the &lt;code&gt;http.server&lt;/code&gt; module. Note the addition of the &lt;code&gt;--bind&lt;/code&gt; flag with the ip &lt;code&gt;0.0.0.0&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;python &lt;span class="nt"&gt;-m&lt;/span&gt; http.server &lt;span class="nt"&gt;--bind&lt;/span&gt; 0.0.0.0 80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Windows HTTP Client Options: &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Much like the Linux list, the list of Windows HTTP clients here also by no means comprehensive. Windows has its own version of GTFOBins in the form of &lt;a href="https://lolbas-project.github.io/#/download" rel="noopener noreferrer"&gt;LOLBAS&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Invoke-WebRequest - Powershell&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;If your Windows access is a powershell environment, or if you're in &lt;code&gt;cmd&lt;/code&gt; and you can spawn a powershell environment with &lt;code&gt;powershell.exe&lt;/code&gt;, you can use the built-in function &lt;code&gt;Invoke-WebRequest&lt;/code&gt; to download files to Windows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Downloads file.txt and saves it as file.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Invoke-WebRequest&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;http://10.10.10.2/file.txt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file.txt&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Certutil.exe - cmd / Powershell&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;certutil.exe&lt;/code&gt; is a legitimate Windows program to download Certificate authority files. However, we can misuse this legitimate program to download files from any HTTP server that we specify.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;certutil.exe -urlcache -split -f http://10.10.10.2/file.txt file.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Potential Firewall Issues &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;In the scenarios I described above, it is assumed that there is either no firewall, or that the firewall has rules in place that will allow us to run any of the HTTP server / clients without issue. In CTFs / the real world, you'll often run into firewalls that have block rules in place to stop the kinds of HTTP transfers described above.&lt;/p&gt;

&lt;p&gt;Firewall rules can be broken out into two distinct categories. Inbound rules and Outbound rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inbound&lt;/strong&gt; firewall rules control what happens when other computers try to access our local machine. For example, we could be running an HTTP webserver on port 80 locally accessible at &lt;code&gt;http://localhost:80&lt;/code&gt;, but if we have a firewall block rule on port 80 in place, no other computers will be able to access that HTTP webserver.&lt;/p&gt;

&lt;p&gt;Windows Defender Firewall blocks all inbound traffic by default, and exceptions for individual programs / services are made from there. This is part of the reason why HTTP isn't a great choice for data exfiltration on Windows.&lt;/p&gt;

&lt;p&gt;While Linux distros include a wide number of firewall programs available, they often won't be enabled by default or won't have restrictive inbound rules enabled by default like Windows does. This makes HTTP an easy way to quickly exfiltrate data from Linux systems, just remember the opsec concerns from earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Outbound&lt;/strong&gt; firewall rules tell the computer what to do when our local computer tries to access computers and resources on other computers. This can be other computers on our local network, or even websites out on the internet. You'll occasionally run into instances where outbound traffic on most ports has been blocked by firewall rules. However, HTTP traffic on port 80, and HTTPS traffic on port 443 are often allowed outbound rules, otherwise the computer wouldn't be able to access the internet.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why you should ALWAYS use return before res.send in Express APIs and Applications</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Tue, 10 Jan 2023 20:33:52 +0000</pubDate>
      <link>https://forem.com/adamkatora/why-you-should-always-use-return-before-ressend-in-express-apis-and-applications-k9k</link>
      <guid>https://forem.com/adamkatora/why-you-should-always-use-return-before-ressend-in-express-apis-and-applications-k9k</guid>
      <description>&lt;p&gt;So, before I get eaten alive in the comments, let me just say that using the word "&lt;strong&gt;&lt;em&gt;ALWAYS&lt;/em&gt;&lt;/strong&gt;" in the title of this article is definitely a little bit of click-bait. If there's one thing I've found to be true in software development, nearly every "rule" has a specific edge case where it can and should be broken.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmeqmjktlmyhytxowqmw7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmeqmjktlmyhytxowqmw7.gif" alt="Only a SITH deals in absolutes" width="400" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what I've also found is that these coding "rules" &lt;em&gt;can&lt;/em&gt; be very beneficial, especially when their usage helps programmers to avoid errors, increase code readability, and have an all-around better understanding of their program's control flow.&lt;/p&gt;

&lt;p&gt;And for most Express.js developers, especially new ones, I think the &lt;code&gt;return res.send()&lt;/code&gt; rule can achieve just that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bug-Free Software Corp - An Expres.js App
&lt;/h2&gt;

&lt;p&gt;To demonstrate, I've written an (intentionally bad) Express.js application called "Bug-Free Software Corp". The application only has one endpoint &lt;code&gt;/login&lt;/code&gt; which accepts a single input, &lt;code&gt;password&lt;/code&gt;, for the single user, &lt;code&gt;admin&lt;/code&gt;. The user is stored in the &lt;code&gt;database.json&lt;/code&gt; file, a simple flat-file key-value database.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Side note:&lt;/strong&gt; For brevity and demonstration purposes, I'm not hashing the password in the "database". You probably already know, but I'd be remiss without saying, if you are writing a real application &lt;a href="https://auth0.com/blog/hashing-passwords-one-way-road-to-security/" rel="noopener noreferrer"&gt;ALWAYS salt and hash your passwords!&lt;/a&gt; That is one rule that should &lt;strong&gt;&lt;em&gt;actually&lt;/em&gt;&lt;/strong&gt; never be broken.&lt;/p&gt;




&lt;p&gt;When a user makes a &lt;code&gt;POST&lt;/code&gt; request to &lt;code&gt;/login&lt;/code&gt; our application checks if the supplied password matches the admin password stored in the database, and if it does it sends back a success message. When the user makes that same request, but with an invalid password, our application sends back an invalid warning message.&lt;/p&gt;

&lt;p&gt;If you'd like to follow along, you can clone the &lt;a href="https://github.com/akatora28/express-js-return-res-example" rel="noopener noreferrer"&gt;git repository here&lt;/a&gt;, otherwise, I've included the code below, the program is short enough you can just read along.&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;express&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;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;JSONdb&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;simple-json-db&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;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;JSONdb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;database.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&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;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="c1"&gt;// Get the password from the POST request body&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputPassword&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;

  &lt;span class="c1"&gt;// Check if user supplied password == the database stored password&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputPassword&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&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="s2"&gt;password&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Login Successful!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Welcome, Admin.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Wrong password!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Acess denied!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Bug tracker counter - Will reset if code gets down to here,&lt;/span&gt;
  &lt;span class="c1"&gt;// but this should never run because we used res.send()... right?!?!&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bugTracker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="nx"&gt;app&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="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;`Bug-Free Software Corp. App listening on port 3000!`&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;`It's been &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bugTracker&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; days since we've had a bug!`&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;You probably already see the glaring error, but just humor me and follow along.&lt;/p&gt;

&lt;p&gt;Let's test the application by running &lt;code&gt;npm start&lt;/code&gt; from within the project directory, you should see output from nodemon and Express that the app is running on port 3000, as well the number of days since a bug, which is pulled from &lt;code&gt;database.json&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;&lt;span class="nv"&gt;$ &lt;/span&gt;npm run start
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; bug-free-software-corp@1.0.0 start
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nodemon index.js

&lt;span class="o"&gt;[&lt;/span&gt;nodemon] 2.0.20
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] to restart at any &lt;span class="nb"&gt;time&lt;/span&gt;, enter &lt;span class="sb"&gt;`&lt;/span&gt;rs&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] watching path&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;: &lt;span class="k"&gt;*&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] watching extensions: js,mjs,json
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] starting &lt;span class="sb"&gt;`&lt;/span&gt;node index.js&lt;span class="sb"&gt;`&lt;/span&gt;
Bug-Free Software Corp. App listening on port 3000!
It&lt;span class="s1"&gt;'s been 365 days since we'&lt;/span&gt;ve had a bug!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using &lt;code&gt;curl&lt;/code&gt; (or Postman) we can make a &lt;code&gt;POST&lt;/code&gt; request to &lt;code&gt;http://localhost:3000/login&lt;/code&gt;. If you cloned the repo, I have two npm scripts &lt;code&gt;npm run curl-right-password&lt;/code&gt; and &lt;code&gt;npm run curl-wrong-password&lt;/code&gt;, otherwise here's the curl 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;# correct password&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"password": "P@ssw0rd!"}'&lt;/span&gt;
&lt;span class="c"&gt;# wrong password&lt;/span&gt;
curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"password": "Wr0ngP@ssw0rd!"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run the right password:&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;npm run curl-right-password
Login Successful!
Welcome, Admin.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the wrong:&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;npm run curl-wrong-password
Wrong password!
Acess denied!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To the end-user, it &lt;strong&gt;&lt;em&gt;looks&lt;/em&gt;&lt;/strong&gt; like everything is running correctly. But, looking back at our terminal that's running our application, we can see that nodemon restarted because the &lt;code&gt;database.json&lt;/code&gt; file changed. It was the bug tracker...&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="o"&gt;[&lt;/span&gt;nodemon] restarting due to changes...
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] starting &lt;span class="sb"&gt;`&lt;/span&gt;node index.js&lt;span class="sb"&gt;`&lt;/span&gt;
Bug-Free Software Corp. App listening on port 3000!
It&lt;span class="s1"&gt;'s been 0 days since we'&lt;/span&gt;ve had a bug!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happened was that even though our if/else statement always hit a condition that executed a &lt;code&gt;res.send()&lt;/code&gt; call, the function continued to execute. In Express, calling the &lt;code&gt;res.send()&lt;/code&gt; method &lt;strong&gt;&lt;em&gt;only&lt;/em&gt;&lt;/strong&gt; sends a response to the client, it does not exit the current function.&lt;/p&gt;

&lt;p&gt;In an application of this size, it's very easy to spot the error before it occurs. But, in larger more complex applications it's very easy to imagine a situation where this could get overlooked leading to errors in the code.&lt;/p&gt;

&lt;p&gt;Fortunately, in this case the fix is simple. I even included it in the title. The only update we'd need to make to our code is to append a &lt;code&gt;return&lt;/code&gt; statement before the &lt;code&gt;res.send()&lt;/code&gt; calls.&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputPassword&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;admin&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="s2"&gt;password&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="c1"&gt;// Added `return` to stop further code execution&lt;/span&gt;
    &lt;span class="k"&gt;return&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Login Successful!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Welcome, Admin.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&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="c1"&gt;// Added `return` to stop further code execution&lt;/span&gt;
    &lt;span class="k"&gt;return&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;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Wrong password!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Acess denied!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&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;If you make the above changes to index.js, and re-run the curl commands, you'll find that the program executes without ever reaching the code to reset the bug-tracker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;Earlier I mentioned that most programming rules have instances when they should be broken, and after having spent the entirety of this article telling you why you &lt;strong&gt;&lt;em&gt;should&lt;/em&gt;&lt;/strong&gt; always follow this rule, I'd like to provide 2 potential reasons why you &lt;strong&gt;&lt;em&gt;shouldn't&lt;/em&gt;&lt;/strong&gt; always follow this rule. (And, why I think those reasons are wrong but we'll get to that in a minute)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason 1.&lt;/strong&gt; In some applications, you &lt;strong&gt;&lt;em&gt;do&lt;/em&gt;&lt;/strong&gt; want code to continue executing after you've already sent a response to the client.&lt;/p&gt;

&lt;p&gt;This is especially true of applications that do heavy processing workloads, take Youtube for example. If you uploaded a large video to Youtube, after the upload was complete, Youtube's platform still has to process the video and convert it into multiple formats for streaming.&lt;/p&gt;

&lt;p&gt;From an application and user-experience standpoint, it makes no sense to keep the single HTTP request alive for the duration of processing and transcoding those video files. Instead, the application just needs to send back a &lt;code&gt;200&lt;/code&gt; status code after the upload completes, letting the user know the upload went smoothly and the application will handle everything else from there.&lt;/p&gt;

&lt;p&gt;Even in these instances though, I'd still make the argument that it's better practice to use job queues and background processing to run that code, and instead send back a &lt;code&gt;200&lt;/code&gt; status code to the user confirming that the job was scheduled successfully.&lt;/p&gt;

&lt;p&gt;I'm hoping to write about job queues and background processing in node.js / Express.js soon, so I'll skip over going into any more detail here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reason 2.&lt;/strong&gt; The return value of &lt;code&gt;res.send()&lt;/code&gt; is undefined, so using &lt;code&gt;return res.send()&lt;/code&gt; makes the code less clear. Instead, you should move the &lt;code&gt;return&lt;/code&gt; statement to the line immediately following &lt;code&gt;res.send()&lt;/code&gt; ie:&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Success!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While it is correct that the return &lt;strong&gt;&lt;em&gt;value&lt;/em&gt;&lt;/strong&gt; of &lt;code&gt;res.send()&lt;/code&gt; isn't important to the program, I still think that consolidating this code to 1 line as opposed to 2 makes the code more readable and easier to understand. After all, that was the whole point of this "rule" anyway.&lt;/p&gt;

&lt;p&gt;If you have any differing opinions on how to structure your Express.js code or think I'm wrong anywhere I'd welcome any opinions in the comments. Thanks for reading!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Creating an HLS VOD (Video on Demand) Streaming Platform with Typescript, AdonisJS, and AWS</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Wed, 25 May 2022 21:36:07 +0000</pubDate>
      <link>https://forem.com/adamkatora/creating-an-hls-vod-video-on-demand-streaming-platform-with-typescript-adonisjs-and-aws-59gg</link>
      <guid>https://forem.com/adamkatora/creating-an-hls-vod-video-on-demand-streaming-platform-with-typescript-adonisjs-and-aws-59gg</guid>
      <description>&lt;p&gt;Example repo &lt;a href="https://github.com/akatora28/adonis-vod-example" rel="noopener noreferrer"&gt;here:&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A while back, I wrote a &lt;a href="https://dev.to/adamkatora/creating-a-vod-video-on-demand-platform-with-rails-ffmpeg-34m9"&gt;version of this article&lt;/a&gt; using Ruby on Rails. While I was happy with how it turned out, at heart I'm a node developer. Because of that, I decided to redo that project in my language of choice.  &lt;/p&gt;

&lt;p&gt;I'll also be using &lt;a href="https://adonisjs.com/" rel="noopener noreferrer"&gt;AdonisJS&lt;/a&gt; as my backend framework instead of Express. I've found the AdonisJS framework has a lot of the conventions and features that I enjoyed about Rails, but within the JS ecosystem.  &lt;/p&gt;

&lt;h2&gt;
  
  
  What We'll Be Building:
&lt;/h2&gt;

&lt;p&gt;At the end of this project, we'll have a simple webapp that is capable of doing the following:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Uploading user submitted &lt;code&gt;.mp4&lt;/code&gt; files to an S3 bucket
&lt;/li&gt;
&lt;li&gt;Transcoding those &lt;code&gt;.mp4&lt;/code&gt; files into an HLS playlist for streaming
&lt;/li&gt;
&lt;li&gt;Serving those HLS videos via Cloudfront CDN &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since we'll be using S3 and Cloudfront, you will need an AWS account. However, the AWS charges of those two services should be nominal given our current use case.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started:
&lt;/h2&gt;

&lt;p&gt;Start by running the following command to initialize a new AdonisJs app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init adonis-ts-app@latest adonis-vod
&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4iiwfsnzh2bd8gtkgpm7.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4iiwfsnzh2bd8gtkgpm7.png" alt="Starting a new Adonis project" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Select the following settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;CUSTOMIZE PROJECT
❯ Select the project structure · web
❯ Enter the project name · adonis-vod
❯ Setup eslint? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt; · &lt;span class="nb"&gt;false&lt;/span&gt;
❯ Configure webpack encore &lt;span class="k"&gt;for &lt;/span&gt;compiling frontend assets? &lt;span class="o"&gt;(&lt;/span&gt;y/N&lt;span class="o"&gt;)&lt;/span&gt; · &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the cli tool finishes running, your new Adonis app will be setup in the &lt;code&gt;adonis-vod&lt;/code&gt; directory. Open that up in your IDE of choice.  &lt;/p&gt;

&lt;p&gt;Next, we'll need to install Lucid, Adonis' default ORM, so that we can create models and interact with our database.  &lt;/p&gt;

&lt;p&gt;Run the following to install Lucid.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @adonisjs/lucid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, after npm completes installation, configure Lucid by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node ace configure @adonisjs/lucid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select &lt;code&gt;SQLite&lt;/code&gt; as a database driver, and for 'Select where to display instructions...' choose 'In the terminal'.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ node ace configure @adonisjs/lucid
❯ Select the database driver you want to use …  Press &amp;lt;SPACE&amp;gt; to &lt;span class="k"&gt;select&lt;/span&gt;
◉ SQLite
◯ MySQL / MariaDB
◯ PostgreSQL
◯ OracleDB
◯ Microsoft SQL Server

...

❯ Select where to display instructions …  Press &amp;lt;ENTER&amp;gt; to &lt;span class="k"&gt;select
  &lt;/span&gt;In the browser
❯ In the terminal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the cli output, the only variable we'll worry about right now is the &lt;code&gt;DB_CONNECTION&lt;/code&gt; env variable.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;env.ts&lt;/code&gt; and edit it to look like the following:  &lt;/p&gt;

&lt;p&gt;(Note the addition of &lt;code&gt;DB_CONNECTION&lt;/code&gt; at the bottom of the file)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ioc:Adonis/Core/Env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;APP_KEY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;APP_NAME&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;DRIVE_DISK&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;development&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;production&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;test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;DB_CONNECTION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&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;If you're not familiar with AdonisJS, the &lt;code&gt;env.ts&lt;/code&gt; is NOT our .env file, but rather a file that checks the existence of the required env vars before starting our server.  &lt;/p&gt;

&lt;p&gt;If you open up the &lt;code&gt;.env&lt;/code&gt; file, you should see&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_CONNECTION=sqlite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;on line 8, which is our actual env variable to select SQLite as our database connection.  &lt;/p&gt;

&lt;p&gt;The configuration settings for our SQLite database are stored in the &lt;code&gt;config/database.ts&lt;/code&gt; file, but we don't need to make any modifications for our app to function.  &lt;/p&gt;

&lt;p&gt;Next, we'll install the S3 driver for AdonisJS' Drive, this will allow us to upload and store our video files in AWS S3. Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i @adonisjs/drive-s3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that finishes installing, run the configure command as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node ace configure @adonisjs/drive-s3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once again, select "In the terminal" to display instructions. This time we will want to make updates to both the env variable names, and to the configuration settings.  &lt;/p&gt;

&lt;p&gt;First open &lt;code&gt;.env&lt;/code&gt; and change the newly created S3 env variables to be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_ACCESS_KEY=dummyKey
AWS_SECRET_KEY=dummySecret
S3_BUCKET=dummyBucket
S3_REGION=dummyRegion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we're changing the variable names from &lt;code&gt;S3_KEY&lt;/code&gt; and &lt;code&gt;S3_SECRET&lt;/code&gt; to &lt;code&gt;AWS_ACCESS_KEY&lt;/code&gt; and &lt;code&gt;AWS_SECRET_KEY&lt;/code&gt;, since we'll be using Cloudfront as well. This name change isn't technically neccessary, but I prefer to be clear that the AWS credentials we'll be using are for more than just S3.  &lt;/p&gt;

&lt;p&gt;You can also delete the &lt;code&gt;S3_ENDPOINT&lt;/code&gt; variable since we won't be using that.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;.env&lt;/code&gt; file updated, open up &lt;code&gt;config/drive.ts&lt;/code&gt;, and scroll down until you see the commented out S3 Driver settings. Uncomment the s3 driver configuration, and update the settings to be the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- This is VERY important&lt;/span&gt;
  &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS_ACCESS_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;AWS_SECRET_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;S3_REGION&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;S3_BUCKET&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(If you see a typescript error with the &lt;code&gt;driver&lt;/code&gt; property after installing and configuring @adonisjs/drive-s3 you might need to Restart your Typescript Server)&lt;/p&gt;

&lt;p&gt;Take note that we updated the key, and secret env variables names from the defaults to the new ones. We also changed the setting &lt;code&gt;visibility&lt;/code&gt; from 'public' to 'private'. If you don't change that variable you will run into errors when trying to upload to s3.  &lt;/p&gt;

&lt;p&gt;We also need to update our &lt;code&gt;local&lt;/code&gt; drive settings in &lt;code&gt;drive.ts&lt;/code&gt; update the settings to the following&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;visibility&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;private&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tmpPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="nx"&gt;serveFiles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;basePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we went ahead and set up our environment variables for AWS, we should handle setting up our S3 bucket, Cloudfront Distribution, and IAM user as well. &lt;/p&gt;

&lt;p&gt;Start by logging into the AWS Console. From there, type 'IAM' in the top search bar, then click the result to open the "Identity and Access Management" dashboard.&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%2Fuploads%2Farticles%2F37l5yd1bhoucqdca56a9.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F37l5yd1bhoucqdca56a9.png" alt="Find IAM in AWS Console" width="800" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the dashboard, select "users" on the left-hand navbar, then click the blue "Add users" button.&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%2Fuploads%2Farticles%2Fnuyphm3gekmx384197r9.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuyphm3gekmx384197r9.png" alt="Add IAM Users" width="800" height="228"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Type in a name for your user, then select "Access key - Programmatic access" for the credential type. Click "Next: permissions" to continue. For permissions&lt;/p&gt;

&lt;p&gt;For simplicity, we'll be using "Attach existing policies directly" with the policies: &lt;code&gt;AmazonS3FullAccess&lt;/code&gt; and &lt;code&gt;CloudFrontFullAccess&lt;/code&gt;, but in a production app you would probably want to use a more restrictive policy following principle of least privilege.&lt;/p&gt;

&lt;p&gt;Simply check the box next to each Policy to attach it to our new user.&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%2Fuploads%2Farticles%2Frn468t9wtwhmliajpqpp.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frn468t9wtwhmliajpqpp.png" alt="Image description" width="800" height="544"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Next: Tags" and skip this page by clicking "Next:Review" at the bottom right hand corner of the screen. As long as your user has the two permission policies from above listed under the "Permissions summary" you're all set to click "Create User"&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%2Fuploads%2Farticles%2Fqgd9p1exkfanisr76p1v.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgd9p1exkfanisr76p1v.png" alt="Image description" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next screen will show you your credentials for the newly created user. As the page mentions, this is the last time AWS will show you these credentials, so it's best to download them as a CSV and store the file securely for future reference.  &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%2Fuploads%2Farticles%2Fbjiwezzxulp3lkzjbpb7.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjiwezzxulp3lkzjbpb7.png" alt="Download IAM credentials" width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With our IAM user created, we can take those credentials from the CSV file and add them to our &lt;code&gt;.env&lt;/code&gt; file. The CSV column &lt;code&gt;Access key ID&lt;/code&gt; corresponds to our &lt;code&gt;AWS_ACCESS_KEY&lt;/code&gt; variable and &lt;code&gt;Secret access key&lt;/code&gt; to &lt;code&gt;AWS_SECRET_KEY&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Next, we'll create an S3 bucket to store our uploaded videos. In the same search bar you used to find 'IAM' start searching for 'S3' to open the 'S3' dashboard.&lt;/p&gt;

&lt;p&gt;Click the orange "Create bucket" button to open up the bucket creation panel. You'll need to provide your bucket a globally unique name, but other than that, we won't need to change any other settings, so scroll to the bottom of the page and click the "Create bucket" button to finish creating your new bucket. &lt;/p&gt;




&lt;p&gt;A quick note about S3 Buckets: By default, when you create a new Bucket the setting "Block &lt;strong&gt;&lt;em&gt;all&lt;/em&gt;&lt;/strong&gt; public access" will be set to true. Even though we will want our users to be able to access the media we store in our S3 bucket, we &lt;strong&gt;&lt;em&gt;don't&lt;/em&gt;&lt;/strong&gt; want users accessing files directly from S3. &lt;/p&gt;

&lt;p&gt;Instead, we'll use a CloudFront CDN Distribution to serve our S3 files. This gives us 2 main benefits: 1 - our media is served from whichever CloudFront server is closest to the requesting user, giving our app faster speeds, 2 - It's cheaper to serve our media from CloudFront than it is from S3.  &lt;/p&gt;




&lt;p&gt;Update the last 2 env variables &lt;code&gt;S3_BUCKET&lt;/code&gt; and &lt;code&gt;S3_REGION&lt;/code&gt; with the correct options for the S3 bucket you just created. For S3 bucket, you simply need the name, not the ARN.&lt;/p&gt;

&lt;p&gt;Finally, let's setup our CloudFront distribution. Open up the CloudFront Dashboard the same way we did with IAM and S3. Click, "Create Distribution".&lt;/p&gt;

&lt;p&gt;On the Create Distribution dashboard, the first setting we'll need to update is the "Origin domain", in our case, the origin should be the S3 bucket you just created.&lt;/p&gt;

&lt;p&gt;In the "Choose origin domain" search dropdown, you should be able to see your newly created bucket listed. Choose that.  &lt;/p&gt;

&lt;p&gt;The "Name" field should populate with a default value after selecting your Bucket.&lt;/p&gt;

&lt;p&gt;For "S3 bucket access" select "Yes use OAI (bucket can restrict access to only CloudFront)". Under "Origin access identitiy" there should be a default option for you to choose. Then finally under "Bucket policy" select "Yes, update the bucket policy".&lt;/p&gt;

&lt;p&gt;You'll also need to update the Response headers policy to use the "CORS-with-preflight-and-SecurityHeadersPolicy" so that we don't run into any CORS issues.&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%2Fuploads%2Farticles%2F5x2e49i9c2cnnlnf80zj.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5x2e49i9c2cnnlnf80zj.png" alt="Setting up CloudFront" width="800" height="754"&gt;&lt;/a&gt;&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%2Fuploads%2Farticles%2Fqy3doh6voeii6qv1xrk4.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqy3doh6voeii6qv1xrk4.png" alt="CloudFront CORS" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In a later installment, we'll configure our CloudFront Distribution to use Signed Cookies to limit media access to only our app users, but for now these settings are enough to get us up and running. Scroll to the very bottom and click "Create Distribution"&lt;/p&gt;

&lt;p&gt;The final configuration step we need to take is adding our "tmp" directory to the &lt;code&gt;exclude&lt;/code&gt; array in our &lt;code&gt;tsconfig.json&lt;/code&gt; file. Since our application will be generating new files (.ts &amp;amp; .m3u8) into the tmp dir, we want to exclude that dir from being watched to automatically restart the server on file changes.&lt;/p&gt;

&lt;p&gt;Open &lt;code&gt;tsconfig.json&lt;/code&gt; and update the exclude array to look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exclude"&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="s2"&gt;"node_modules"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"tmp"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you forget to add "tmp" to &lt;code&gt;exclude&lt;/code&gt; you'll see your dev server restarting a number of times while the transcode function we'll be implementing later is running.&lt;/p&gt;

&lt;p&gt;With those configuration steps out of the way, we can start building out our app.  &lt;/p&gt;

&lt;p&gt;Run the following two commands to generate a database migration and a model for our Video object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node ace make:migration videos
node ace make:model Videos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that AdonisJS will handle changing word between singular or plural automatically, so &lt;code&gt;node ace make:model Videos&lt;/code&gt; becomes &lt;code&gt;app/Models/Video.ts&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;database/migrations/[timestamp]_videos.ts&lt;/code&gt; file, and update the &lt;code&gt;public async up()&lt;/code&gt; method to look like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;up &lt;/span&gt;&lt;span class="p"&gt;()&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="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTable&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="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&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;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;original_video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hls_playlist&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * Uses timestamptz for PostgreSQL and DATETIME2 for MSSQL
     */&lt;/span&gt;
    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;created_at&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="na"&gt;useTz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;updated_at&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="na"&gt;useTz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;p&gt;In the above code, we are adding 3 columns to our database table: &lt;strong&gt;name&lt;/strong&gt; - which is the name of our video, &lt;strong&gt;original_video&lt;/strong&gt; - which will store a reference to path of our original video file in s3, and &lt;strong&gt;hls_playlist&lt;/strong&gt; - which will store a reference to the hls playlist file in s3 that our app will generate and upload.  &lt;/p&gt;

&lt;p&gt;Pay attention to the fact that for database column names in migration files, AdonisJS uses snake_case convention.  &lt;/p&gt;

&lt;p&gt;Next, open &lt;code&gt;app/Models/Video.ts&lt;/code&gt; and update the class definition to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Video&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseModel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;column&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;isPrimary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;originalVideo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;hlsPlaylist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;column&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="na"&gt;autoCreate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;column&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="na"&gt;autoCreate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;autoUpdate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;updatedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above code, you'll notice that we're adding the same database columns that we added in the migration, except this time as properties on our Video class, and in camelCase instead of snake_case.  &lt;/p&gt;

&lt;p&gt;With our databse model and migration setup, we're almost ready to start using our database in our app. Before we can do so though, we need to "apply" our migration to our database. If you're coming from a MongoDB or NoSQL background, the concept of migrations might be new to you, but fortunatley applying database migrations is easy, run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node ace migration:run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The above command will "apply" that migration by creating the tables and columns specified in our migration file, and setting them to the correct data type within the database. If you want to read up a little more on migrations, &lt;a href="https://docs.adonisjs.com/guides/database/migrations" rel="noopener noreferrer"&gt;here's the AdonisJS docs on the topic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now that our database migrations have been run, we can write our Controller, so that we can actually perform CRUD operations on our Video model. Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node ace make:controller Videos
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That will automatically generate an &lt;code&gt;app/Controllers/Http/VideosController.ts&lt;/code&gt; file for you. We'll be adding 4 methods to our Videos controller so that we can create new videos, upload &amp;amp; transcode them, then watch back our videos via HLS streaming. The 5 methods will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;index()&lt;/strong&gt; - List all the videos in our app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;create()&lt;/strong&gt; - Render the video upload page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;store()&lt;/strong&gt; - Save a new video&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;show()&lt;/strong&gt; - Watch a single video&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For our &lt;code&gt;VideosController&lt;/code&gt; we'll also need to install a few more dependencies. Run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @ffmpeg-installer/ffmpeg @ffprobe-installer/ffprobe hls-transcoder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(Full Disclosure: &lt;a href="https://www.npmjs.com/package/hls-transcoder" rel="noopener noreferrer"&gt;hls-transcoder&lt;/a&gt; is a package I maintain)&lt;/p&gt;

&lt;p&gt;Below is the implementation of the &lt;code&gt;VideosController&lt;/code&gt; class. The only thing you'll need to update is &lt;code&gt;{url: 'YOUR-CLOUDFRONT-URL-HERE'}&lt;/code&gt; in the &lt;code&gt;show()&lt;/code&gt; method, line 29. And as the comment mentions, don't include the protocol, ie &lt;code&gt;https://&lt;/code&gt;. This could be moved to an env variable, but for our purposes hard-coding is fine and not really a security risk in this instance.  &lt;/p&gt;

&lt;p&gt;Otherwise, feel free to copy / past the Video Controller code from below, and I'll briefly explain what each method does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpContextContract&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ioc:Adonis/Core/HttpContext&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ioc:Adonis/Core/Application&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Drive&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ioc:Adonis/Core/Drive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ioc:Adonis/Core/Logger&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@ffmpeg-installer/ffmpeg&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;ffprobe&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;@ffprobe-installer/ffprobe&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Video&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;App/Models/Video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Transcoder&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hls-transcoder&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;


&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VideosController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;HttpContextContract&lt;/span&gt;&lt;span class="p"&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;videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videos/index.edge&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;videos&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;HttpContextContract&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="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videos/create.edge&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="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;HttpContextContract&lt;/span&gt;&lt;span class="p"&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;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findByOrFail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;cloudfront&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR-CLOUDFRONT-URL-HERE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- Put your cloudfront url here DON'T include https://&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;view&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videos/show.edge&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;video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cloudfront&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;store&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;HttpContextContract&lt;/span&gt;&lt;span class="p"&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;videoFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;videoFile&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="c1"&gt;// Since id is generated at the database level, we can't use video.id before video is created&lt;/span&gt;
    &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalVideo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`uploads/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/original.mp4`&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;videoFile&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;moveToDisk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`uploads/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`original.mp4`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;await&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;transcodeVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/videos&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="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;transcodeVideo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;local&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Drive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&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;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Drive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;s3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Get FileBuffer from S3&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoFileBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;originalVideo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Save S3 file to local tmp dir&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`transcode/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/original.mp4`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;videoFileBuffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// Get reference to tmp file&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tmpVideoPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tmpPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`transcode/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/original.mp4`&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;transcoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Transcoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;tmpVideoPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tmpPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`transcode/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;span class="na"&gt;ffmpegPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ffmpeg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;ffprobePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ffprobe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Log transcoder progress status&lt;/span&gt;
    &lt;span class="nx"&gt;transcoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;progress&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;progress&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;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;progress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c1"&gt;// Run the transcoding&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;transcoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// After transcoding, upload files to S3&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tmpPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`transcode/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&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="nx"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.ts&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.m3u8&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`transcode/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;file&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`uploads/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;file&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;fileStream&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;// Then, clean up our tmp/ dir&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rmSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tmpPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`transcode/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;span class="na"&gt;recursive&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hlsPlaylist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`uploads/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/index.m3u8`&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&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="nf"&gt;resolve&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;p&gt;With that code added, let me briefly explain what we're doing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;index()&lt;/code&gt; - The index method queries our database for every video, then renders the &lt;code&gt;videos/index.edge&lt;/code&gt; template with the array of &lt;code&gt;videos&lt;/code&gt; passed into the template as state.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;create()&lt;/code&gt; - The create method returns the view with our form to upload and create new video objects.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;show()&lt;/code&gt; - The show method, which accepts a video id as a url parameter, queries our database to find that video by the supplied id, and renders the &lt;code&gt;videos/show.edge&lt;/code&gt; template with both the video, and the url of our CloudFront distribution passed into the template as state.&lt;/p&gt;

&lt;p&gt;The above 3 methods should be familiar if you've used frameworks like Rails or Laravel before. The 4th method &lt;code&gt;store()&lt;/code&gt; is also used in Rails and Laravel conventions, but we're adding a good bit of custom functionality here, so let's walk through that in more depth.&lt;/p&gt;

&lt;p&gt;First, our &lt;code&gt;store()&lt;/code&gt; method accepts 2 inputs from the request, &lt;code&gt;videoFile&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;. Next, we use the &lt;code&gt;Video&lt;/code&gt; model class to create a new video object, and we pass in the &lt;code&gt;name&lt;/code&gt; variable. Since we'll be using the &lt;code&gt;video id&lt;/code&gt; in our transcoding and storage PATHs we can't instantiate the new video object with the &lt;code&gt;originalVideo&lt;/code&gt; or &lt;code&gt;hlsPlaylist&lt;/code&gt; properties just yet.  &lt;/p&gt;

&lt;p&gt;After creating the new video, we can call&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;video.originalVideo = `uploads/${video.id}/original.mp4`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
, to set that property (It doesn't save to the database until we call the .save() method though!)&lt;/p&gt;

&lt;p&gt;Then, we use the &lt;code&gt;.moveToDisk&lt;/code&gt; method from Drive, to upload our user's video to S3 for storage.  &lt;/p&gt;

&lt;p&gt;In the next line, we call the private &lt;code&gt;this.transcodeVideo()&lt;/code&gt; method, and pass in our &lt;code&gt;video&lt;/code&gt; as a parameter. Let's walk through what this method does.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;transcodeVideo()&lt;/code&gt; gets references to the &lt;code&gt;local&lt;/code&gt; and &lt;code&gt;s3&lt;/code&gt; Drives. Then, using the &lt;code&gt;s3&lt;/code&gt; Drive, we find the file we just uploaded by using the &lt;code&gt;video.originalVideo&lt;/code&gt; property we just set, and save a reference to the File Buffer.&lt;/p&gt;

&lt;p&gt;Then, using the &lt;code&gt;local&lt;/code&gt; Drive, we store that file in our tmp directory to use for transcoding. If it seems redundant to upload the file to s3 then download the file locally, it kind of is, except setting up our transcoder this way makes it significantly easier to move our transcoding to a background job if we decide to implement a Job Queue later on.&lt;/p&gt;

&lt;p&gt;With the original video file saved to our tmp dir, we then pass that file to our transcoder, and call the &lt;code&gt;transcoder.transcode()&lt;/code&gt; method. The transcoder will emit &lt;code&gt;progress&lt;/code&gt; events everytime &lt;code&gt;ffmpeg&lt;/code&gt; updates us on our transcoding progress, so I've included &lt;code&gt;Logger.info&lt;/code&gt; calls to console log that status as it comes through. Our frontend is very bare, so we won't be getting any progress updates or feedback on the frontend.&lt;/p&gt;

&lt;p&gt;Finally, after the transcoder finishes running, we loop over the Transcoder's output directory and upload the new &lt;code&gt;.ts&lt;/code&gt; and &lt;code&gt;.m3u8&lt;/code&gt; files needed for HLS playback. After the files are uploaded, we remove the files in the &lt;code&gt;/tmp&lt;/code&gt; dir, then set the &lt;code&gt;hlsPlaylist&lt;/code&gt; property on our video object before calling the &lt;code&gt;video.save()&lt;/code&gt; method to write those property updates to the database.&lt;/p&gt;

&lt;p&gt;Back in the &lt;code&gt;store()&lt;/code&gt; method, after &lt;code&gt;this.transcodeVideo()&lt;/code&gt; completes running, we redirect the user to the Videos index, where their newly uploaded video should appear.  &lt;/p&gt;

&lt;p&gt;There's a lot happening in those 2 methods, and the code could (and should) be refactored before going into any sort of production usage, but for our example it works just fine.&lt;/p&gt;

&lt;p&gt;You'll also remember that in a few of the methods we made mention of templates. These are references to Edge templates, AdonisJS's templating engine. Let's create those files now by creating a new folder &lt;code&gt;resources/views/videos&lt;/code&gt; And create three new files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;resources/views/videos/create.edge&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resources/views/videos/index.edge&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;resources/views/videos/show.edge&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;(This isn't really a frontend tutorial, so I'm loading video.js via cdn where needed, and I'm foregoing any styling or design. Basically, it ugly, but it works.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;create.edge&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Create Video&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/videos"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Back&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt;
  &lt;span class="na"&gt;action=&lt;/span&gt;&lt;span class="s"&gt;"{{ route('VideosController.store') }}"&lt;/span&gt;
  &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;
  &lt;span class="na"&gt;enctype=&lt;/span&gt;&lt;span class="s"&gt;"multipart/form-data"&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Video Name&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"name"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;label&lt;/span&gt; &lt;span class="na"&gt;for=&lt;/span&gt;&lt;span class="s"&gt;"videoFile"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Choose a video file:&lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"file"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"videoFile"&lt;/span&gt; &lt;span class="na"&gt;accept=&lt;/span&gt;&lt;span class="s"&gt;"video/mp4"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;br&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Create Video&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;index.edge&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Videos Index&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/videos/create"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Create new Video&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding-bottom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;4px&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nl"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.3s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.card&lt;/span&gt;&lt;span class="nd"&gt;:hover&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;box-shadow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;8px&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

@each(video in videos)
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"container"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Video Name:&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; {{ video.name }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/videos/{{ video.id}}"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Watch&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
@end
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;show.edge&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://vjs.zencdn.net/7.19.2/video-js.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;Show Video&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"/videos"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Back&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;hr&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Video Name: &lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; {{ video.name }}&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;video-js&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;vid1&lt;/span&gt; &lt;span class="na"&gt;width=&lt;/span&gt;&lt;span class="s"&gt;852&lt;/span&gt; &lt;span class="na"&gt;height=&lt;/span&gt;&lt;span class="s"&gt;480&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"vjs-default-skin"&lt;/span&gt; &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;source&lt;/span&gt;
     &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"http://{{ cloudfront.url }}/uploads/{{ video.id }}/index.m3u8"&lt;/span&gt;
     &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/x-mpegURL"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/video-js&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://vjs.zencdn.net/7.19.2/video.min.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;player&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;videojs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vid1&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="na"&gt;limitRenditionByPlayerDimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;player&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're almost ready to test our app, the last step before we can do so is to configure our routes to connect to our Videos Controller.  &lt;/p&gt;

&lt;p&gt;Open the &lt;code&gt;start/routes.ts&lt;/code&gt; file and add the following routes below the Hello World route.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/videos/create&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;VideosController.create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/videos&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;VideosController.index&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/videos/:id&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;VideosController.show&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Route&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/videos&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;VideosController.store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once those routes are added, start the Adonis server in development mode by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a web browser, open up &lt;code&gt;http://localhost:3333/videos&lt;/code&gt; to find the videos index page.&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%2Fuploads%2Farticles%2Fh3bmxmdaghgkbheiji51.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh3bmxmdaghgkbheiji51.png" alt="Videos index" width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the "Create new Video" to open up &lt;code&gt;/videos/create&lt;/code&gt;. I'm uploading a copy of Blender's Big Buck Bunny. Once you click the "Create Video" button, no feedback will be shown on the frontend. But, if you check the terminal where you're running your adonis server, you should start to see ffmpeg progress after a couple seconds.&lt;/p&gt;

&lt;p&gt;(I'd also recommend not using a huge video file just due to the lack of feeback)&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%2Fuploads%2Farticles%2Fb14xs07ng7tqdkqe6x70.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb14xs07ng7tqdkqe6x70.png" alt="Create Video" width="800" height="619"&gt;&lt;/a&gt;&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%2Fuploads%2Farticles%2Fcsfkytss8tb7q7g7aft1.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcsfkytss8tb7q7g7aft1.png" alt="ffmpeg progress" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After video transcoding finishes, you'll automatically be redirected back to the video index, but this time, your new video should be listed.&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%2Fuploads%2Farticles%2F0towx65hkhs55xtogrqf.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0towx65hkhs55xtogrqf.png" alt="Video index with Big Buck Bunny" width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you click the "Watch" link, you'll be taken to the Show video page, where (fingers crossed) your video will be available for HLS playback inside a video.js player.&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%2Fuploads%2Farticles%2Ff4hxxc5p6wdgege7p3mw.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4hxxc5p6wdgege7p3mw.png" alt="Watching back our video" width="800" height="619"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It works! To verify that our video is being streamed to us via HLS, you can open up the developer tools in the browser of your choice (I'm using Chrome) to look at the network requests.&lt;/p&gt;

&lt;p&gt;If we look at the network tab you can see the various &lt;code&gt;.ts&lt;/code&gt; files being loaded for our video. (It's only loading at 720p, but that's a frontend video.js issue so we won't troubleshoot that here)&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%2Fuploads%2Farticles%2F2usoik9keozah308299f.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2usoik9keozah308299f.png" alt="Show video without throttling" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we do want to test HLS adaptive playback though, we can use the Network throttling feature in Chrome to simulate a lower bandwidth connection. I'm using the "Slow 3G" preset.&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%2Fuploads%2Farticles%2Fcu8i2ur9e6a3ldiq1s45.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcu8i2ur9e6a3ldiq1s45.png" alt="Show Video with throttling" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see that on the slower connection, our HLS stream adapts to start using the lower quality (thereby smaller file size) versions to give the user a better playback experience.&lt;/p&gt;

&lt;p&gt;We now have a primitive, but functional, HLS transcoder and VOD streaming platform. In the next installment, we can implement CloudFront Pre-signed Cookies to limit access to our videos, and also implement a Job Queue, to give the end-user a better experience during transcoding.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Geocoding Against Custom Geography with Geocod.io &amp; Turf.js</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Tue, 05 Apr 2022 13:31:24 +0000</pubDate>
      <link>https://forem.com/adamkatora/geocoding-against-custom-geography-with-geocodio-turfjs-54o3</link>
      <guid>https://forem.com/adamkatora/geocoding-against-custom-geography-with-geocodio-turfjs-54o3</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/akatora28/turfjs-custom-geoboundaries"&gt;Github Repo:&lt;/a&gt;  &lt;/p&gt;

&lt;p&gt;For most of my geocoding needs, &lt;a href="https://www.geocod.io/"&gt;Geocod.io&lt;/a&gt; more than fits the bill. Their API provides a fast and simple way to convert addresses into geographic coordinates, get congressional or state legislative districts, and much more. I'd recommend giving their &lt;a href="https://www.geocod.io/docs"&gt;API docs&lt;/a&gt; a read if you have an upcoming project that you need geocoding for.&lt;/p&gt;

&lt;p&gt;(Full disclosure: I'm NOT sponsored by Geocod.io, I just like using their service and it makes my life easier)&lt;/p&gt;

&lt;p&gt;Despite all these great features, there are some instances where we need to check against geographic boundaries that Geocod.io doesn't have. An example of this would be seeing if someone's address is inside a specific City or County Council district.  &lt;/p&gt;

&lt;p&gt;Fortunately, we can use turf.js to extend Geocod.io's functionality to fit our own specific needs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 1,000 foot overview:
&lt;/h2&gt;

&lt;p&gt;To give you the gist of what we'll be doingt:&lt;/p&gt;

&lt;p&gt;First, we'll still use Geocod.io to convert our Address into latitude and longitude coordinates. Doing so allows us to take those coordinates, and work with them through the &lt;a href="https://turfjs.org/"&gt;turf.js&lt;/a&gt; module.  &lt;/p&gt;

&lt;p&gt;Next, we'll take the geoJSON file of our custom geography, and use the &lt;a href="https://www.npmjs.com/package/node-geojson"&gt;node-geojson&lt;/a&gt; module to extract the &lt;code&gt;features&lt;/code&gt; (more on these later) into a format we can pass into turf.js as a &lt;code&gt;polygon&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once we have those two things ready to go, we'll use a turf.js function &lt;code&gt;booleanPointInPolygon&lt;/code&gt;, to check if our coordinates is inside one of those polygons.  &lt;/p&gt;

&lt;p&gt;If that all sounds a bit confusing now, don't worry, things will make more sense once we see it in action, and also once we start visualizing some of our spatial data.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Code:
&lt;/h2&gt;

&lt;p&gt;Let's start with a fresh project, I'm creating a new directory called &lt;code&gt;turf-tut&lt;/code&gt; to hold our working files in. Create that directory, then &lt;code&gt;cd&lt;/code&gt; inside and run the following 2 commands to install our dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;geocodio-library-node node-geojson @turf/turf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;dotenv &lt;span class="nt"&gt;--save-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything should be pretty self-explanatory here, the only thing that might look a little weird is that we'll be installing dotenv as a dev dependency to store our Geocodio API Key. It's a bad idea to hard code API keys.&lt;/p&gt;

&lt;p&gt;Once that finishes installing, update your &lt;code&gt;package.json&lt;/code&gt; file to add the following &lt;code&gt;start&lt;/code&gt; script. Your final &lt;code&gt;package.json&lt;/code&gt; should look something like this:&lt;/p&gt;

&lt;p&gt;(Note: the version numbers to the right of your packages might be different from mine. If you copy/paste this entire .json file, you'll need to re-run &lt;code&gt;npm install&lt;/code&gt; which will reinstall all these packages from the package.json file)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&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="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node -r dotenv/config index.js"&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="nl"&gt;"dependencies"&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="nl"&gt;"@turf/turf"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^6.5.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"geocodio-library-node"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.4.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node-geojson"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.0.2"&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="nl"&gt;"devDependencies"&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="nl"&gt;"dotenv"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^16.0.0"&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;Create a &lt;code&gt;.env&lt;/code&gt; file and add the following line to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GEOCODIO_API_KEY="HelloWorld!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, create an &lt;code&gt;index.js&lt;/code&gt; file, and add the following code:&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;GEOCODIO_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GEOCODIO_API_KEY&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Geocodio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;geocodio-library-node&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// const geocoder = new Geocodio('YOUR_API_KEY');&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geoJSON&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;node-geojson&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;turf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;@turf/turf&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GEOCODIO_API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if we run &lt;code&gt;npm start&lt;/code&gt; we should see the below response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ npm start

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; start
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node &lt;span class="nt"&gt;-r&lt;/span&gt; dotenv/config index.js

HelloWorld!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take note that since we called &lt;code&gt;-r dotenv/config&lt;/code&gt; in our &lt;code&gt;start&lt;/code&gt; script, we could access those env vars through the process.env object without having to configure that in our code.  &lt;/p&gt;

&lt;p&gt;You'll also notice that we're executing our code inside &lt;code&gt;async function main()&lt;/code&gt;, this is to avoid issues with top-level awaits, &lt;a href="https://v8.dev/features/top-level-await"&gt;a topic&lt;/a&gt; that I won't go into here.  &lt;/p&gt;

&lt;p&gt;Before we can dive into writing code, we have 2 final setup steps. 1 - downloading some geoJSON data to work with, and 2 - setting up a Geocod.io account.&lt;/p&gt;

&lt;p&gt;For geoJSON, go to: &lt;a href="https://www1.nyc.gov/site/planning/data-maps/open-data/districts-download-metadata.page"&gt;https://www1.nyc.gov/site/planning/data-maps/open-data/districts-download-metadata.page&lt;/a&gt; and select "City Council Districts (Clipped to Shoreline)" and click the globe that says "GeoJSON". You'll be redirected to a text webpage with the GeoJSON data, save the data from that website, or grab the file from the example repo. &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Data Side Note:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
When working with public data like this, especially datasets that deal with things like legislative districts, it's important to note that the possibility of the data being inaccurate or incomplete always exists.&lt;/p&gt;

&lt;p&gt;Just be aware that nyc.gov provides this data as-is for informational purposes only as stated in their &lt;a href="https://www1.nyc.gov/site/planning/data-maps/open-data/districts-download-metadata.page"&gt;disclaimer&lt;/a&gt;  &lt;/p&gt;



&lt;p&gt;For Geocod.io go to &lt;a href="https://dash.geocod.io/register"&gt;dash.geocod.io/register&lt;/a&gt;, sign up for an account, then once you're logged in, hit the "API Keys" button on the left-hand sidebar, then hit the "Create an API Key" button. The only permissions we'll need are &lt;code&gt;GET /v1.7/geocode&lt;/code&gt;. Give your key a name, save it, then you should be able to copy your key and paste it into the .env file we created earlier in the &lt;code&gt;GEOCODIO_API_KEY&lt;/code&gt; variable.  &lt;/p&gt;

&lt;p&gt;Let's return to our &lt;code&gt;index.js&lt;/code&gt; file and start building out our geocoder.  &lt;/p&gt;

&lt;p&gt;With your API Key now stored as a .env variable, we can update our &lt;code&gt;index.js&lt;/code&gt;, change the Geocodio config line like below:&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="p"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;// const geocoder = new Geocodio('YOUR_API_KEY'); &amp;lt;- Change this&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;geocoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Geocodio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GEOCODIO_API_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// &amp;lt;- To this&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then update our &lt;code&gt;main()&lt;/code&gt; function:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;geoResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;geocoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geocode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;City Hall Park, New York, NY 10007&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;geoResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;lng&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;geoResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lng&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;lat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;geoResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lat&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;pt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;turf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;lat&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Our coordinates are: &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;lng&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;lat&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Our point is: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pt&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's only a few lines of code, but we have a lot going on here. To start, we create a variable, &lt;code&gt;geoResponse,&lt;/code&gt; and set it equal to the value of the promise returned from &lt;code&gt;geocoder.geocode()&lt;/code&gt;. In the above code I supplied the address &lt;code&gt;City Hall Park, New York, NY 10007&lt;/code&gt;, that is (as you probably could assume) the address for New York City Hall.  &lt;/p&gt;

&lt;p&gt;Next, we &lt;code&gt;console.log&lt;/code&gt; the response (I just used JSON.stringify to make sure everything gets printed) so you can see what the API response schema looks like (you could also check the docs for this). Then, we extract the Longitude and Latitude from our &lt;code&gt;geocoder.geocode&lt;/code&gt; response and store them as variables.&lt;/p&gt;

&lt;p&gt;Next, we create a variable &lt;code&gt;pt&lt;/code&gt; which we set as a &lt;code&gt;turf.point()&lt;/code&gt;. Note that the &lt;code&gt;.point()&lt;/code&gt; function accepts a single array of Longitude,Latitude. Turf.js uses the longitude first convention as does GeoJSON. If you take those coordinates and plug them into Google Maps they'll need to be latitude first, so it's good to keep track of this while we work.&lt;/p&gt;

&lt;p&gt;Finally, I console log our coordinates array, as well as the turf point &lt;code&gt;pt&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;After running &lt;code&gt;npm start&lt;/code&gt; again, you should see an output similar to the following.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ npm start

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; start
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node &lt;span class="nt"&gt;-r&lt;/span&gt; dotenv/config index.js

&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"input"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"address_components"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"city"&lt;/span&gt;: &lt;span class="s2"&gt;"New York"&lt;/span&gt;,
            &lt;span class="s2"&gt;"state"&lt;/span&gt;: &lt;span class="s2"&gt;"NY"&lt;/span&gt;,
            &lt;span class="s2"&gt;"zip"&lt;/span&gt;: &lt;span class="s2"&gt;"10007"&lt;/span&gt;,
            &lt;span class="s2"&gt;"country"&lt;/span&gt;: &lt;span class="s2"&gt;"US"&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="s2"&gt;"formatted_address"&lt;/span&gt;: &lt;span class="s2"&gt;"New York, NY 10007"&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;,
    &lt;span class="s2"&gt;"results"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"address_components"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"city"&lt;/span&gt;: &lt;span class="s2"&gt;"New York"&lt;/span&gt;,
                &lt;span class="s2"&gt;"county"&lt;/span&gt;: &lt;span class="s2"&gt;"New York County"&lt;/span&gt;,
                &lt;span class="s2"&gt;"state"&lt;/span&gt;: &lt;span class="s2"&gt;"NY"&lt;/span&gt;,
                &lt;span class="s2"&gt;"zip"&lt;/span&gt;: &lt;span class="s2"&gt;"10007"&lt;/span&gt;,
                &lt;span class="s2"&gt;"country"&lt;/span&gt;: &lt;span class="s2"&gt;"US"&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;,
            &lt;span class="s2"&gt;"formatted_address"&lt;/span&gt;: &lt;span class="s2"&gt;"New York, NY 10007"&lt;/span&gt;,
            &lt;span class="s2"&gt;"location"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="s2"&gt;"lat"&lt;/span&gt;: 40.713941,
                &lt;span class="s2"&gt;"lng"&lt;/span&gt;: &lt;span class="nt"&gt;-74&lt;/span&gt;.007401
            &lt;span class="o"&gt;}&lt;/span&gt;,
            &lt;span class="s2"&gt;"accuracy"&lt;/span&gt;: 1,
            &lt;span class="s2"&gt;"accuracy_type"&lt;/span&gt;: &lt;span class="s2"&gt;"place"&lt;/span&gt;,
            &lt;span class="s2"&gt;"source"&lt;/span&gt;: &lt;span class="s2"&gt;"TIGER/Line® dataset from the US Census Bureau"&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;
Our coordinates are:  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-74&lt;/span&gt;.007401, 40.713941 &lt;span class="o"&gt;]&lt;/span&gt;
Our point is:  &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;type&lt;/span&gt;: &lt;span class="s1"&gt;'Feature'&lt;/span&gt;,
  properties: &lt;span class="o"&gt;{}&lt;/span&gt;,
  geometry: &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;: &lt;span class="s1"&gt;'Point'&lt;/span&gt;, coordinates: &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-74&lt;/span&gt;.007401, 40.713941 &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;Great, we now have a means to convert an address into lng,lat coordinates, and convert that into a &lt;code&gt;turf.js&lt;/code&gt; point. If you'll recall back to our 1,000 ft overview, that's one of the two input parameters we need for &lt;code&gt;booleanPointInPolygon&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;So let's now turn our attention to our geoJSON file. If you haven't worked with geoJSON before, it might be worth &lt;a href="https://developer.here.com/blog/an-introduction-to-geojson"&gt;briefly familiarizing yourself&lt;/a&gt;. I'm by no means an expert on GeoJSON, but I'll do my best to explain enough to get through our use case.  &lt;/p&gt;

&lt;p&gt;GeoJSON is valid JSON (ie you can save the file as either a .json or .geojson), however, GeoJSON has a pre-defined format for how its data should be structured, which allow different applications to share GeoJSON between them. Here's an example of GeoJSON data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "type": "Feature",
  "properties": {
    "name": "Dinagat Islands"
  },
  "geometry": {
    "type": "Point",
    "coordinates": [125.6, 10.1]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that looks familiar, it's because our &lt;code&gt;turf.point()&lt;/code&gt; from earlier is actually valid GeoJSON itself. Taking a closer look at the geojson file, you'll also notice that the first value, &lt;code&gt;type&lt;/code&gt;, is equal to &lt;code&gt;Feature&lt;/code&gt;. In this case, features refer to &lt;a href="https://en.wikipedia.org/wiki/Simple_Features"&gt;Simple Features&lt;/a&gt;, which are things like points, lines, polygons, multi-points, etc (think back to high school geometry).  &lt;/p&gt;

&lt;p&gt;Additionally, geojson files can have the &lt;code&gt;type&lt;/code&gt; of &lt;code&gt;FeatureCollection&lt;/code&gt;, which (again you probably guessed) is comprised of a collection of &lt;code&gt;Features&lt;/code&gt;. &lt;code&gt;FeatureCollection&lt;/code&gt;, is the &lt;code&gt;type&lt;/code&gt; of the NYC Councilmanic districts file that we downloaded earlier.  &lt;/p&gt;

&lt;p&gt;Another great tool is &lt;a href="https://geojson.io/"&gt;this online geojson editor&lt;/a&gt;, courtesy of &lt;a href="https://github.com/tmcw"&gt;Tom MacWright&lt;/a&gt;. You can either copy / paste, the contents of our geoJSON file onto that website, or use the file upload feature to load it up.  &lt;/p&gt;

&lt;p&gt;After loading the file, you'll see a map of New York City with various polygons overtop of city limits. Each of those polygons is a distinct City Council district, and is a geoJSON &lt;code&gt;Feature&lt;/code&gt;. (See, told you it'd be easier to visualize). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3JJ0QhRH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xlio8cjtic4vyved0oge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3JJ0QhRH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xlio8cjtic4vyved0oge.png" alt="nyc city council geojson features" width="880" height="676"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try clicking on a specific polygon, you'll see that geojson.io shows a pop-over tooltip with more info about that polygon. This additional info is the &lt;code&gt;properties&lt;/code&gt; value on our GeoJSON file. In the below image, I zoomed in on the map to City Hall, and clicked the polygon to pull up the &lt;code&gt;properties&lt;/code&gt; for that feature. You can see that it has the properties, &lt;code&gt;OBJECTID&lt;/code&gt;, &lt;code&gt;CounDist&lt;/code&gt;, &lt;code&gt;Shape__Area&lt;/code&gt;, &lt;code&gt;Shape__Length&lt;/code&gt;. (The other properties geojson.io adds so that you can change the polygon line &amp;amp; fill colors, etc. Just disregard those). &lt;/p&gt;

&lt;p&gt;The property that we're interested in is &lt;code&gt;CounDist&lt;/code&gt;, that refers to the number of the NYC Council District.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--usioVSht--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d487uxmafb9s1grdfp18.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--usioVSht--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d487uxmafb9s1grdfp18.png" alt="NYC City Hall Polygon" width="880" height="676"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Side Note: The properties &lt;code&gt;Shape__Area&lt;/code&gt; and &lt;code&gt;Shape__Length&lt;/code&gt; were included on the geoJSON file when we downloaded it. GeoJSON doesn't automatically compute those values for you. But, Turf.js has the &lt;a href="https://turfjs.org/docs/#area"&gt;area&lt;/a&gt; function that you could use to implement size calculation if you wanted.&lt;/p&gt;




&lt;p&gt;Geojson.io also has a nice feature &lt;code&gt;Table view&lt;/code&gt;, which further helps understand how our geojson data works. If you click a cell in the Table view of our geojson file, you'll see the map zooms you to be centered on that polygon. You'll also notice that you can edit the contents of the cell. I mentioned this earlier, but the table view really highlights it, the &lt;code&gt;OBJECTID&lt;/code&gt; field != Council District. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--n9SAbnR0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6zsnw7nsnt6o5zc97q4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--n9SAbnR0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6zsnw7nsnt6o5zc97q4.png" alt="GeoJSON table view" width="680" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turning our attention back to &lt;code&gt;index.js&lt;/code&gt; (you can comment out our existing console.log calls for now) add the following new code inside our main() function at the bottom:&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;var&lt;/span&gt; &lt;span class="nx"&gt;geodata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;geoJSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createUsingFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./nyc-city-council.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;features&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;geodata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetAllFeatures&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&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;In this new section of code we're creating a new variable, &lt;code&gt;geodata&lt;/code&gt;, and setting it's value to the return of &lt;code&gt;geoJSON.createUsingFile()&lt;/code&gt;. That does 2 things: 1 - it loads our geoJSON from file into memory so our application can manipulate the geoJSON file, but 2 - we also get access to some nice helper functions from the &lt;code&gt;node-geojson&lt;/code&gt; module such as &lt;code&gt;GetAllFeatures()&lt;/code&gt; which we use in the next line to save each of the features from our geoJSON file into a new array.  &lt;/p&gt;

&lt;p&gt;And, as we just saw, each feature in our geoJSON is a file, so when we call the &lt;code&gt;.forEach&lt;/code&gt; function over our features array, we console.log the properties of each feature, which should be &lt;code&gt;OBJECTID&lt;/code&gt;, &lt;code&gt;CounDist&lt;/code&gt;, &lt;code&gt;Shape__Area&lt;/code&gt;, &lt;code&gt;Shape__Length&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After updating the code and running &lt;code&gt;npm start&lt;/code&gt; again, you should see output like the following:&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="o"&gt;&amp;gt;&lt;/span&gt; start
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node &lt;span class="nt"&gt;-r&lt;/span&gt; dotenv/config index.js

&lt;span class="o"&gt;{&lt;/span&gt;
  OBJECTID: 1,
  CounDist: 12,
  Shape__Area: 137870996.813004,
  Shape__Length: 56950.2637871384
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt;
  OBJECTID: 2,
  CounDist: 18,
  Shape__Area: 106383536.643585,
  Shape__Length: 62147.4707677974
&lt;span class="o"&gt;}&lt;/span&gt;

...rest

&lt;span class="o"&gt;{&lt;/span&gt;
  OBJECTID: 51,
  CounDist: 17,
  Shape__Area: 135003397.512329,
  Shape__Length: 119656.385650236
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, update our features.forEach() loop to the following:&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="nx"&gt;features&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// THIS IF ELSE IF VERY IMPORTANT!!!&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Polygon&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;poly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;turf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;MultiPolygon&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="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;poly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;turf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multiPolygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;geometry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;coordinates&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;isPointInPoly&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;turf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;booleanPointInPolygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;poly&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="nx"&gt;isPointInPoly&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Your point is in Council District: &lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CounDist&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;p&gt;Once again, we've added only a few lines of code, but there's &lt;strong&gt;a lot&lt;/strong&gt; going on here. So let's break it down. The first thing we do is check if our feature is of type &lt;code&gt;Polygon&lt;/code&gt; or &lt;code&gt;MultiPolygon&lt;/code&gt;. It is very important we run this check because if we try passing a &lt;code&gt;MultiPolygon&lt;/code&gt; geoJSON feature to the &lt;code&gt;Polygon&lt;/code&gt; &lt;code&gt;turf.js&lt;/code&gt; we'll get a confusing error message and spend a couple hours banging our heads against a keyboard until we figure it out.  &lt;/p&gt;

&lt;p&gt;Don't ask me why I know that.  &lt;/p&gt;

&lt;p&gt;Once we have our correct polygon type, we then pass our &lt;code&gt;point&lt;/code&gt;, &lt;code&gt;pt&lt;/code&gt;, from earlier and &lt;code&gt;polygon&lt;/code&gt; into the &lt;code&gt;turf.booleanPointInPolygon()&lt;/code&gt; function. That function (again, this is kinda obvious here) checks if the point is inside the polygon, and if so, returns &lt;code&gt;True&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Finally, if we hit a match, we console log back to the user, which feature (aka council district) the match was in.&lt;/p&gt;

&lt;p&gt;(For clarity I saved the result of &lt;code&gt;booleanPointInPolygon&lt;/code&gt; to a new variable, but you could just as easily run the if() check on the function itself.)  &lt;/p&gt;

&lt;p&gt;From looking at the pdf map on the nyc.gov site, I know that City Hall should be in district 1, but now for the moment of truth. Can our app figure that out?&lt;/p&gt;

&lt;p&gt;After saving index.js, run &lt;code&gt;npm start&lt;/code&gt; one last, fateful time.&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="o"&gt;&amp;gt;&lt;/span&gt; start
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node &lt;span class="nt"&gt;-r&lt;/span&gt; dotenv/config index.js

Your point is &lt;span class="k"&gt;in &lt;/span&gt;Council District:  1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a thing of beauty. Look out, Uber.  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Extra Credit:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If we really want to test how well our Geocoder does, let's pull an address off Governor's Island, to see if the MultiPolygon is really testing all the polygons. &lt;/p&gt;

&lt;p&gt;Originally, I wanted to use the address Statue of Liberty, because the GeoJSON file from ny.gov indicated it was also in Council District 1. There were 2 problems with that though, 1 - Geocod.io had a hard time converting the very non-standard address into lat, lng coordinates, and 2 - The Statue of Liberty is technically in New Jersey so I don't know what that's included in the GeoJSON file.  &lt;/p&gt;

&lt;p&gt;Since resolving inter-state disputes is also outside the scope of this tutorial, I pulled the address for Taco Vista, a TexMex restaurant at &lt;code&gt;140 Carder Rd, New York, NY 10004&lt;/code&gt; instead. Sounds tasty.&lt;/p&gt;

&lt;p&gt;Change line 10 in &lt;code&gt;index.js&lt;/code&gt; to &lt;code&gt;geocoder.geocode('140 Carder Rd, New York, NY 10004')&lt;/code&gt;, then run &lt;code&gt;npm start&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once again, Council District 1.&lt;/p&gt;




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

&lt;p&gt;GeoSpatial data can be really fun (and really frustrating) to work with. I hope this example was helpful for someone looking to get their toes feet and dive into working with spatial data, geoJSON and, turf.js more.  &lt;/p&gt;

&lt;p&gt;A good further extension of this project would be to integrate it into Express.js as a backend api, then use &lt;a href="https://docs.mapbox.com/mapbox.js/api/v3.3.1/"&gt;Mapbox&lt;/a&gt;, or &lt;a href="https://leafletjs.com/"&gt;leaflet.js&lt;/a&gt; to build a frontend to display the points and polygons on a map.&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>javascript</category>
      <category>node</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Serverless DERN TODO App Pt. 2.5</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Sat, 05 Mar 2022 13:41:01 +0000</pubDate>
      <link>https://forem.com/adamkatora/serverless-dern-todo-app-pt-25-1ccn</link>
      <guid>https://forem.com/adamkatora/serverless-dern-todo-app-pt-25-1ccn</guid>
      <description>&lt;h2&gt;
  
  
  Some thoughts and ideas
&lt;/h2&gt;

&lt;p&gt;This write-up in the &lt;a href="https://dev.to/adamkatora/fully-serverless-dern-stack-todo-app-pt-1-dynamodb-express-react-node-3dlc"&gt;Serverless DERN TODO App series&lt;/a&gt; won't involve any actual code or further development to our app. So feel free to skip ahead if you're just here for the code.&lt;/p&gt;

&lt;p&gt;I just wanted to give a rough road map of where I plan on taking this project, what some of the potential architectural challenges or limitations might be, and also think through some possible solutions.  &lt;/p&gt;

&lt;p&gt;But first, a meme.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Bx0RsCGA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rt6clybi09gt4282yh6s.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Bx0RsCGA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rt6clybi09gt4282yh6s.jpg" alt="That's just AWS Amplify but with more steps" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;QUESTION:&lt;/strong&gt; DERN Stack? That just sounds like &lt;a href="https://aws.amazon.com/amplify/"&gt;AWS Amplify&lt;/a&gt; but with more steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ANSWER:&lt;/strong&gt; I mean yeah, kind of, but doing things this way gives us a lot more control over each piece of our tech stack. Plus, it's fun way to learn more about Claudia.js, DynamoDB, API Gateway, and all sorts of other AWS tools.  &lt;/p&gt;

&lt;p&gt;Speaking of other AWS Tools, if you're interested in learning more about AWS Amplify, I'd definitely recommend checking it out. The last that I had played around with it, it was still pretty new in terms of fully-developed features, and in-depth documentation, but there was a lot of cool stuff under the hood like GraphQL, and easy integration with s3 storage.&lt;/p&gt;




&lt;h2&gt;
  
  
  DynamoDB
&lt;/h2&gt;

&lt;p&gt;Turning our attention back to our DERN app, up until now we've been using DynamoDB a &lt;em&gt;little&lt;/em&gt; bit wrong. Both the &lt;a href="https://dynamoosejs.com/guide/Scan/"&gt;Dynamoose&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/bp-query-scan.html"&gt;DynamoDB&lt;/a&gt; docs specifically mention that scans are &lt;strong&gt;significantly&lt;/strong&gt; less performant than query. Significant enough that most people recommend avoiding scans altogether unless your're performing ETL operations (and hence would need the entire table) or really know what you're doing.&lt;/p&gt;

&lt;p&gt;This is due to the fact that scan iterates over &lt;em&gt;each&lt;/em&gt; item in our table, whereas query uses the partition key (aka our primary key) to optimize the query. Query is how DynamoDB offers that single-digit millisecond latency at scale, and Scan slows down the more data (aka items) that your table has. Not good.  &lt;/p&gt;

&lt;p&gt;What is good, however, is that using scan during development does give us a quick way to get our application up and running, and start to get a sense of what data access patterns our application requires.  &lt;/p&gt;

&lt;p&gt;If you'll recall back to Part 2, for our Register User feature we scanned the database to check if anyone already had the username we wanted. Essentially, we are enforcing a unique constraint on that attribute, as DynamoDB doesn't have a built-in unique constraint like RDBMS's do.  &lt;/p&gt;

&lt;p&gt;If the number of users in our application (regardless of active or inactive) started to grow, this would start to slow down the Register and Login functions, because they're searching over the entire database with every scan.&lt;/p&gt;

&lt;p&gt;Finding user's by their username is an access pattern we need, so we need a way to efficiently query that information.&lt;/p&gt;

&lt;p&gt;One solution this issue, is by using a &lt;code&gt;Global Secondary Index&lt;/code&gt; or GSI. GSI's allow us to create a partition key and a sort key, on a specific attribute in our table (in this case username).  &lt;/p&gt;

&lt;p&gt;That's merely scratching the surface of GSIs, sort keys, local indexes and all the great features DynamoDB offers. But, we'll be in a much better place once we start to optimize our tables in future parts of this series.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Future Plans &amp;amp; Features (and more Dynamo)
&lt;/h2&gt;

&lt;p&gt;The first (and probably most obvious feature) we'll be implementing is the ability to create Todos. Let's be honest, it's not much of a "Todo" app if it can't even do that.  &lt;/p&gt;

&lt;p&gt;Fortunately, this is fairly easy once we make the corrections to our DynamoDB tables mentioned above. This feature does however, bring up the interesting concept of "joins", which I put in quotes since joins don't technically exist in DynamoDB.  &lt;/p&gt;

&lt;p&gt;While joins don't &lt;em&gt;technically&lt;/em&gt; exist in DynamoDB, we're still kind of applying the concept of joins to our data, by splitting the Todo items into their own table, then adding a reference back to the owning user's id.  &lt;/p&gt;

&lt;p&gt;We can then create a GSI on the userId attribute in our Todos table, and very quickly query for every Todo owned by a user.  &lt;/p&gt;

&lt;p&gt;This "join" concept does open the possibility for some even more interesting features later down the line (which is what I was ultimately trying to get at with this segment) like sharing access to Todos with other users, creating Organizations with multiple users, fine-grained access control and a lot more.  &lt;/p&gt;

&lt;p&gt;These features are things that I haven't yet had the chance to try and implement, but I think they'll make for some very interesting challenges, and as our app continues to grow and take shape I think we'll be better able to visualize and tackle those challenges. &lt;/p&gt;

&lt;p&gt;Overall it's a lot of ideas and things I'd like to try, and hopefully will be able to in this series, time permitting. If only there was some sort of app I could keep track of all these Todos in...&lt;/p&gt;

</description>
      <category>programming</category>
      <category>dynamodb</category>
      <category>aws</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Building an Active Directory Pentesting Home Lab in VirtualBox</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Fri, 04 Mar 2022 01:39:38 +0000</pubDate>
      <link>https://forem.com/adamkatora/building-an-active-directory-pentesting-home-lab-in-virtualbox-53dc</link>
      <guid>https://forem.com/adamkatora/building-an-active-directory-pentesting-home-lab-in-virtualbox-53dc</guid>
      <description>&lt;p&gt;Active Directory is often one of the largest attack services in Enterprise settings. In fact, the &lt;a href="https://www.offensive-security.com/offsec/oscp-exam-structure/" rel="noopener noreferrer"&gt;OSCP Exam was recently updated&lt;/a&gt; to have less emphasis on buffer overflows but added a section dedicated to Active Directory.  &lt;/p&gt;

&lt;p&gt;AD can be confusing at first to learn, but one of the best ways to learn anything in software, is by installing and setting it up ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downloading Windows Server 2019
&lt;/h2&gt;

&lt;p&gt;Find the &lt;a href="https://www.microsoft.com/en-us/evalcenter/evaluate-windows-server-2019" rel="noopener noreferrer"&gt;Windows Server 2019 download&lt;/a&gt; from the Microsoft Evaluation Center. Scroll down to the option "Windows Server 2019", and select the ISO download option, which Microsoft is apparently labeling as "Please select your experience:"&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%2Fuploads%2Farticles%2Fnt7ojsgyhomv3pjcjta3.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%2Fuploads%2Farticles%2Fnt7ojsgyhomv3pjcjta3.jpg" alt="Windows Evaluation Center Downloads"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before the download will start, you'll be prompted to fill out some personal information. It asks for a work email, but a personal email (ie gmail, etc) should work fine too. Finally select your language, then start the download.  &lt;/p&gt;

&lt;p&gt;After clicking download, you should see the file pop up in your downloads bar, and the webpage will update to reflect the file name of the Windows Server 2019 Eval version you're downloading.&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%2Fuploads%2Farticles%2F26yad9povzymmzlloy04.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%2Fuploads%2Farticles%2F26yad9povzymmzlloy04.jpg" alt="Download Starting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Windows Server 2019 in VirtualBox
&lt;/h2&gt;

&lt;p&gt;In VirtualBox, start by clicking the New VM button, the blue spikey looking thing. In the VM Options pop-up, for &lt;code&gt;Name&lt;/code&gt; type in "Windows Server 2019", for &lt;code&gt;Machine Folder&lt;/code&gt; select a folder on your host computer where you want to store your VM files. Select "Microsoft Windows" for &lt;code&gt;Type&lt;/code&gt;, and for &lt;code&gt;Version&lt;/code&gt; select "Windows 2019 (64-bit)". Then click "Continue".  &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%2Fuploads%2Farticles%2Fd2zf2p475usfd1rnygf4.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%2Fuploads%2Farticles%2Fd2zf2p475usfd1rnygf4.jpg" alt="New VirtualBox Machine"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the next options panel, "Memory size", you can leave the default at 2048MB, aka 2GB, which is the minimum required memory amount for Windows Server 2019, or. But if you have the host RAM to spare, bumping up the VM memory to 4096MB or a little more is reccommended.  &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%2Fuploads%2Farticles%2Fxscjiw1w37b3yo1o66ri.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%2Fuploads%2Farticles%2Fxscjiw1w37b3yo1o66ri.jpg" alt="VirtualBox memory size"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(For our pentesting lab, these small values are fine, obviously though in a production setting you'd need significantly more RAM to run smoothly)&lt;/p&gt;

&lt;p&gt;For the "Hard disk" panel, select "Create a virtual hard disk now", then select "VDI (VirtualBox Disk Image)" and hit Continue. For "Storage on physical hard disk" I'm selecting the "Dynamically allocated" option.  &lt;/p&gt;

&lt;p&gt;The "File location and size" should default to a new folder with your VM name from earlier, in the directory you also specified in the earlier step. I'd reccommend leaving this default so that all the VM files are contained in a single location. I'm leaving my VDI size at 50.00GB.  &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%2Fuploads%2Farticles%2F3521fz3f5q18znmx2x16.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%2Fuploads%2Farticles%2F3521fz3f5q18znmx2x16.jpg" alt="Create new Hard Disk"&gt;&lt;/a&gt;&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%2Fuploads%2Farticles%2F6jnaugijjbnm95zy023y.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%2Fuploads%2Farticles%2F6jnaugijjbnm95zy023y.jpg" alt="Select Hard Disk Type"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After clicking through that, our new VM should appear in the left-hand sidebar, I have a few other VM's already installed which is why my machine appears further down on the list.  &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%2Fuploads%2Farticles%2F2hvg1l8t7k8i4jup9v28.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%2Fuploads%2Farticles%2F2hvg1l8t7k8i4jup9v28.jpg" alt="New VM Created Successfully"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we have a VM created, we still need to install Windows Server 2019 onto our Virtual Machine. With the new VM highlighted (the background color should be a light blue) click the settings button.&lt;/p&gt;

&lt;p&gt;In the new pop-up, select "Storage", then under the "Storage Devices" window, click the empty disc icon. On the right-hand windows "Attributes", click the blue disk icon, click "Choose a disk file", then find the Windows Server 2019 .iso file we downloaded earlier. The filename will probably be the same or similar to: &lt;code&gt;17763.737.190906-2324.rs5_release_svc_refresh_SERVER_EVAL_x64FRE_en-us_1.iso&lt;/code&gt;. If the iso is loaded up correctly, you should see the "Empty" next to the disk icon change to the .iso file name. Click "OK" to save changes and close the pop-up. &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%2Fuploads%2Farticles%2F8lp2znpkd9g870hnc88h.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%2Fuploads%2Farticles%2F8lp2znpkd9g870hnc88h.jpg" alt="Add iso file to our VM"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In VirtualBox, the steps that we just took are equivalent to inserting an install CD (or in this day and age, an install USB). So now we'll need to turn on the VM so we can actually install Windows Server from that .iso.  &lt;/p&gt;

&lt;p&gt;With the new VM still highlighted, click the green "Start" arrow. A new window should pop-up on your host machine. This is our Server 2019 VM. After it finishes initially booting up, you'll see the Windows Server 2019 installation prompt. &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%2Fuploads%2Farticles%2F64hwadgqbf0stps0h7yu.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%2Fuploads%2Farticles%2F64hwadgqbf0stps0h7yu.jpg" alt="Installing Windows Server 2019"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leave the default settings as-is, and click next. You should see a new tab with a single "Install Now" button. Click that, and on the "Windows Setup" tab that appears, You should see 4 different versions of Server 2019. The option that we want is "Windows Server 2019 Standard Evaluation (Desktop Experience)".&lt;/p&gt;

&lt;p&gt;It's important to select the "&lt;strong&gt;Desktop Experience&lt;/strong&gt;" version of Server 2019, this is the version of Windows Server that has a GUI similar to a standard Windows 10 install. In prior versions of Windows Server, you were able to install a GUI after initial installation, but that is no longer the case, so let's make sure we're installing the GUI version from the start.  &lt;/p&gt;

&lt;p&gt;After selecting the correct version, you'll need to accept Windows License terms. Then select the "custom install" version, since this is a fresh machine and we're not upgrading from any previous versions. Our VirtualBox VDI should appear in the Windows Install tab as "Drive 0 Unallocated Space", it should be selected by default, so click the Next button.  &lt;/p&gt;

&lt;p&gt;This will start the actual installation of Windows Server 2019 Desktop Experience. Like the download step from earlier, this process will take a little bit of time to run, so let Windows do it's thing through the install process. You'll probably notice that the Virtual Machine restarts a couple times through the install. This is normal.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Initial Login &amp;amp; Setup
&lt;/h2&gt;

&lt;p&gt;Once the install is finished, you'll need to supply a few more configuration settings to complete setting up your Windows Server VM. The built-in administrator username is pre-selected as "Administrator", for a password, I'm using &lt;code&gt;P@ssw0rd!&lt;/code&gt;, not very secure, but it'll work for a small-scale home lab.  &lt;/p&gt;

&lt;p&gt;Additionally, back in the VirtualBox manager, you can save the Administrator password for this machine by clicking "Settings", in the "General" tab, select "Description" then add some notes with our machine credentials.  &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%2Fuploads%2Farticles%2F2586wipr46cbezw3xmrp.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%2Fuploads%2Farticles%2F2586wipr46cbezw3xmrp.jpg" alt="VirtualBox VM Description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you've entered and saved our Administrator password, Windows should finish applying those new settings, and then open up a to a standard lock screen which should look familiar to you if you've used Windows 10 before.  &lt;/p&gt;

&lt;p&gt;To login to our Windows Server VM, we'll need to hit "Ctrl + Alt + Delete". I work on both macOS and Windows Hosts, and trying to figure out the equivalent keys between various hosts can be a pain. Fortunately, VirtualBox has a nice built-in feature that allows us to input "Ctrl + Alt + Delete".  &lt;/p&gt;

&lt;p&gt;On a macOS host, make sure you have your Windows Server 2019 VM selected, then in the upper menu bar, select "Input", hover over the "Keyboard" option, then click "Insert Ctrl-Alt-Delete". That dropdown will also show you what the keyboard shortcut on your host is to enter that without having the select the menu bar option.  &lt;/p&gt;

&lt;p&gt;Once that's entered, you should be able to type in our Administrator password, &lt;code&gt;P@ssw0rd!&lt;/code&gt; and login. &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%2Fuploads%2Farticles%2F2det6ukd0doel680hdjq.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%2Fuploads%2Farticles%2F2det6ukd0doel680hdjq.jpg" alt="Windows Server Lock Screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On initial login, I got an automatic prompt asking if I wanted to turn on "Network Discovery", I went ahead and turned that on.  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Side Note:&lt;/strong&gt; To the best of my knowledge, enabling "Network Discovery" doesn't affect ICMP settings. Ie, if you were to try to ping your Server 2019 VM from another host in your VirtualBox network right now, it wouldn't work. That's skipping ahead a couple steps though, so don't worry too much about it right now if that doesn't make sense to you.&lt;/p&gt;




&lt;p&gt;You'll also most likely see that the "Server Manager" program starts up by default. The astute readers among you might have noticed a &lt;em&gt;tiny&lt;/em&gt; bright Orange "1" followed by a large heading labeled "Configure this local server". I think that means Windows wants us to click on it.  &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%2Fuploads%2Farticles%2Fie6efa9q49sbhhwmoio0.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%2Fuploads%2Farticles%2Fie6efa9q49sbhhwmoio0.jpg" alt="Windows Server Manager"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on that link takes us to the "Local Server" configuration page, which can also be access via the left-hand sidebar.  &lt;/p&gt;

&lt;p&gt;There's two main settings to take note of at the moment, the first is our &lt;code&gt;Computer Name&lt;/code&gt;: "WIN-K3SDKO5BM8I", the second is the name of our &lt;code&gt;Workgroup&lt;/code&gt;: "WORKGROUP".  &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%2Fuploads%2Farticles%2Fgkxmgsd9h1etotpd9pql.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%2Fuploads%2Farticles%2Fgkxmgsd9h1etotpd9pql.jpg" alt="Server Manager Local Server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I won't go into too much detail about the differences between a &lt;code&gt;Workgroup&lt;/code&gt; and a &lt;code&gt;Domain&lt;/code&gt; but one of the key differences for our use case is that in a &lt;code&gt;Workgroup&lt;/code&gt;, user accounts are managed by &lt;em&gt;individual computers&lt;/em&gt;, whereas in a domain user accounts are managed &lt;em&gt;by a central server or servers&lt;/em&gt;, called &lt;em&gt;domain controllers&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;You might also hear it explained that in a &lt;code&gt;Workgroup&lt;/code&gt;, all the various computers in that &lt;code&gt;Workgroup&lt;/code&gt; are essentially peers, and no one single computer has elevated or admin credentials above the others in that &lt;code&gt;Workgroup&lt;/code&gt;. &lt;code&gt;Domains&lt;/code&gt;, conversely, have that central &lt;code&gt;domain controller&lt;/code&gt; as the top-level administrative component, and as such have admin rights over the various user accounts within the &lt;code&gt;Domain&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Active Directory Domains is what you're more likely to see in larger scale, or Enterprise environments, and that's what we're trying to set up (albeit on a smaller scale) for our local pen-testing environment.  &lt;/p&gt;

&lt;p&gt;With that explanation out of the way, let's go ahead and get started on our AD setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Active Directory
&lt;/h2&gt;

&lt;p&gt;To start, let's rename our Windows Server 2019 &lt;code&gt;Computer Name&lt;/code&gt; to something reflective of the fact that this will become our Domain Controller. Click the light-blue computer name, then in the "System Properties" pop-up, select the "Change..." button down next to the "To rename this computer...." text.  &lt;/p&gt;

&lt;p&gt;I'm renaming my computer the very creative name, ADAMDC. Adam, for my name, and DC to reference the fact that this is our domain controller.&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%2Fuploads%2Farticles%2Fphygt78u93oxtiutqb70.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%2Fuploads%2Farticles%2Fphygt78u93oxtiutqb70.jpg" alt="Renaming our Server"&gt;&lt;/a&gt;&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%2Fuploads%2Farticles%2Fpci885p9mmho8pzbv76z.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%2Fuploads%2Farticles%2Fpci885p9mmho8pzbv76z.jpg" alt="Renaming our Server"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hit "OK" and after a few seconds that should update. You'll also see a pop-up stating that you'll have to restart in order for our name change to take effect. Hit "apply" in the "System Properties" window to save the changes. But choose the "restart later" option, to delay the automatic restart. &lt;/p&gt;

&lt;p&gt;We still need to setup some VirtualBox networking options, something we can't do while our VM is running, so this is a good opportunity to shutdown our VM.  &lt;/p&gt;

&lt;p&gt;Click the "Windows" button in the bottom left-hand screen, then the power button icon and select "Shut down". This should be very familiar if you're used to working with Windows 10. What might not be familiar is the next pop-up, selecting a reason for shutting down.  &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%2Fuploads%2Farticles%2Fy3uo1ukxg2emojny5hji.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%2Fuploads%2Farticles%2Fy3uo1ukxg2emojny5hji.jpg" alt="Shutdown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our case, we'll just select "Other (Planned)" as the reason every time we shutdown our Windows Server VM. This isn't a production server, so it's not really important or even necessary to log reasons for shutdown.  &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%2Fuploads%2Farticles%2Fxkfpsdqpc8ci4brcl0uo.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%2Fuploads%2Farticles%2Fxkfpsdqpc8ci4brcl0uo.jpg" alt="Mr. Resetti but Windows"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you do shutdown without go through that process (ie you just close your virtual machine window) you'll get a mildly annoying pop-up the next time you turn on the vm. It's kinda like Microsoft's version of &lt;a href="https://animalcrossing.fandom.com/wiki/Resetti" rel="noopener noreferrer"&gt;Mr. Resetti&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once that completes shutting down, click back into your "VirtualBox Manager".&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing Active Directory - VBox Network Configuration
&lt;/h3&gt;

&lt;p&gt;We haven't look too much into our actual VirtualBox setup so far, other than what was absolutely needed to get our Windows Installation up and running. Now though, we're going to want to make sure we have our networking options setup and configured correctly before we move onto to actually installing Active Directory.  &lt;/p&gt;

&lt;p&gt;VirtualBox has a couple different types of virtual networking options, but the one we're going to focus on is a "Host-Only" adapter. TODO MORE INFO&lt;/p&gt;

&lt;p&gt;First, ensure that you have a network setup in VirtualBox. &lt;/p&gt;

&lt;p&gt;Click "Tools" and then the hamburger menu looking thing to the right of it. You should see 4 options pop-up, "Welcome", "Media", "Network", "Cloud". Network is the option we want so click on that. &lt;/p&gt;

&lt;p&gt;If you don't already have a network click the "Create" button to make a new one, otherwise click properties to edit your existing one. Make sure the "DHCP Server" option is disable, and also select "Configure Adapter Manually"&lt;/p&gt;

&lt;p&gt;Give your adapter the settings of:&lt;br&gt;&lt;br&gt;
IPv4 Address: &lt;code&gt;192.168.56.1&lt;/code&gt;&lt;br&gt;&lt;br&gt;
IPv4 Network Mask: &lt;code&gt;255.255.255.0&lt;/code&gt;  &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%2Fuploads%2Farticles%2Fkipdkk5c7hbouaao65fc.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%2Fuploads%2Farticles%2Fkipdkk5c7hbouaao65fc.jpg" alt="VirtualBox Network"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Still in VirtualBox Manager, select your Windows Server VM again, and select the options for that VM.&lt;/p&gt;

&lt;p&gt;In the Options pop-up, choose the "Network" tab, I have a NAT adapter in the Adapter 1 slot, so in Adapter 2 I've checked "Enable Network Adapter", set the Attached to: value as "Host-only Adapter", and for Name:, that's the name of the network we just created, I've selected my VBox network.&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%2Fuploads%2Farticles%2Fbxh728g5vykmhz6usfmq.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%2Fuploads%2Farticles%2Fbxh728g5vykmhz6usfmq.jpg" alt="VirtualBox Host-only Adapter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Save those settings, and it's time to start up our Windows Server VM Again. Click the green start arrow. Then login with our administrator credentials.  &lt;/p&gt;

&lt;p&gt;When you login, Server Manager should pop-up automatically again. Confirm that our computer rename from earlier successfully completed, then minimize Server Manager, and open up Command Prompt.&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%2Fuploads%2Farticles%2Fnb5zd248ow2cvvhuxj59.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%2Fuploads%2Farticles%2Fnb5zd248ow2cvvhuxj59.jpg" alt="Renaming our Server Worked"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In command prompt, type the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ipconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see an output with two network adapters listed (Virtualbox treats these as Ethernet connections). The first Network should be your NAT. That's how your VM gets external internet access. The second adapater should be the host only adapter we just setup, so you should notice that the IP will be in the range of the subnet that we specified when setting up the network a few moments ago.  &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%2Fuploads%2Farticles%2F0n3xa7fzzyhlvhg1f4yp.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%2Fuploads%2Farticles%2F0n3xa7fzzyhlvhg1f4yp.jpg" alt="Running ipconfig"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we need to manually assign our Windows Server Computer a static IP for our host-only network. Open up Control Panel, then select "Network and Internet", then "Network Sharing Center". You should once again see our two networks listed.&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%2Fuploads%2Farticles%2F2kwwrpqku7lo5tfsx8oj.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%2Fuploads%2Farticles%2F2kwwrpqku7lo5tfsx8oj.jpg" alt="Network and Sharing Center"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click the host-only network Ethernet (should most likely be Ethernet2), then select "Properties", then in the properties pop-up select the "Internet Protocol Version 4 (TCP/IPv4)" so that it is highlighted. Then click properties for IPv4.  &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%2Fuploads%2Farticles%2F2csvktbg5peqe24pq1xh.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%2Fuploads%2Farticles%2F2csvktbg5peqe24pq1xh.jpg" alt="Manually selecting IPv4"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A new window should popup, and "Obtain an IP address automatically" is most likely pre-selected. Choose "Use the following IP address:"&lt;/p&gt;

&lt;p&gt;IP Address: &lt;code&gt;192.168.56.2&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Subnet mask: &lt;code&gt;255.255.255.0&lt;/code&gt;&lt;br&gt;&lt;br&gt;
Default gateway: &lt;code&gt;192.168.56.1&lt;/code&gt;  &lt;/p&gt;

&lt;p&gt;Preferred DNS server: &lt;code&gt;192.168.56.2&lt;/code&gt;&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%2Fuploads%2Farticles%2Fyfamlwrcyu8ub73hokkd.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%2Fuploads%2Farticles%2Fyfamlwrcyu8ub73hokkd.jpg" alt="Set IP and DNS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With those settings inputted, click through the various "OK" buttons to apply the changes.&lt;/p&gt;

&lt;p&gt;Back in command prompt type &lt;code&gt;cls&lt;/code&gt; to clear out previous ipconfig command. Then, re-run ipconfig.  &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%2Fuploads%2Farticles%2Fc4lr47ysio27gqj3czls.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%2Fuploads%2Farticles%2Fc4lr47ysio27gqj3czls.jpg" alt="New IP who dis?"&gt;&lt;/a&gt;&lt;br&gt;
&lt;small&gt;New IP, who dis?&lt;/small&gt;&lt;/p&gt;

&lt;p&gt;With all that setup out of the way, we now have a local network (the host-only network) that our VirtualMachines can use to talk to each other. We also assigned our server a static IP in that network which is recommended before setting up Active Directory. Finally, we pointed the preferred DNS server to our Windows Server Machine. We'll install a DNS server later to handle DNS for our AD Domain.  &lt;/p&gt;
&lt;h2&gt;
  
  
  Installing Active Directory - Back to Windows Server
&lt;/h2&gt;



&lt;p&gt;So, we now have our domain &lt;strong&gt;&lt;em&gt;controller&lt;/em&gt;&lt;/strong&gt; ready (not &lt;em&gt;entirely&lt;/em&gt; true but we'll fix that shortly), but we still need a &lt;strong&gt;&lt;em&gt;domain&lt;/em&gt;&lt;/strong&gt; for it to be in control of.  &lt;/p&gt;

&lt;p&gt;Return to the Server Manager Dashboard, and this time, select option 2 from the middle list. It's the "Add Roles and Features" link. The "Add Roles and Features Wizard" should pop up.&lt;/p&gt;

&lt;p&gt;Click "Next &amp;gt;" on the "Before You Begin" page, then on "Installation Type" ensure the first option "Role-based or feature-based installation" is still selected and click "Next &amp;gt;"  &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%2Fuploads%2Farticles%2Fdazhobzqlrt3t506u58o.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%2Fuploads%2Farticles%2Fdazhobzqlrt3t506u58o.jpg" alt="Role Based Install"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the "Select destination server" page, you should see our Windows Server 2019 Machine, named &lt;code&gt;ADAMDC&lt;/code&gt;, in the Server Pool list, and click next.&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%2Fuploads%2Farticles%2Fvakno0kwyls8tv2w4vo3.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%2Fuploads%2Farticles%2Fvakno0kwyls8tv2w4vo3.jpg" alt="Server List"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the "Server Roles" you'll want to check the "Active Directory Domain Services" box. (This is the whole thing we've been working towards). &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%2Fuploads%2Farticles%2Fxezf7pbze2xriayzrhwo.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%2Fuploads%2Farticles%2Fxezf7pbze2xriayzrhwo.jpg" alt="Server Roles AD DS"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you click that box, a new pop-up will appear confirming that you want to add the additional required services for AD. &lt;/p&gt;

&lt;p&gt;We do. Click "Add Features".&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%2Fuploads%2Farticles%2Fcwq2bbpxzydoyu2sq1dg.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%2Fuploads%2Farticles%2Fcwq2bbpxzydoyu2sq1dg.jpg" alt="Server Roles"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click "Next" through the "Features" page.  &lt;/p&gt;

&lt;p&gt;That'll bring you to the Active Directory Domain Services information page. Give it a quick read, then click "Next &amp;gt;" &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%2Fuploads%2Farticles%2Firq3nio5904ix3afp78s.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%2Fuploads%2Farticles%2Firq3nio5904ix3afp78s.jpg" alt="Active Directory Domain Services"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is it, the confirmation page. As Uncle Ben said, "With great power comes great responsibility." When you're ready, click "Install".  &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%2Fuploads%2Farticles%2Fqaj4wp2xzzzoobgj3w9z.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%2Fuploads%2Farticles%2Fqaj4wp2xzzzoobgj3w9z.jpg" alt="Ready to Install"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The installation will start and run for a little, you can close out of the installer window if you want, but since we're already in a VM you can also just open up a different window on your host to watch youtube while we wait.&lt;/p&gt;

&lt;p&gt;After the installation completes, we still have a little more work to do. Remember earlier when I said we had our "domain controller" ready? Well, I might've lied a little bit, back then it was still just a lowly server. But I think our server has performed admirably so far, and is worthy of a promotion.&lt;/p&gt;

&lt;p&gt;Click the "Promote this server to a domain controller" link. If you closed out of the installation wizard, you can also find this link back in Server Manager.  &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%2Fuploads%2Farticles%2F889tg84xaxmmf1lm4248.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%2Fuploads%2Farticles%2F889tg84xaxmmf1lm4248.jpg" alt="Promotion"&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswfk988st8rzzy1uy4wz.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswfk988st8rzzy1uy4wz.png" alt="Promotion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That will pull up the "Active Directory Domain Service Deployment Configuration Wizard" Select "Add a new forest" radio button, and I'm using the Root domain name: &lt;code&gt;adamdomain.com&lt;/code&gt;. Which for the record, is a domain I do not own. This isn't something you'd want to do in a real-world install, but since this is just for our home lab I think it'll be fine.  &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%2Fuploads%2Farticles%2Fszmgz4bffunmxva3kiwu.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%2Fuploads%2Farticles%2Fszmgz4bffunmxva3kiwu.jpg" alt="AD DS Configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Leave Forest &amp;amp; Domain functional level at "Windows Server 2016", and add a (DSRM) password, I'm going to use &lt;code&gt;P@ssw0rd!&lt;/code&gt; again since this isn't a real AD install. Then go to the next page.&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%2Fuploads%2Farticles%2Fgv3f1ybtdnrwaifz9ywj.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%2Fuploads%2Farticles%2Fgv3f1ybtdnrwaifz9ywj.jpg" alt="Forest Configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On DNS Options, uncheck "Create DNS delegation" and click "Next". &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%2Fuploads%2Farticles%2Fvu14ochldpm2xih2woii.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%2Fuploads%2Farticles%2Fvu14ochldpm2xih2woii.jpg" alt="DNS delegation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Wait for the NetBIOS domain name to automatically detect your domain name then click "Next".&lt;/p&gt;

&lt;p&gt;Click "Next" again on the Paths page to accept the defaults.  &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%2Fuploads%2Farticles%2Fyisa4lwbnidawc4zjpdj.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%2Fuploads%2Farticles%2Fyisa4lwbnidawc4zjpdj.jpg" alt="Path Defaults"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the "Review Options" page, there isn't much for us to do, click "Next" and the Configuration Wizard will run a check script to ensure the Active Directory install can complete successfully on your machine.  &lt;/p&gt;

&lt;p&gt;On mine, I got a warning about weak cryptography algorithms and a warning about our first network adapter (the VirtualBox NAT) not having a static IP.&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%2Fuploads%2Farticles%2F2xtf0uun9bsyux6jn6sg.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%2Fuploads%2Farticles%2F2xtf0uun9bsyux6jn6sg.jpg" alt="Review Options 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can ignore those and click "Install". This will initiate the rest of the Active Directory installation process. This process might take a while to complete, and like some of the previous stages, will lock out and reset while it runs.  &lt;/p&gt;

&lt;p&gt;Once the server finishes install AD and resets, you'll see the lock screen again. Enter "Ctrl + Alt + Delete", but this time, you should notice the login page looks different than before.  &lt;/p&gt;

&lt;p&gt;This is part of the change from our Server being in a WORKGRUP into a Domain. Log in with our adminstrator credentials, those are still valid. Then when "Server Manager" starts up again, click "Local Server". It will take a couple moments for the information on the Local Server panel to update. but you should see that it now reflects our AD install.  &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%2Fuploads%2Farticles%2F32caqumaz2ft0pwwgydr.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%2Fuploads%2Farticles%2F32caqumaz2ft0pwwgydr.jpg" alt="It worked"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that done, we now have Active Directory installed, we don't have any other users, or other computers connected to our Domain yet, but we can do those things in another write-up.  &lt;/p&gt;

&lt;p&gt;One other thing that I noticed and found interesting through the install is that after the install was finished ICMP was enabled when prior to that it was not. If you had tried to ping our Windows Server Prior to the AD installation, you would've gotten "Destination Host Unreachable".&lt;/p&gt;

&lt;p&gt;Here's a ping from kali to our Windows Server post-install:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fts3vyclcvgvlwz6cto37.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%2Fuploads%2Farticles%2Fts3vyclcvgvlwz6cto37.jpg" alt="Kali Ping"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's easy enough to configure Windows Server to &lt;a href="https://kb.iu.edu/d/aopy" rel="noopener noreferrer"&gt;enable ICMP pings&lt;/a&gt;, but I always thought it was counter-intuitive (although probably a sensible default for security reasons) that it was blocked by default.  &lt;/p&gt;

&lt;p&gt;Nmap still seems to have issues running a ping against our windows host though, a default scan returns "Host Seems Down", but adding the &lt;code&gt;-Pn&lt;/code&gt; flag does show that our Windows AD Server is up and running, here's the results of a standard &lt;code&gt;nmap -sC -sV -Pn&lt;/code&gt; scan from Kali.&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="o"&gt;(&lt;/span&gt;kali㉿kali&lt;span class="o"&gt;)&lt;/span&gt;-[~]
└─&lt;span class="nv"&gt;$ &lt;/span&gt;nmap &lt;span class="nt"&gt;-sC&lt;/span&gt; &lt;span class="nt"&gt;-sV&lt;/span&gt; 192.168.56.2 &lt;span class="nt"&gt;-Pn&lt;/span&gt;
Starting Nmap 7.92 &lt;span class="o"&gt;(&lt;/span&gt; https://nmap.org &lt;span class="o"&gt;)&lt;/span&gt; at 2022-03-03 16:39 EST
Nmap scan report &lt;span class="k"&gt;for &lt;/span&gt;192.168.56.2
Host is up &lt;span class="o"&gt;(&lt;/span&gt;0.00074s latency&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Not shown: 989 filtered tcp ports &lt;span class="o"&gt;(&lt;/span&gt;no-response&lt;span class="o"&gt;)&lt;/span&gt;
PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos &lt;span class="o"&gt;(&lt;/span&gt;server &lt;span class="nb"&gt;time&lt;/span&gt;: 2022-03-03 21:40:22Z&lt;span class="o"&gt;)&lt;/span&gt;
135/tcp  open  msrpc         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          Microsoft Windows Active Directory LDAP &lt;span class="o"&gt;(&lt;/span&gt;Domain: adamdomain.com0., Site: Default-First-Site-Name&lt;span class="o"&gt;)&lt;/span&gt;
445/tcp  open  microsoft-ds?
464/tcp  open  kpasswd5?
593/tcp  open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp  open  tcpwrapped
3268/tcp open  ldap          Microsoft Windows Active Directory LDAP &lt;span class="o"&gt;(&lt;/span&gt;Domain: adamdomain.com0., Site: Default-First-Site-Name&lt;span class="o"&gt;)&lt;/span&gt;
3269/tcp open  tcpwrapped
Service Info: Host: ADAMDC&lt;span class="p"&gt;;&lt;/span&gt; OS: Windows&lt;span class="p"&gt;;&lt;/span&gt; CPE: cpe:/o:microsoft:windows

Host script results:
|_nbstat: NetBIOS name: ADAMDC, NetBIOS user: &amp;lt;unknown&amp;gt;, NetBIOS MAC: 08:00:27:a9:d5:db &lt;span class="o"&gt;(&lt;/span&gt;Oracle VirtualBox virtual NIC&lt;span class="o"&gt;)&lt;/span&gt;
| smb2-security-mode: 
|   3.1.1: 
|_    Message signing enabled and required
| smb2-time: 
|   &lt;span class="nb"&gt;date&lt;/span&gt;: 2022-03-03T21:40:23
|_  start_date: N/A

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ &lt;span class="nb"&gt;.&lt;/span&gt;
Nmap &lt;span class="k"&gt;done&lt;/span&gt;: 1 IP address &lt;span class="o"&gt;(&lt;/span&gt;1 host up&lt;span class="o"&gt;)&lt;/span&gt; scanned &lt;span class="k"&gt;in &lt;/span&gt;64.26 seconds

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>cybersecurity</category>
      <category>tutorial</category>
      <category>windows</category>
    </item>
    <item>
      <title>Fully Serverless DERN Stack TODO App Pt. 2 - Building out our API</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Wed, 02 Mar 2022 19:30:57 +0000</pubDate>
      <link>https://forem.com/adamkatora/fully-serverless-dern-stack-todo-app-pt-2-building-out-our-api-2jpk</link>
      <guid>https://forem.com/adamkatora/fully-serverless-dern-stack-todo-app-pt-2-building-out-our-api-2jpk</guid>
      <description>&lt;h2&gt;
  
  
  Part 2 - Building out our API &amp;amp; Auth system
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/adamkatora/fully-serverless-dern-stack-todo-app-pt-1-dynamodb-express-react-node-3dlc"&gt;Part. 1&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're just joining us, in &lt;a href="https://dev.to/adamkatora/fully-serverless-dern-stack-todo-app-pt-1-dynamodb-express-react-node-3dlc"&gt;Part 1&lt;/a&gt; of this series, we setup a simple express.js application, then used Claudia.js to deploy our app to AWS.  &lt;/p&gt;

&lt;p&gt;Here in Part 2, we'll be building out enough of our application that by the end you'll have a small, but functional, REST API. Since Part 1. was a lot of boilerplate Claudia.js setup, I've tried to get this Part 2 out as quickly as possible so that you can start to get an idea of what our final app will look like.&lt;/p&gt;

&lt;p&gt;As such, I haven't been able to fully go through this write-up myself to ensure that there's no bugs in the code, and add in helpful screenshots. That'll be coming soon. I'm going to make sure the &lt;a href="https://github.com/akatora28/serverless-dern-todo-pt2/"&gt;Github repo&lt;/a&gt; for this write-up is up to date first, so if you run into any issues, try checking there first for working code examples.&lt;/p&gt;

&lt;p&gt;With all that out of the way, let's move on to the fun stuff,  developing some features for our app. Mainly, a simple Auth system. We'll start by adding the Dynamoose package so writing some data models. We'll also add morgan, a logger middleware so that we can get info about incoming requests in the console.&lt;/p&gt;

&lt;p&gt;From the &lt;code&gt;/backend&lt;/code&gt; folder run the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;dynamoose morgan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, inside the &lt;code&gt;/backend/src&lt;/code&gt; create a &lt;code&gt;models&lt;/code&gt; directory where we'll store our dynamoose models.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd src
mkdir models
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're going to try to keep our app simple, so we'll create 2 models. 1.) Will be a User model, with a &lt;em&gt;very&lt;/em&gt; (read NOT production ready) basic auth system. 2.) Will be a Todo model to store information about User's Todos.&lt;/p&gt;

&lt;p&gt;From inside the models folder create two new files for each of the models. I like to follow a &lt;code&gt;[ModelName].model.js&lt;/code&gt; naming convention in my Express.js apps.&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;cd &lt;/span&gt;models
&lt;span class="nb"&gt;touch &lt;/span&gt;User.model.js
&lt;span class="nb"&gt;touch &lt;/span&gt;Todo.model.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, it's time to build out our models. If you've used Mongoose before, the syntax and schema of Dynamoose models should look very familiar to you.&lt;/p&gt;

&lt;p&gt;Type the following code for our User model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;User.model.js&lt;/em&gt;&lt;/strong&gt;&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;dynamoose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dynamoose&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;userSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// UUIDv4 ID&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timestamps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dynamoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We start by importing the dynamoose library with &lt;code&gt;require("dynamoose")&lt;/code&gt;. Next, we define our model's schema with the &lt;code&gt;dynamoose.Schema()&lt;/code&gt;. The first Object we pass into &lt;code&gt;dynamoose.Schema()&lt;/code&gt; contains all the fields and their associated "attribute types" (aka data types) for our model. &lt;/p&gt;

&lt;p&gt;You can read about the available &lt;a href="https://dynamoosejs.com/guide/Schema#attribute-types"&gt;attribute types here&lt;/a&gt;.  &lt;/p&gt;

&lt;p&gt;For right now, we're just going to create fields for &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;username&lt;/code&gt;, and &lt;code&gt;password&lt;/code&gt;. &lt;/p&gt;




&lt;p&gt;I've mentioned this already, and I think it goes without saying but just to cover all my bases here, &lt;strong&gt;I wouldn't use this auth implementation in a production app&lt;/strong&gt;. There's much better and more secure IdP services out there for developers. AWS has their &lt;a href="https://aws.amazon.com/cognito/"&gt;Cognito&lt;/a&gt; IdP service, and &lt;a href="https://auth0.com/"&gt;Auth0&lt;/a&gt; is another good choice. Both offer a fairly generous free-tier to allow you to get started quickly and eventually grow into a paid plan.&lt;/p&gt;




&lt;p&gt;We also pass a second object to the &lt;code&gt;.Schema()&lt;/code&gt; method, with some additional Schema Settings. We're setting "timestamps" to true which will automatically add createdAt &amp;amp; updatedAt timestamps.&lt;/p&gt;

&lt;p&gt;Finally, we use the &lt;code&gt;dynamoose.model()&lt;/code&gt; method, to create a new const &lt;code&gt;User&lt;/code&gt;. The first param passed to &lt;code&gt;.model&lt;/code&gt; is a string. This is what our model will be called. The second param we pass to &lt;code&gt;.model&lt;/code&gt; is the object containing our SchemaDefinition and SchemaSettings, which in our case we stored in the &lt;code&gt;userSchema&lt;/code&gt; const.&lt;/p&gt;

&lt;p&gt;At the bottom of the file, we have a standard &lt;code&gt;module.exports&lt;/code&gt; so that we can import the &lt;code&gt;User&lt;/code&gt; model in other files. &lt;/p&gt;

&lt;p&gt;With that created. Let's add the following to our &lt;code&gt;Todo.model.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;backend/src/models/Todo.model.js&lt;/em&gt;&lt;/strong&gt;&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;dynamoose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dynamoose&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;todoSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;dynamoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;//UUIDv4&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;notes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dueDate&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;status&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timestamps&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;Todo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dynamoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todoSchema&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Todo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our &lt;code&gt;Todo&lt;/code&gt; model is very similar to our &lt;code&gt;User&lt;/code&gt; model with one major difference. We added a field for &lt;code&gt;user&lt;/code&gt; with a type of &lt;code&gt;Object&lt;/code&gt;. We might end up changing this around later on, but that's one of the beauties of NoSQL databases, we don't have to get bogged down in too much data-modelling early on.&lt;/p&gt;

&lt;p&gt;Now that we have our Models in place, we need to start building out how our API will interact with our models. I like to structure my Express.js apps in a bit of an MVC pattern (in this case React will be our &lt;strong&gt;V&lt;/strong&gt; - view layer), and also create "Service Layers". If those two things don't make sense to you, no worries, just follow along and hopefully the project structure and code should help you make sense of those terms as we go along.  &lt;/p&gt;

&lt;p&gt;Also, if you've been following along this far, I'm going to assume you're comfortable with making new directories &amp;amp; files, so I'll just explain what new dirs and files we're creating, then at the end show the project structure instead of showing the bash command to create each new file.&lt;/p&gt;

&lt;p&gt;Back inside the &lt;code&gt;/src&lt;/code&gt; directory, make directories for &lt;code&gt;routes&lt;/code&gt;, &lt;code&gt;controllers&lt;/code&gt;, and &lt;code&gt;services&lt;/code&gt;. Inside &lt;code&gt;/src/routes&lt;/code&gt; create an &lt;code&gt;index.js&lt;/code&gt; file and an &lt;code&gt;auth.routes.js&lt;/code&gt; file. Inside the &lt;code&gt;/src/contollers&lt;/code&gt; directory create a file &lt;code&gt;Auth.controller.js&lt;/code&gt;. Inside the &lt;code&gt;/src/services&lt;/code&gt; directory create an &lt;code&gt;Auth.services.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;With all of those files created, this is what our project structure should look like now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;backend/
    - node_modules/
    - src/
        - controllers/
            - Auth.controller.js
        - models/
            - Todo.model.js
            - User.model.js
        - routes/
            - Auth.routes.js
            - index.js
        - services/
            - Auth.service.js
        - app.js
        - app.local.js
    - claudia.json
    - lambda.js
    - package-lock.json
    - package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With those files created, let's get our router setup. &lt;/p&gt;

&lt;p&gt;Let's start by editing our &lt;code&gt;src/app.js&lt;/code&gt; file. Make the following changes so that your app.js file looks like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/app.js&lt;/em&gt;&lt;/strong&gt;&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;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// morgan for logging&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;morgan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;morgan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;morgan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;// Import Routes&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./routes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we start by adding the morgan logging middleware. This will handle automatically logging to the console what requests our app receives, useful for both development and catching things that go wrong in production.&lt;/p&gt;

&lt;p&gt;Next, we tell our app to handle all routes from our &lt;code&gt;./routes/index.js&lt;/code&gt; file. You'll notice that we didn't explicitly reference the &lt;code&gt;/.routes/index.js&lt;/code&gt; file though, just the dir name. &lt;/p&gt;

&lt;p&gt;Let's go ahead and implement our routes file now. Inside &lt;code&gt;/src/routes/index.js&lt;/code&gt; add the following code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/routes/index.js&lt;/em&gt;&lt;/strong&gt;&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Router&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;authRoutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;./Auth.routes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Moved our API Root GET "Hello world!" here&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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;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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&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="c1"&gt;// Import Auth routes&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authRoutes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've moved our API Root GET request to this file to keep it organized with the other routes. We'll keep it now for testing, &lt;/p&gt;

&lt;p&gt;In the second line of &lt;code&gt;/src/routes/index.js&lt;/code&gt; we require() our &lt;code&gt;./Auth.routes.js&lt;/code&gt; file and store it as a const, &lt;code&gt;authRoutes&lt;/code&gt;. We haven't implemented that file yet either, so let's do that now.&lt;/p&gt;

&lt;p&gt;Inside &lt;code&gt;/src/routes/Auth.routes.js&lt;/code&gt; file, add the following code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/routes/Auth.routes.js&lt;/em&gt;&lt;/strong&gt;&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// TODO - implement this route fully&lt;/span&gt;
&lt;span class="c1"&gt;// POST - /api/auth/register&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/register&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;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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/register&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;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a &lt;code&gt;POST&lt;/code&gt; endpoint for &lt;code&gt;/api/auth/register&lt;/code&gt; which simply returns a string "/register" back to the requester.&lt;/p&gt;

&lt;p&gt;With the boilerplate for our routing system mostly complete. This would be a good time to test everything is working before we continue much further.&lt;/p&gt;

&lt;p&gt;Back in Postman, let's first test our "Hello world!" request to make sure that's still working from the new &lt;code&gt;routes/index.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Make sure the local dev server is running with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then use Postman to make a &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;http://localhost:3000/&lt;/code&gt; (In part 1 I promoted this to a variable &lt;code&gt;{{BASE_URL}}&lt;/code&gt;, I'll be referencing that moving forward)&lt;/p&gt;

&lt;p&gt;You should see the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm run dev

&amp;gt; dern-backend@1.0.0 dev
&amp;gt; nodemon src/app.local.js

[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/app.local.js`
App is listening on port 3000.
GET / 200 1.582 ms - 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice the output is the same as before, except the morgan middleware logged our &lt;code&gt;GET&lt;/code&gt; request. In Postman you should see the return value of "Hello world!"&lt;/p&gt;

&lt;p&gt;Let's also test our &lt;code&gt;/api/auth/register&lt;/code&gt; endpoint is working. Create a new &lt;code&gt;POST&lt;/code&gt; request in Postman for that endpoint.&lt;/p&gt;

&lt;p&gt;In Postman you should see "/register" as the response value, and the console should have logged the new &lt;code&gt;POST&lt;/code&gt; request:&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;npm run dev
...
POST /api/auth/register 200 2.381 ms - 9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next step is to setup our Controllers, these are the &lt;strong&gt;C&lt;/strong&gt; in MV*&lt;em&gt;C&lt;/em&gt;*. To briefly explain the job of Controllers, they receive the HTTP request data from the application Router. The Controller &lt;/p&gt;

&lt;h2&gt;
  
  
  TODO - Explain this better
&lt;/h2&gt;

&lt;p&gt;Add the following code to our &lt;code&gt;/src/controllers/Auth.controller.js&lt;/code&gt; file:&lt;br&gt;
&lt;strong&gt;&lt;em&gt;/src/controllers/Auth.controller.js&lt;/em&gt;&lt;/strong&gt;&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="c1"&gt;// Register New User&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// req validation would be handled here&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newUserInput&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;

    &lt;span class="c1"&gt;// TODO - Auth Service Register User&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newUserInput&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;The controller is mostly a placeholder right now, but we're saving the request body into a const &lt;code&gt;newUserInput&lt;/code&gt;. However, we haven't implemented the express.json() middleware in order to be able to access the req.body object.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;/src/app.js&lt;/code&gt; add this to lines 4 &amp;amp; 5  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/app.js&lt;/em&gt;&lt;/strong&gt;&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="c1"&gt;// Using express.json() to read req.body&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(If you've previously used body-parser for Express.js this has essentially replaced that)&lt;/p&gt;

&lt;p&gt;Next, update the &lt;code&gt;/src/routes/Auth.routes.js&lt;/code&gt; file to the following to send the request to our new Controller:  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/routes/Auth.routes.js&lt;/em&gt;&lt;/strong&gt;&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Router&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;authController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../controllers/Auth.controller&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// POST - /api/auth/register&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/register&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since this is the first time in our application that we're dealing with request body data, this is a good opportunity to test that as well.&lt;/p&gt;

&lt;p&gt;You should still have a &lt;code&gt;POST {{BASE_URL}}/api/auth/register&lt;/code&gt; request. Click on the "Body" tab for that request, and click the gray dropdown box that says "none". Change that value from "none" to "raw", then in the Blue Text dropdown that appears, select "JSON".&lt;/p&gt;

&lt;p&gt;Set the body value to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adam"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adamPass"&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;With all that set, run the request. In the console, you should see our &lt;code&gt;POST&lt;/code&gt; request logged. Additionally, the API response should just be the request body returned back to you.&lt;/p&gt;

&lt;p&gt;With that working, we can now implement the Service Layer of our application. To briefly explain the job of the service layer, the service layer is where the bulk of our application's business logic exists. This is where we'll put our Dynamoose calls to perform CRUD operations, and handle logic for validating users' accounts, passwords, etc.&lt;/p&gt;

&lt;p&gt;A major benefit of moving our business logic out of the controller (or even worse, the routes) and into a service layer, is that is makes our code much more modular and re-usable.  &lt;/p&gt;

&lt;p&gt;Let's take the Auth service we're about to implement for example. We want Users to be able to register for our app. We also want them to be able to login. However, wouldn't it be a nice feature, if after a User successfully registers for our app, they're automatically logged in.  &lt;/p&gt;

&lt;p&gt;If we were to keep all of that logic inside the controllers, we would have to copy/paste the login into the register controller as well. Not terrible at first, but it can quickly become a pain to maintain that duplicate code in two places, and goes directly against the DRY Principle (&lt;strong&gt;D&lt;/strong&gt;on't &lt;strong&gt;R&lt;/strong&gt;epeat &lt;strong&gt;Y&lt;/strong&gt;ourself).  &lt;/p&gt;

&lt;p&gt;Again, don't worry if that doesn't all make sense right now, we'll implement the service layer so you can see how it all works together.&lt;/p&gt;

&lt;p&gt;We'll need two more packages for our Auth implementation. From the &lt;code&gt;/backend&lt;/code&gt; folder install the bcryptjs, and uuid packages with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;bcryptjs uuid
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll add the following AWS SDK configuration settings to &lt;code&gt;/src/app.js&lt;/code&gt;. Below &lt;code&gt;app.use(express.json())&lt;/code&gt; add the following:&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="p"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;// Dynamoose configuration&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamoose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dynamoose&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;dynamoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;region&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="s2"&gt;us-east-1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Side Note:&lt;/strong&gt; Regarding AWS Authentication &amp;amp; Configuration -&lt;br&gt;&lt;br&gt;
On my dev machine, I export the Access Key, Secret Key, and Session Token into my terminal, which allows my application to quickly interact with AWS Cli &amp;amp; SDK services without too much configuration. If you know how to do this and can follow along as such, great.  &lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is what you would type into a bash terminal to export those variables:&lt;/em&gt;&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;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"[ACCESS_KEY_ID]"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"[ACCESS_KEY]"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SESSION_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"[SESSION_TOKEN]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise, for readers newer to AWS, I think it's probably simpler and more straight forward to configure that information in our app via code.  &lt;/p&gt;

&lt;p&gt;A major caveat of doing so is that our application will have to access sensitive information, ie our AWS ACCESS_KEY &amp;amp; SECRET_ACCESS_KEY. You should never hard-code sensitive information like keys &amp;amp; secrets into your application. Later on in this write-up, I install and configure dotenv so we can sign our JWTs with a secret.&lt;/p&gt;

&lt;p&gt;You'll need to install with npm the &lt;code&gt;dotenv&lt;/code&gt; package. Then, update your app.js file to include dotenv and configure it, ideally as early as possible in your application.&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="c1"&gt;// Dotenv config&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;dynamoose&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accessKeyId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;secretAccessKey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;region&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="s2"&gt;us-east-1&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget, you'll need a &lt;code&gt;.env&lt;/code&gt; file in the &lt;code&gt;/backend&lt;/code&gt; folder with the following values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWS_ACCESS_KEY_ID=[YOUR ACCESS KEY]
AWS_SECRET_ACCESS_KEY=[YOUR SECRET KEY]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I still have to build out and test a working example for this, but check the &lt;a href="https://github.com/akatora28/serverless-dern-todo-pt2/"&gt;github repo for pt. 2&lt;/a&gt; to see the latest code examples if you're running into issues implementing this.&lt;/p&gt;




&lt;p&gt;Then add the following to the &lt;code&gt;/src/services/Auth.service.js&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/services/Auth.service.js&lt;/em&gt;&lt;/strong&gt;&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="c1"&gt;// Import our Dynamoose User model, Bcrypt for password hashing and uuidv4&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../models/User.model&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;bcrypt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bcryptjs&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="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;v4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newUserInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// newUserInfo is req.body - so it should be a JSON object ie {"username":"adam","password":"adamPass"}&lt;/span&gt;

    &lt;span class="c1"&gt;// First, check is there's already a user registered with this username&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;existingUser&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Runs a DynamoDB scan and returns the result&lt;/span&gt;
        &lt;span class="nx"&gt;existingUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newUserInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}}).&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// If there already is a User, throw an Error&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EXISTING_USER_ERROR&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="c1"&gt;// User doesn't already exist, so let's register them&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt; 
    &lt;span class="k"&gt;try&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;uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;uuidv4&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;salt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;genSalt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&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;hashedPass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newUserInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;salt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nx"&gt;newUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newUserInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;hashedPass&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// TODO loginUser(newUser) -&amp;gt; return JWT w/ newUser&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loginUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// userInfo should be a JSON Object {"username":"adam","password":"adamPass"}&lt;/span&gt;
    &lt;span class="c1"&gt;// First, Check if the User even exists - In contrast to the above, in this case we do want there to be an existing User&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;existingUser&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;existingUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;}}).&lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// If User doesn't exist, throw an error&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INVALID_LOGIN_CREDENTIALS&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="c1"&gt;// Check if the supplied password matches the bcrypt hashed password saved in the User record&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;validPass&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// bcyrpt.compare will return true / false depending on if the passwords match&lt;/span&gt;
        &lt;span class="c1"&gt;// User.scan() always returns an array, hence why we specify existingUser[0].password below&lt;/span&gt;
        &lt;span class="nx"&gt;validPass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;existingUser&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If validPass is false, throw an error&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;validPass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INVALID_LOGIN_CREDENTIALS&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="c1"&gt;// TODO - JWTs - We do need someway for our user to stay logged in after all&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&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="s2"&gt;Login Successful&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Update the &lt;code&gt;/src/controllers/Auth.controller.js&lt;/code&gt; file:&lt;br&gt;
&lt;strong&gt;&lt;em&gt;/src/controllers/Auth.controller.js&lt;/em&gt;&lt;/strong&gt;&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;authService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;../services/Auth.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Register New User&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// req validation would be handled here - We're just assuming the request is properly formed&lt;/span&gt;
    &lt;span class="c1"&gt;// fine for a proof-of-concept, terrible in practice&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newUserInput&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;newUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newUserInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;EXISTING_USER_ERROR&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="k"&gt;return&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;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;422&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&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="s2"&gt;User already exists&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="c1"&gt;// If you don't include the above return, the code will continue executing &lt;/span&gt;
            &lt;span class="c1"&gt;// and hit the throw new Error("REGISTER_USER_ERROR") below, causing our app to crash&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userInput&lt;/span&gt; &lt;span class="o"&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;body&lt;/span&gt;

    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;existingUser&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;existingUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;authService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loginUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;INVALID_LOGIN_CREDENTIALS&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="k"&gt;return&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;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;401&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;message&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="s2"&gt;Invalid username or password&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="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingUser&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;Lastly, don't forget to add a &lt;code&gt;/api/auth/login&lt;/code&gt; endpoint to the &lt;code&gt;/src/routes/Auth.routes.js&lt;/code&gt; file, add this on lines 7 &amp;amp; 8 below the existing &lt;code&gt;/api/auth/register&lt;/code&gt; endpoint:&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="c1"&gt;// POST - /api/auth/login&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the first substantial bit of code we've written, so let's take a moment to examine what everything does. Also, I've written this to use async/await as opposed to callbacks since I think it's cleaning and easier to understand. If you're not familiar with the syntax &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await"&gt;here's some documentation that might help clarify&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Starting with the &lt;code&gt;Auth.service.js&lt;/code&gt; file, we imported our Dynamoose User model that we created earlier, we also imported bcrypt for hashing passwords, and uuidv4 to generate ids for our DynamoDB records.  &lt;/p&gt;

&lt;p&gt;Then, we created a function &lt;code&gt;registerUser&lt;/code&gt; which accepts a single Object, &lt;code&gt;newUserInfo&lt;/code&gt;, as a parameter. There's no type checking, or input validation implemented, but &lt;code&gt;newUserInfo&lt;/code&gt; should consist of a string &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;. Next in the registerUser function, we check if there is already a User registered with the supplied username, if there is we return a named error "EXISTING_USER_ERROR".&lt;/p&gt;

&lt;p&gt;If a user doesn't already exist, we precede with User creation by generating a uuid, salting &amp;amp; hashing the new user's password, then finally using the User.create() method (which is part of Dynamoose) to store the new user as a record in our DynamoDB table. &lt;/p&gt;

&lt;p&gt;Once that is complete we return the newUser Object in the response body with a default status code of 200.&lt;/p&gt;

&lt;p&gt;You'll notice that above the return line, I left a TODO comment indicating where we'll eventually call the AuthService login function (in this case it's in the same file). We'll be adding in JWT for frontend auth soon, but I wanted to include that to illustrate the benefit of implementing a service layer.&lt;/p&gt;

&lt;p&gt;For the loginUser function in our Auth Service, the code is very similar to the registerUser function, except instead of throwing an error if a user exists, we throw an error if the user doesn't exist.&lt;/p&gt;

&lt;p&gt;We also use the bcrypt.compare function to see if the user supplied a valid password. Since &lt;code&gt;Dynamoose.scan()&lt;/code&gt; returns an array, in our case the existingUser variable, we have to specify &lt;code&gt;existingUser[0].password&lt;/code&gt; when supplying the hashed password to bcrypt, otherwise existingUser.password would be undefined.&lt;/p&gt;

&lt;p&gt;In our Auth Controller file, &lt;code&gt;/src/controllers/Auth.controller.js&lt;/code&gt;, we imported our Auth Service file and saved it as a const &lt;code&gt;authService&lt;/code&gt;. We then updated, the Controller's &lt;code&gt;register&lt;/code&gt; function to make a call to the Auth Service's &lt;code&gt;registerUser&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;If the Auth Service call returns an "EXISTING_USER_ERROR" error to us, we send a 422 status and error message as a response. An important thing to note about Express is that it will continue to execute code even after a call to &lt;code&gt;res.send()&lt;/code&gt;, or &lt;code&gt;res.json()&lt;/code&gt; is made. That is why we include the &lt;code&gt;return&lt;/code&gt; statement immediately before &lt;code&gt;res.status("422")...&lt;/code&gt; is called. If we didn't have the return statement, Express would continue to the next line &lt;code&gt;throw new Error(err)&lt;/code&gt; and throw an error that would crash our app, even though we handled the error correctly.&lt;/p&gt;

&lt;p&gt;Try removing the &lt;code&gt;return&lt;/code&gt; statement from that line and sending a couple test requests if you want to see how that works.&lt;/p&gt;

&lt;p&gt;In the Auth Controller &lt;code&gt;login&lt;/code&gt; function, we make a call to the Auth Service &lt;code&gt;loginUser&lt;/code&gt; function, and same as with register, either handle the named error, or send the return value of the &lt;code&gt;authService.loginUser()&lt;/code&gt; call in the response.&lt;/p&gt;

&lt;p&gt;The last thing we updated was to add the new login endpoint &lt;code&gt;/api/auth/login&lt;/code&gt; to &lt;code&gt;Auth.routes.js&lt;/code&gt; which should be pretty self-explanatory.  &lt;/p&gt;

&lt;p&gt;With all that new code added our app is starting to shape up. We currently have a way to register new users, and also a way to validate returning users accounts &amp;amp; passwords. The final piece missing, as I mentioned earlier is some sort of authentication token so our Express REST API can know when it's dealing with an authenticated user vs an unauthenticated one.  &lt;/p&gt;




&lt;h3&gt;
  
  
  Quick aside on JWT's for API Authentication
&lt;/h3&gt;

&lt;p&gt;Without trying to go into &lt;strong&gt;&lt;em&gt;too&lt;/em&gt;&lt;/strong&gt; much detail about JWTs (JSON Web Tokens) or REST API Authentication methods here, I want to briefly explain what it is we'll be doing to add JWTs to our app, and why I chose them.&lt;/p&gt;

&lt;p&gt;Oftentimes, I feel that a lot of developers (especially in tutorials) will use JWTs just because it's latest shiny new JS toy, or because it's JS based Auth token and their writing a tutorial in JS.  &lt;/p&gt;

&lt;p&gt;While there tons more developers that choose JWTs (or different tokens) for the right reasons, I think it's beneficial to explain the pros &amp;amp; cons they offer and why I'm using it here.  &lt;/p&gt;

&lt;p&gt;JWTs are &lt;a href="https://en.wikipedia.org/wiki/Digital_signature"&gt;cryptographically signed&lt;/a&gt; using a secret key that (hopefully) only our app has access to. That means we can generate a JWT for our client, and when they send it back to us, we can verify wether or not the JWT was created by us. &lt;/p&gt;

&lt;p&gt;That also means that we never have to make a call to the database, or even store our client's JWTs in a database, in order for them to be used.&lt;/p&gt;

&lt;p&gt;This is both a pro and a con of JWTs. Assume for a minute that a hacker get's ahold of a client's JWT, they can now interact with our app as that compromised user. You might think that a simple solution is to just invalidate that JWT or add it to a &lt;code&gt;denylist&lt;/code&gt;, but remember, we don't have either of those.  &lt;/p&gt;

&lt;p&gt;The only way to &lt;em&gt;invalidate&lt;/em&gt; that Token would be to change the secret key our app is signing JWTs with, which would affect &lt;strong&gt;every user and JWT&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Since our app is simple and more of a proof-of-concept right now, we're fine using JWTs as long as we're aware of the potential security concerns. Additionally, not having to make a database call to verify a user's authentication status will work well for our current application setup.&lt;/p&gt;




&lt;p&gt;Let's go ahead and add JWT authentication into our app. Thanks to &lt;a href="https://www.digitalocean.com/community/tutorials/nodejs-jwt-expressjs"&gt;Danny Denenberg for a nice guide on simple JWT implementation in Express&lt;/a&gt;. We'll need to install two new packages, jsonwebtoken to read and create JWTs and dotenv to store our JWTs secret key in a .env file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;jsonwebtoken dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are also going to create a new directory in our &lt;code&gt;/src/&lt;/code&gt; folder, called &lt;code&gt;utils&lt;/code&gt; to store our JWT related code. Inside the newly create &lt;code&gt;/src/utils&lt;/code&gt; directory. Create a file &lt;code&gt;JWTauth.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Finally, in the &lt;code&gt;/backend&lt;/code&gt; directory (aka the project root), create a new file &lt;code&gt;.env&lt;/code&gt;. Note, if you put your &lt;code&gt;.env&lt;/code&gt; file inside &lt;code&gt;/src/&lt;/code&gt; it won't work and you'll get &lt;code&gt;undefined&lt;/code&gt; when you try to acccess any env variables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/backend/.env&lt;/em&gt;&lt;/strong&gt;&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="nx"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(In a real app you wouldn't want to use "secret" as your JWT secret, you also wouldn't want to publish that anywhere, ie Github, etc.)&lt;/p&gt;

&lt;p&gt;Update our &lt;code&gt;/src/app.js&lt;/code&gt; file to read our new .env file, add the following to lines 4, 5 &amp;amp; 6 of &lt;code&gt;app.js&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/app.js&lt;/em&gt;&lt;/strong&gt;&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="c1"&gt;// Dotenv config&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the following code to the new &lt;code&gt;/src/utils/JWTAuth.js&lt;/code&gt; file:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/utils/JWTAuth.js&lt;/em&gt;&lt;/strong&gt;&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;jwt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;jsonwebtoken&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateAccessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;username&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="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;expiresIn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2h&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;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authenticateToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&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="nx"&gt;next&lt;/span&gt;&lt;span class="p"&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;authHeader&lt;/span&gt; &lt;span class="o"&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;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorization&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nx"&gt;jwt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;JWT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&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;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;)&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;
        &lt;span class="nx"&gt;next&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;p&gt;Finally, let's update our Register User and Login User functions in the Auth Service to generate JWTs for authenticated users.&lt;/p&gt;

&lt;p&gt;Add this on line 5 of &lt;code&gt;/src/services/Auth.service.js&lt;/code&gt;, it come immediately after the previous &lt;code&gt;require()&lt;/code&gt; imports.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/services/Auth.services.js&lt;/em&gt;&lt;/strong&gt;&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;jwtAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;../utils/JWTauth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we can call the &lt;code&gt;jwtAuth.generateAccessToken()&lt;/code&gt; function inside our Service Layer to get a valid JWT for our client.&lt;/p&gt;

&lt;p&gt;First, we'll update the &lt;code&gt;loginUser&lt;/code&gt; function in Auth Service to generate our JWT.&lt;/p&gt;

&lt;p&gt;Update the final 3 lines in the loginUser function, this should start with our placeholder comment &lt;code&gt;// TODO - JWTs....&lt;/code&gt;, you can remove that comment now.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/services/Auth.services.js - loginUser()&lt;/em&gt;&lt;/strong&gt;&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="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;jwtAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateAccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;existingUser&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Additionally, update the final 3 lines of our registerUser function in the Auth Service to make a call to loginUser. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/services/Auth.service.js - regiserUser()&lt;/em&gt;&lt;/strong&gt;&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="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;loginUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;username&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;password&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newUserInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;authToken&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that code added, we can now successfully register users, then log them in and return a valid JWT. Existing users can also login with a valid username / password combination, and recieve a new valid JWT.  &lt;/p&gt;

&lt;p&gt;We've come along way to building the Auth component of our app and we're &lt;strong&gt;&lt;em&gt;almost&lt;/em&gt;&lt;/strong&gt; done. The final step is to add a new &lt;code&gt;protected route&lt;/code&gt; that will implement our &lt;code&gt;authenticateToken()&lt;/code&gt; middleware function we defined in the &lt;code&gt;JWTauth.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Open up &lt;code&gt;/src/routes/Auth.routes.js&lt;/code&gt; and update it so that is looks like the following:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;/src/routes/Auth.routes.js&lt;/em&gt;&lt;/strong&gt;&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;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;Router&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;authController&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../controllers/Auth.controller&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;jwtAuth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&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;../utils/JWTauth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// POST - /api/auth/register&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/register&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// POST - /api/auth/login&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// PROTECTED ROUTE - ALL /api/auth/protected&lt;/span&gt;
&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/protected&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jwtAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authenticateToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;protected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice that we added a new &lt;code&gt;ALL&lt;/code&gt; (this just means it will accept any valid HTTP request) endpoint at &lt;code&gt;/api/auth/protected&lt;/code&gt;, and added two functions after the route declaration. The first function is our &lt;code&gt;jwtAuth.authenticateToken&lt;/code&gt; which acts as middleware. That means that any request sent  to the &lt;code&gt;/api/auth/protected&lt;/code&gt; endpoint will first be sent to &lt;code&gt;jwtAuth.authenticateToken&lt;/code&gt; before being sent to &lt;code&gt;authController.protected&lt;/code&gt;. We haven't implemented the &lt;code&gt;protected&lt;/code&gt; function in our &lt;code&gt;authController&lt;/code&gt; so let's do that now. &lt;/p&gt;

&lt;p&gt;Add the following code to the end of our Auth Controller:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;/src/controllers/Auth.controller.js&lt;/em&gt;&lt;/strong&gt;&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="p"&gt;...&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&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="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="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Reached Protected Route&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="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/protected&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We should now be able to create a new user, receive a valid JWT, and use that JWT to authenticate and reach our protected endpoint.&lt;/p&gt;

&lt;p&gt;Let's start by confirming the endpoint is inaccessible to unauthenticated users.  &lt;/p&gt;

&lt;p&gt;Back in Postman, create a new request to the endpoint &lt;code&gt;/api/auth/protected&lt;/code&gt;. Since we used the router.all() for this endpoint you can make the request a &lt;code&gt;GET&lt;/code&gt; or a &lt;code&gt;POST&lt;/code&gt; or whatever else you'd like.&lt;/p&gt;

&lt;p&gt;Send the request through, and you should see a response "Unauthorized" with status code 401.  &lt;/p&gt;

&lt;p&gt;Next, let's test registering a new user, which will in turn test the login function, by updating the body of our &lt;code&gt;POST&lt;/code&gt; &lt;code&gt;/api/auth/register&lt;/code&gt; request to the following:&lt;/p&gt;

&lt;p&gt;(since our app checks the username field for existing users, we're updating that here.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adam2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"password"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"adamPass"&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 sending that request through, you should get response similar to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJhZGFtMiIsImlhdCI6MTY0NjI0MTI5MiwiZXhwIjoxNjQ2MjQ4NDkyfQ.-vaQXL5KzgeJ4Wer_sdMa-hWmovCezCRxXevEZvurfE"&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;If you want to examine the JWT, head on over to &lt;a href="//jwt.io"&gt;JWT.io&lt;/a&gt; and copy and paste the token value into the editor. Since the secret this token was generated with is just "secret", again that's a TERRIBLE IDEA in production, you should be able to verify the token as well.&lt;/p&gt;

&lt;p&gt;With our newly created JWT, let's copy the value, ie just this part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJhZGFtMiIsImlhdCI6MTY0NjI0MTI5MiwiZXhwIjoxNjQ2MjQ4NDkyfQ.-vaQXL5KzgeJ4Wer_sdMa-hWmovCezCRxXevEZvurfE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then add it to our Postman &lt;code&gt;/api/auth/protected&lt;/code&gt; request in the &lt;code&gt;authorization&lt;/code&gt; header. One thing to note about working with JWTs in Auth headers, is that the token itself is usually prefixed by the term "Bearer". So in Postman &amp;gt;&amp;gt; Headers &amp;gt;&amp;gt; type in "Authorization" for the header name then add the following for the value:&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="nx"&gt;Bearer&lt;/span&gt; &lt;span class="nx"&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eyJ1aWQiOiJhZGFtMiIsImlhdCI6MTY0NjI0MTI5MiwiZXhwIjoxNjQ2MjQ4NDkyfQ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;vaQXL5KzgeJ4Wer_sdMa&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;hWmovCezCRxXevEZvurfE&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that header added, resend the request. If everything goes well, instead of the "Unauthorized" response, you should now see a response body "/protected" which is what we returned in our &lt;code&gt;authController.protected&lt;/code&gt; function. You'll also notice we should have console logged the line "Reached Protected Route" to our dev console. I added this to demonstrate that the &lt;code&gt;jwtAuth.authenticateToken&lt;/code&gt; stops further code execution in the case of unauthorized users.&lt;/p&gt;

&lt;p&gt;And with that, we have now implemented a Auth system, albeit a simple one, for our application. Since we covered so much ground in this section, I think this would be a good place to take a pause. In the next section, we'll start back up with deploying our newly updated app onto AWS, and test out any issues that might occur in the cloud that we're not running into on our local dev machine.  &lt;/p&gt;

&lt;p&gt;I also decided on a new name for our Todo App, "git-er-dern", which has a 2:3 pun to word ratio. Quite impressive in my humble opinion.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>tutorial</category>
      <category>devops</category>
      <category>node</category>
    </item>
    <item>
      <title>Fully Serverless DERN Stack TODO App Pt. 1 - (DynamoDB, Express, React, Node)</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Tue, 01 Mar 2022 21:41:06 +0000</pubDate>
      <link>https://forem.com/adamkatora/fully-serverless-dern-stack-todo-app-pt-1-dynamodb-express-react-node-3dlc</link>
      <guid>https://forem.com/adamkatora/fully-serverless-dern-stack-todo-app-pt-1-dynamodb-express-react-node-3dlc</guid>
      <description>&lt;h2&gt;
  
  
  Pt. 1 - Setting up our Backend API and deploying to AWS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Update 3/2/2022&lt;/strong&gt; &lt;a href="https://dev.to/adamkatora/fully-serverless-dern-stack-todo-app-pt-2-building-out-our-api-2jpk"&gt;Pt. 2&lt;/a&gt; is now published.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/akatora28/serverless-dern-todo-pt1" rel="noopener noreferrer"&gt;Completed Pt.1 Github Repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sorry for doing a &lt;a href="https://www.freecodecamp.org/news/every-time-you-build-a-to-do-list-app-a-puppy-dies-505b54637a5d/" rel="noopener noreferrer"&gt;boring TODO app&lt;/a&gt;, I figured there were enough moving parts with this write-up between, Express, React, AWS, Serverless, etc that making a &lt;strong&gt;&lt;em&gt;very&lt;/em&gt;&lt;/strong&gt; simple application would be welcomed. I'm also assuming that for this tutorial you already have some basic experience with AWS, AWS CLI, Express.js &amp;amp; Node.js but I'll try to make everything as beginner friendly as I can.&lt;/p&gt;




&lt;p&gt;The MERN stack (MongoDB, Express, React, Node.js) is one of the most popular stacks among Node.js developers. However, this stack has a major achilles heel. &lt;/p&gt;

&lt;p&gt;It &lt;strong&gt;&lt;em&gt;requires&lt;/em&gt;&lt;/strong&gt; servers *shudders*.  &lt;/p&gt;

&lt;p&gt;Even if you do deploy your code to the cloud via a FaaS (Functions as a Service) platform, that pesky &lt;strong&gt;M&lt;/strong&gt; in the &lt;strong&gt;M&lt;/strong&gt;ERN stack, aka MongoDB needs a to be backed by a server. Either self-hosted, ie. via an EC2 instance running on AWS, or via a managed service, like &lt;a href="https://www.mongodb.com/atlas/database" rel="noopener noreferrer"&gt;MongoDB Atlas&lt;/a&gt; (which, also runs their instances on AWS EC2 but it has a very nice interface.)  &lt;/p&gt;

&lt;p&gt;What if, we could build a &lt;strong&gt;&lt;em&gt;truly&lt;/em&gt;&lt;/strong&gt; serverless Express.js API, with a React SPA Frontend?&lt;/p&gt;

&lt;p&gt;Well, now we can.  &lt;/p&gt;

&lt;p&gt;AWS offers &lt;a href="https://aws.amazon.com/dynamodb/" rel="noopener noreferrer"&gt;DynamoDB&lt;/a&gt;, a managed NoSQL database that can offer blazing-fast single-digit millisecond performance.  &lt;/p&gt;

&lt;p&gt;Additionally, the node.js library &lt;a href="https://dynamoosejs.com/getting_started/Introduction" rel="noopener noreferrer"&gt;Dynamoose&lt;/a&gt; is a modeling tool for DynamoDB that is very similar to the highly popular &lt;a href="https://mongoosejs.com/" rel="noopener noreferrer"&gt;Mongoose&lt;/a&gt; for MongoDB. Developers already familiar with the MERN stack should feel right at home using Dynamoose with minimal modifications.&lt;/p&gt;

&lt;p&gt;Plus, with a little deployment magic help from &lt;a href="https://claudiajs.com/" rel="noopener noreferrer"&gt;Claudia.js&lt;/a&gt;, we have a very easy way to build and deploy serverless Express.js apps.&lt;/p&gt;

&lt;p&gt;Finally, we'll build out a React SPA frontend, and deploy that on AWS Cloudfront so that we're getting the benefits of having our static code and assets delivered via a global CDN.  &lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Side Note:&lt;/strong&gt; I'm really playing up the "negatives" of servers &amp;amp; databases for dramatic effect. Servers actually aren't that big and scary. In the real-world, the backend needs of every application will obvioiusly vary greatly. Serverless is a great tool to have in the toolbelt, but I don't believe it should be the end-all be-all for every situation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Let's start by setting up our project directory. I'm going to start by making my project directory called &lt;code&gt;dern-todo&lt;/code&gt;, then inside that directory I'm also going to create a directory called &lt;code&gt;backend&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir dern-todo &amp;amp;&amp;amp; cd dern-todo
mkdir backend &amp;amp;&amp;amp; cd backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're going to keep all of our Express.js / Claudia.js code inside the &lt;code&gt;/backend&lt;/code&gt; directory, and when we eventually create a React frontend SPA, it will live-in, unsurpisingly, a directory called &lt;code&gt;frontend&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Make sure you're in the &lt;code&gt;backend&lt;/code&gt; directory, then initialize our backend application with NPM init.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm going to use all the NPM defaults except for 2 things. 1.) I'm changing the package name to &lt;code&gt;dern-backend&lt;/code&gt; instead of just &lt;code&gt;backend&lt;/code&gt;, which is pulled in from the directory name.&lt;/p&gt;

&lt;p&gt;2.) I'm going to change "entry point: (index.js)" to app.js, which is what we'll use for our Claudia.js setup&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See &lt;span class="sb"&gt;`&lt;/span&gt;npm &lt;span class="nb"&gt;help &lt;/span&gt;init&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;definitive documentation on these fields
and exactly what they &lt;span class="k"&gt;do&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;

Use &lt;span class="sb"&gt;`&lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &amp;lt;pkg&amp;gt;&lt;span class="sb"&gt;`&lt;/span&gt; afterwards to &lt;span class="nb"&gt;install &lt;/span&gt;a package and
save it as a dependency &lt;span class="k"&gt;in &lt;/span&gt;the package.json file.

Press ^C at any &lt;span class="nb"&gt;time &lt;/span&gt;to quit.
package name: &lt;span class="o"&gt;(&lt;/span&gt;backend&lt;span class="o"&gt;)&lt;/span&gt; dern-backend
version: &lt;span class="o"&gt;(&lt;/span&gt;1.0.0&lt;span class="o"&gt;)&lt;/span&gt; 
description: 
entry point: &lt;span class="o"&gt;(&lt;/span&gt;index.js&lt;span class="o"&gt;)&lt;/span&gt; app.js
&lt;span class="nb"&gt;test command&lt;/span&gt;: 
git repository: 
keywords: 
author: 
license: &lt;span class="o"&gt;(&lt;/span&gt;ISC&lt;span class="o"&gt;)&lt;/span&gt; 
About to write to /Users/[path]/dern-todo/backend/package.json:

&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"name"&lt;/span&gt;: &lt;span class="s2"&gt;"dern-backend"&lt;/span&gt;,
  &lt;span class="s2"&gt;"version"&lt;/span&gt;: &lt;span class="s2"&gt;"1.0.0"&lt;/span&gt;,
  &lt;span class="s2"&gt;"description"&lt;/span&gt;: &lt;span class="s2"&gt;""&lt;/span&gt;,
  &lt;span class="s2"&gt;"main"&lt;/span&gt;: &lt;span class="s2"&gt;"app.js"&lt;/span&gt;,
  &lt;span class="s2"&gt;"scripts"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"test"&lt;/span&gt;: &lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 1"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"author"&lt;/span&gt;: &lt;span class="s2"&gt;""&lt;/span&gt;,
  &lt;span class="s2"&gt;"license"&lt;/span&gt;: &lt;span class="s2"&gt;"ISC"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;


Is this OK? &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;yes&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From our &lt;code&gt;/backend&lt;/code&gt; directory, let's go ahead and install &lt;code&gt;express&lt;/code&gt;. We'll also install &lt;code&gt;nodemon&lt;/code&gt; and save it as a dev-depency to automatically restart our server on code changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;express
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save-dev&lt;/span&gt; nodemon
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, house-keeping item, I like to put all code assets into a &lt;code&gt;/src&lt;/code&gt; directory to help keep things organized.&lt;/p&gt;

&lt;p&gt;Then, after we create that directory, we'll also create our app.js file, PLUS an app.local.js which we'll use to run our app locally while testing.&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;src &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;src
&lt;span class="nb"&gt;touch &lt;/span&gt;app.js
&lt;span class="nb"&gt;touch &lt;/span&gt;app.local.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll setup a &lt;em&gt;very&lt;/em&gt; simple express to get everything setup for further development.  &lt;/p&gt;

&lt;p&gt;Thanks to &lt;a href="https://attacomsian.com/blog/express-js-aws-lambda-claudia-serverless-app" rel="noopener noreferrer"&gt;attacomsian&lt;/a&gt; for a great Claudia.js setup which I'm basing the Claudia.js portion of this write-up on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;backend/src/app.js&lt;/em&gt;&lt;/strong&gt;&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;express&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;express&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;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&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;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="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&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="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, our app.local.js file&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;backend/src/app.local.js&lt;/em&gt;&lt;/strong&gt;&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;app&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;./app&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="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&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="nx"&gt;app&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="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;`App is listening on port &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;Finally, edit &lt;code&gt;backend/package.json&lt;/code&gt; to add the following script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dern-backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&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="nl"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nodemon src/app.local.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Error: no test specified&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;amp;&amp;amp; exit 1"&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="err"&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;We can confirm that our express app works by running the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ npm run dev

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dern-backend@1.0.0 dev
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nodemon src/app.local.js

&lt;span class="o"&gt;[&lt;/span&gt;nodemon] 2.0.15
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] to restart at any &lt;span class="nb"&gt;time&lt;/span&gt;, enter &lt;span class="sb"&gt;`&lt;/span&gt;rs&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] watching path&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;: &lt;span class="k"&gt;*&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] watching extensions: js,mjs,json
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] starting &lt;span class="sb"&gt;`&lt;/span&gt;node src/app.local.js&lt;span class="sb"&gt;`&lt;/span&gt;
App is listening on port 3000.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that up and running, let's get the Claudia.js stuff configured so we can deploy our app to AWS. First, you can check if you already have Claudia installed on your system by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claudia &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see a version number returned, ie &lt;code&gt;5.14.0&lt;/code&gt;, you're all set. If not, you can install Claudia.js globally with the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; claudia
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that we're using the &lt;code&gt;-g&lt;/code&gt; flag with NPM to install the claudia package globally.&lt;/p&gt;

&lt;p&gt;After that completes, you can confirm the installation was successful by running the above &lt;code&gt;claudia --version&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;With Claudia, successfully installed, we're ready to use it to generate AWS Lambda wrapper. Run the following command from the &lt;code&gt;/backend&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claudia generate-serverless-express-proxy &lt;span class="nt"&gt;--express-module&lt;/span&gt; src/app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the following output in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;❯ claudia generate-serverless-express-proxy &lt;span class="nt"&gt;--express-module&lt;/span&gt; src/app
npm &lt;span class="nb"&gt;install &lt;/span&gt;aws-serverless-express &lt;span class="nt"&gt;-S&lt;/span&gt;

added 3 packages, and audited 171 packages &lt;span class="k"&gt;in &lt;/span&gt;2s

18 packages are looking &lt;span class="k"&gt;for &lt;/span&gt;funding
  run &lt;span class="sb"&gt;`&lt;/span&gt;npm fund&lt;span class="sb"&gt;`&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;details

found 0 vulnerabilities
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"lambda-handler"&lt;/span&gt;: &lt;span class="s2"&gt;"lambda.handler"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in our backend directory, a new file &lt;code&gt;lambda.js&lt;/code&gt; has been created. This file has configuration values for claudia.  &lt;/p&gt;

&lt;p&gt;With that in-place, we're almost ready to do an initial deploy to AWS. We'll just need to make sure we've &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-configure.html" rel="noopener noreferrer"&gt;configured the AWS CLI &amp;amp; credentials&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Sure, at the moment our express "app" is just a simple "Hello, World!", but let's make sure we're deploying early &amp;amp; often so we can work out any bugs / differences between local and AWS.&lt;/p&gt;

&lt;p&gt;Run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claudia create &lt;span class="nt"&gt;--handler&lt;/span&gt; lambda.handler &lt;span class="nt"&gt;--deploy-proxy-api&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will take a little bit of time to run, as claudia is doing some important stuff for us automatically, but you should see status updates in your terminal. Once it completes, you should see a json output with some information about our claudia app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;saving&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;configuration&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="nl"&gt;"lambda"&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="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dern-backend-executor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dern-backend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&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="nl"&gt;"api"&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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[api-id]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://[api-id].execute-api.us-east-1.amazonaws.com/latest"&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;If you're unfamiliar with AWS services such as Lambda &amp;amp; API Gateway, I'll briefly explain. Lambda is AWS's "Functions As A Service" platform, it allows to upload code (in our case node.js code) and run it on-demand, as opposed to needed to deploy, provision and manage node.js servers.  &lt;/p&gt;

&lt;p&gt;There's a variety of &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-invocation.html" rel="noopener noreferrer"&gt;ways you can invoke your Lambda function&lt;/a&gt; once it's uploaded onto AWS, but the method we're going to be using (through Claudia.js that is), is via an API Gateway.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://aws.amazon.com/api-gateway/" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt; is a service that allows to deploy API's on AWS. One of the ways API Gateway works is by allowing you to specify various endpoints, and invoke specific Lambda functions when a request is made to that endpoint. &lt;/p&gt;

&lt;p&gt;Although manually defining endpoints in AWS can be a useful method to build and deploy micro-services, Cluadia.js allows us to deploy our express app as a single Lambda function, and uses a &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-set-up-simple-proxy.html" rel="noopener noreferrer"&gt;proxy resource with a greedy path variable&lt;/a&gt; to pass the endpoints to our express app. &lt;/p&gt;

&lt;p&gt;Below is what you'll see in the AWS Console for API Gateway after Claudia finishes deploying.&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%2Fuploads%2Farticles%2Fv6gw9jca8qtcd7dnnnnz.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%2Fuploads%2Farticles%2Fv6gw9jca8qtcd7dnnnnz.jpg" alt="API Gateway view of deployed Claudia.js App"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I won't go into too much detail here about the various settings and configurations of API Gateway, but the laymans version of how to interpret the image above is that API Gateway will pass any HTTP requests, ie. &lt;code&gt;POST /api/auth/login {"user":"username":"pass":"password"}&lt;/code&gt; (that's just psuedo-code), to our Lambda function, which is an Express.js app, and the Express.js App Lambda function will handle the request the same way it would if the app were running on a server.  &lt;/p&gt;

&lt;p&gt;If that sounds complicated, don't worry, we'll run through a quick example to see how everything is working.  &lt;/p&gt;

&lt;p&gt;Throughout the rest of this write-up / series, I'm going to be using &lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; to test out our api until we build out a frontend. I'm going to keep all related requests in a Postman collection named "Serverless DERN TODO". Going into too much detail on Postman is going to be outside the scope of this tutorial, but I'll try to explain what I'm doing each step of the way in case this is your first time using the tool.&lt;/p&gt;

&lt;p&gt;If you'll recall back to our &lt;code&gt;app.js&lt;/code&gt; file from earlier, you'll remember that we setup a single &lt;code&gt;GET&lt;/code&gt; endpoint at our API root. Let's use Postman to make a &lt;code&gt;GET&lt;/code&gt; request there and confirm everything is working.&lt;/p&gt;

&lt;p&gt;The URL that we will make the request to is the url from the Claudia json output earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lambda"&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="err"&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="nl"&gt;"api"&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="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[api-id]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://[api-id].execute-api.us-east-1.amazonaws.com/latest"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;thing&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;If you need to find that information again, you can either go into the AWS API Gateway console, click "Stages", then "latest". The URL is the "Invoke URL". &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%2Fuploads%2Farticles%2Fso7ubbnhz4y17js68r8s.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%2Fuploads%2Farticles%2Fso7ubbnhz4y17js68r8s.jpg" alt="Finding API ID"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or, you'll notice that after we ran the &lt;code&gt;claudia create ...&lt;/code&gt; command earlier, a new claudia.json file was created which stores our api-id &amp;amp; the region we deployed our api to, in this case us-east-1. You can take those two values and put them into the following URL pattern&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;https://[api-id].execute-api.[aws-region].amazonaws.com/latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The &lt;code&gt;/latest&lt;/code&gt; path at the end of our Invoke URL is the "stage" from API Gateway. You can configure multiple stages (ie dev, v1, etc) but the default stage Claudia creates for us is "latest". Express will start routing after the &lt;code&gt;/latest&lt;/code&gt; stage. For example, if we made a &lt;code&gt;/login&lt;/code&gt; endpoint, the final URL would look like &lt;code&gt;https://[api-id].execute-api.[aws-region].amazonaws.com/latest/login&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;Here's our Postman &lt;code&gt;GET&lt;/code&gt; request to the API root. We get back, &lt;code&gt;Hello world!&lt;/code&gt;&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%2Fuploads%2Farticles%2F7gimtl0au0c7ghrkkh9k.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%2Fuploads%2Farticles%2F7gimtl0au0c7ghrkkh9k.jpg" alt="Postman Hello World"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don't forget, we also setup our &lt;code&gt;app.local.js&lt;/code&gt; file so that we can develop and test on our local machine. Run the &lt;code&gt;npm dev&lt;/code&gt; command to startup our express app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; dern-backend@1.0.0 dev
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; nodemon src/app.local.js

&lt;span class="o"&gt;[&lt;/span&gt;nodemon] 2.0.15
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] to restart at any &lt;span class="nb"&gt;time&lt;/span&gt;, enter &lt;span class="sb"&gt;`&lt;/span&gt;rs&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] watching path&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;: &lt;span class="k"&gt;*&lt;/span&gt;.&lt;span class="k"&gt;*&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] watching extensions: js,mjs,json
&lt;span class="o"&gt;[&lt;/span&gt;nodemon] starting &lt;span class="sb"&gt;`&lt;/span&gt;node src/app.local.js&lt;span class="sb"&gt;`&lt;/span&gt;
App is listening on port 3000.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm also going to change our Base URL to a Postman variable. Highlight, the entire url in our request, click the "Set as variable" popup that appears, then select, "Set as a new variable". I'm naming my variable &lt;code&gt;BASE_URL&lt;/code&gt; and setting the scope to the collection. Finally, click the orange "Set variable" button to save.  &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%2Fuploads%2Farticles%2F0nflgqlq9cg3unzwfzl9.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%2Fuploads%2Farticles%2F0nflgqlq9cg3unzwfzl9.jpg" alt="Promote URL to variable"&gt;&lt;/a&gt;&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%2Fuploads%2Farticles%2F7y7d652mjfgh8htp10nj.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%2Fuploads%2Farticles%2F7y7d652mjfgh8htp10nj.jpg" alt="Naming our variable"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If all went correctly, you should see the url in the &lt;code&gt;GET&lt;/code&gt; request changed to &lt;code&gt;{{BASE_URL}}&lt;/code&gt;.&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%2Fuploads%2Farticles%2Fdynvtrycxxhi7ab9mbfb.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%2Fuploads%2Farticles%2Fdynvtrycxxhi7ab9mbfb.jpg" alt="It worked"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now that we've promoted our API Gateway URL to a variable, it's time to immediately change its value to point to our localhost server.&lt;/p&gt;

&lt;p&gt;Access the variables by clicking on the name of the collection in the left-hand sidebar (mine is named Serverless DERN TODO). Then click the "variables" tab, you should see &lt;code&gt;BASE_URL&lt;/code&gt; the variable we just created. It has two fields, "INITIAL VALUE" &amp;amp; "CURRENT VALUE". Change the URL inside "CURRENT VALUE" to "&lt;a href="http://localhost:3000" rel="noopener noreferrer"&gt;http://localhost:3000&lt;/a&gt;".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IMPORTANT!&lt;/strong&gt; Don't forget to save &lt;strong&gt;BOTH&lt;/strong&gt; the collection and the &lt;code&gt;GET&lt;/code&gt; request to ensure that Postman is using the updated value for the variable. The orange circles on the request and collection tabs will let you know if you have unsaved changes.&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%2Fuploads%2Farticles%2Fhvwknzs2vdae144nl3ys.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%2Fuploads%2Farticles%2Fhvwknzs2vdae144nl3ys.jpg" alt="Updating Postman variables"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should be able to send the &lt;code&gt;GET&lt;/code&gt; request again, and see the same &lt;code&gt;Hello world!&lt;/code&gt; response. Right now, we don't have any logging in our app, so you won't see anything in the terminal running our local version of the app. The only difference you might notice is a significantly lower ms response time vs. the AWS API Gateway request, since our localhost version doesn't have very far to go.  &lt;/p&gt;

&lt;p&gt;With all that setup, we're in a good place to stop for Part 1. We've accomplished a lot so far, we have an Express.js app setup and ready to easily deploy to AWS via Claudia.js. We also have a local dev version of our Express app ready for further development and testing.  &lt;/p&gt;

&lt;p&gt;Up next is &lt;a href="https://dev.to/adamkatora/fully-serverless-dern-stack-todo-app-pt-2-building-out-our-api-2jpk"&gt;Pt. 2 of the series&lt;/a&gt; where we'll start foucsing on building out the features of our application like building some data models with Dynamoose.&lt;/p&gt;

</description>
      <category>serverless</category>
      <category>node</category>
      <category>javascript</category>
      <category>aws</category>
    </item>
    <item>
      <title>Setting up and Using BloodHound in Kali Linux</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Mon, 28 Feb 2022 23:26:24 +0000</pubDate>
      <link>https://forem.com/adamkatora/setting-up-and-using-bloodhound-in-kali-linux-23pg</link>
      <guid>https://forem.com/adamkatora/setting-up-and-using-bloodhound-in-kali-linux-23pg</guid>
      <description>&lt;p&gt;BloodHound is a tool used to visualize and identify attack paths in Active Directory Domains. Being that AD is Windows based, some of the default tools for BloodHound (ie. SharpHound ingestor) only run on Windows. Fortunately, there are tools for Unix-like systems that allow us to easily work with BloodHound on Kali and other Linux machines.  &lt;/p&gt;

&lt;p&gt;It is important to note, that you will need a set of valid Domain Credentials (ie a Username &amp;amp; Password) for the ingestor to be able to run.  &lt;/p&gt;

&lt;p&gt;I'll be working on a fresh Virtualbox install of Kali, version 2022.1-amd64.&lt;/p&gt;

&lt;h2&gt;
  
  
  BloodHound Quick Overview
&lt;/h2&gt;

&lt;p&gt;BloodHound consists of 2 main parts: 1.) an ingestor to enumerate / collect Active Directory Domain data. 2.) A GUI application to visualize the relationships between the Active Directory Domain data that was collected by the ingestor.  &lt;/p&gt;

&lt;p&gt;The GUI Application itself is an electron app backed by a neo4j graph database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingestors
&lt;/h2&gt;

&lt;p&gt;On Kali linux, the easiest way to get running with an ingestor is to use &lt;a href="https://github.com/fox-it/BloodHound.py" rel="noopener noreferrer"&gt;BloodHound.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Start by creating a new folder on your Desktop, I'm calling mine "BH_tut", this will just help us keep all our working files organized. Then, change directory to your newly created folder.&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;cd&lt;/span&gt; ~/Desktop
&lt;span class="nb"&gt;mkdir &lt;/span&gt;BH_tut &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;BH_tut
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xoriyqs3z6hj8rormre.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0xoriyqs3z6hj8rormre.jpg" alt="Setting up our Folders" width="800" height="634"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we'll need to grab the source code for BloodHound.py ingestor off github. In a web browser, open up &lt;a href="https://github.com/fox-it/BloodHound.py" rel="noopener noreferrer"&gt;https://github.com/fox-it/BloodHound.py&lt;/a&gt;. Then, click the green "Code" button and select the Copy To Clipboard icon from the dropdown.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7q1doj8i3pn70nalem5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh7q1doj8i3pn70nalem5.jpg" alt="Get the link from Github" width="800" height="643"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Back in your terminal, run the following command from within the "BH_Tut" folder to copy the source files to your local machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/fox-it/BloodHound.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe18yxcwrfr65osg9ls74.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe18yxcwrfr65osg9ls74.jpg" alt="Copy Repo to Local" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Change directory into the newly downloaded, BloodHound.py folder.&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;cd &lt;/span&gt;BloodHound.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Typically, I'd reccommend spinning up a new Python virtual enviornment and installing BloodHound.py and its dependencies into the venv as opposed to the system version of Python. However, as of Kali 2022.1 (maybe even earlier, I haven't tested any others) all the python packages required to run BloodHound.py are pre-installed on the system version of Python.  &lt;/p&gt;

&lt;p&gt;If you follow along with the DBCreator section later on, we will end up needing to create a venv there.&lt;/p&gt;




&lt;p&gt;You can confirming that BloodHound.py is working by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bloodhound.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without supplying any options to the program, you should just see the banner printed with all the available flags and options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9cuhdar3w1lsejd1zofj.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9cuhdar3w1lsejd1zofj.jpg" alt="Confirm BloodHound.py is working" width="800" height="522"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With that up and running, we can focus on enumerating our AD Domain. It's time to get some data, or as I like to call it, release the hounds! &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdwwuq2njfxofp4fnmfe.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmdwwuq2njfxofp4fnmfe.gif" alt="Release the Hounds!" width="498" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's an example command of how you'd run bloodhound.py on an Active Directory Domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./bloodhound.py &lt;span class="nt"&gt;-c&lt;/span&gt; All &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;username] &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;password] &lt;span class="nt"&gt;-dc&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;domain controller domain name] &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;domain name] &lt;span class="nt"&gt;-ns&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;nameserver ip] 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Flags:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collection Method: '-c'

&lt;ul&gt;
&lt;li&gt;We'll set to all to save everything that BloodHound can grab&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Username: '-u'

&lt;ul&gt;
&lt;li&gt;The username of an active user account in this Active Directory Domain&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Password: &lt;code&gt;-p&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;The password for the above user account&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Domain Controller (Domain Name not IP): '-dc'

&lt;ul&gt;
&lt;li&gt;The domain name of the Domain Controller (typically follows the pattern of 'dc.[domain].com'), this won't work if you just supply the IP address of the domain controller&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Domain (Domain Name not IP): '-d'

&lt;ul&gt;
&lt;li&gt;The name of the Active Directory domain (Taking the above example, this typically would be the same as above without the &lt;code&gt;dc.&lt;/code&gt; subdomain, ie '[domain].com')&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Name Server (IP Address): '-ns'

&lt;ul&gt;
&lt;li&gt;Here, you can specify a custom nameserver IP Address to resolve the above &lt;code&gt;-dc&lt;/code&gt; &amp;amp; &lt;code&gt;-d&lt;/code&gt; flags to.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;I ran my bloodhound.py on a HackTheBox machine I was working on, it's a retired box, but I still kept some info redacted to avoid any spoilers.  &lt;/p&gt;

&lt;p&gt;Here's what your directory will look similar too after successfully enumerating an AD Domain.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpoy53jxvm556p24byxq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpoy53jxvm556p24byxq.jpg" alt="Running Bloodhound.py" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, separate .json files have been created for each of the categories of collection items bloodhound.py was able to enumerate through.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Generating Sample data w/ DBCreator
&lt;/h2&gt;




&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; This shows how to generate sample data, but also covers installing neo4j, which is required to run BloodHound. If you already collected data with an ingestor, feel free to skip ahead to the point where I've written "Neo4j Installation", and make sure you install neo4j on your Kali machine.&lt;/p&gt;




&lt;p&gt;If you don't currently have an AD Domain you can run bloodhound.py in, no worries, we can use &lt;a href="https://github.com/BloodHoundAD/BloodHound-Tools/tree/master/DBCreator" rel="noopener noreferrer"&gt;BloodHound Database Creator&lt;/a&gt; to generate some sample data.&lt;/p&gt;

&lt;p&gt;Start by going to the &lt;a href="https://github.com/BloodHoundAD/BloodHound-Tools" rel="noopener noreferrer"&gt;BloodHound-Tools Github Repo&lt;/a&gt; and grabbing the clone link like we did above for BloodHound.py.&lt;/p&gt;

&lt;p&gt;Then, clone that repo into the "BH_tut" folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/BloodHoundAD/BloodHound-Tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once that finshes downloading, change directory into the "BloodHound-Tools" directory and then the "DBCreator" directory inside that&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;cd &lt;/span&gt;BloodHound-Tools
&lt;span class="nb"&gt;cd &lt;/span&gt;DBCreator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, we will create a new Python venv to install the DBCreator depencies as these aren't all in the default system Python.  &lt;/p&gt;

&lt;p&gt;Ensure that the venv module is installed on your Kali machine by running&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;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;python3 python3-venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that installed, create the new venv by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, activate the venv by running:&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;source &lt;/span&gt;venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install the dependencies for DBCreator by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; I hit an issue running the DBCreator.py &lt;code&gt;generate&lt;/code&gt; command. According to the BloodHound-Tools &lt;a href="https://github.com/BloodHoundAD/BloodHound-Tools/issues/22" rel="noopener noreferrer"&gt;github issues&lt;/a&gt;, it seems as if I wasn't the only one.  &lt;/p&gt;

&lt;p&gt;The workaround I found was to download &lt;a href="https://github.com/BloodHoundAD/BloodHound-Tools/files/6327214/DBCreator_update.zip" rel="noopener noreferrer"&gt;this updated DBCreator.py&lt;/a&gt; file, and replace the existing DBCreator.py file with that new one.  &lt;/p&gt;

&lt;p&gt;After swapping out those files I was able to sucessfully run the &lt;code&gt;generate&lt;/code&gt; command from DBCreator.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;DBCreator is now installed, but we still need a neo4j instance running for the generator to save our sample data to&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Neo4j Installation
&lt;/h2&gt;

&lt;p&gt;Install neo4j from the apt repository with:&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;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;neo4j
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcilpq65kr7zqpsuud0jy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcilpq65kr7zqpsuud0jy.jpg" alt="Installing Neo4j" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After installation completes, start neo4j with the following command:&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;sudo &lt;/span&gt;neo4j console
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4f0a70jcz25tgma6ktfw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4f0a70jcz25tgma6ktfw.jpg" alt="Starting Neo4j" width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then navigate to &lt;code&gt;localhost:7474&lt;/code&gt;. Login with the default credentials&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;username: neo4j 
password: neo4j
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm876cs5rilwn7rnbqbk3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm876cs5rilwn7rnbqbk3.jpg" alt="Neo4j First Login" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After you've successfully logged in with the default credentials, you'll be prompted to change the password. I've opted to go with &lt;code&gt;kalilinux&lt;/code&gt; for the new password. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tyo7gkec8oxktwk56km.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tyo7gkec8oxktwk56km.jpg" alt="Neo4j Change Password" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Return to the terminal that you installed DBCreator in. Ensure that the venv is activated (that's the venv/bin/activate command), then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python DBCreator.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see a banner "BloodHound Sample Database Creator" and &lt;code&gt;(CMD)&lt;/code&gt; appear at the bottom of the terminal. This is where we can set the neo4j configuration options. Type the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dbconfig
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll be promted for the DB url: the default value&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 should work, hit enter.

For DB Username, `neo4j`, the default is correct.  

For DB Password, supply the new password we created, `kalilinux`.

And finally, No, to "Use encryption".

![Running DBCreator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ednd5wk1yvciaz4vz9l.jpg)

After hitting "enter" you should see a message "Database Connection Successful!", after which, you can run the command `generate`. (If you hit an error with generate don't forget the updated DBCreator.py file from above)

![Running the DBCreator Generate Command](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iiatxblrosp0qc7imx66.jpg)

Now our neo4j database is seeded with sample data and we're ready to install the BloodHound GUI app.

___

## Installing BloodHound w/ Pre-Compiled Binaries  

**Note: Don't install the version of BloodHound from the apt repository. It's not maintained by the BloodHoun dev team and as such might be out of date**

Navigate to the [BloodHound Github Repo](https://github.com/BloodHoundAD/BloodHound) at [https://github.com/BloodHoundAD/BloodHound](https://github.com/BloodHoundAD/BloodHound). On the right-hand sidebar, you should see a heading titled "Releases". Immediately below that should be the latest release available. Click to navigate to the binaries page for that.  

Scroll down to the "Assets" list where you should see a zip download for Linux x64. Download that zip to your Kali machine.

Move or copy the downloaded file from your downloads folder, to our BH_tut folder on the Desktop. 

______
**NOTE:** For this write-up, I'm just installing BloodHound in our BH_tut folder. In the real-world, you should install programs such as this into the Linux /opt directory, ie /opt/BloodHound.
______



```bash
cp ~/Downloads/BloodHound-linux-x64.zip ~/Desktop/BH_tut/BloodHound-linux-x64.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, from the BH_tut directory, unzip the file and once it's completed unzipped, change into the newly created directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;unzip BloodHound-linux-x64.zip
&lt;span class="nb"&gt;cd &lt;/span&gt;BloodHound-linux-x64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure that the 'BloodHound' file is executable.&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;sudo chmod &lt;/span&gt;770 BloodHound
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run the program with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./BloodHound
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the BloodHound login screen and be prompted to supply the neo4j credentials that we created earlier. We kept our username the default at &lt;code&gt;neo4j&lt;/code&gt;, and set the password to &lt;code&gt;kalilinux&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5gnktfpm0zwej1xovf4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5gnktfpm0zwej1xovf4.jpg" alt="BloodHound First Login" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you followed along with the DBCreator setup, once logged in you should see a data graph with your sample data loaded up. To view some of the pre-built analysis queries, click "Search Node" dropdown, then "Analysis" tab, then choose one of the pre-built queries to view.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr4a2koc0c3ofzpq6hn96.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr4a2koc0c3ofzpq6hn96.jpg" alt="DBCreator Sample Data" width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you used an ingestor to get your data, we still have one more step to load up the data into our neo4j database.  &lt;/p&gt;

&lt;p&gt;Navigate back to your BloodHound.py folder, and find the .json files that were created earlier. With the BloodHound application window open, click and drag (hold ctrl to select multiple files) the .json files into BloodHound.&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/qvIyu0vlDdg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;You'll see an uploads progress window. Once all of the uploads reach 100% upload completion, feel free to close the window. If you click the "Search for a node" toggle dropdown, then the analysis tab, the pre-built queries should work for however much data your ingestor was able to find.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz69ge1483fgock9hprcu.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz69ge1483fgock9hprcu.jpg" alt="Ingestor Data" width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This walkthrough is certainly only scratching the surface of what's possible with BloodHound. I myself am looking forward to digging into the Analysis queries in more depth, and depending on what I learn, hopefully write a follow-up on that. Hopefully, this is able to help new pentesters looking to get started with AD &amp;amp; BloodHound though.&lt;/p&gt;

</description>
      <category>bloodhound</category>
      <category>activedirectory</category>
      <category>kalilinux</category>
    </item>
    <item>
      <title>Creating a VOD (Video On Demand) Platform with Rails &amp; FFMPEG</title>
      <dc:creator>Adam Katora</dc:creator>
      <pubDate>Wed, 06 Oct 2021 00:55:46 +0000</pubDate>
      <link>https://forem.com/adamkatora/creating-a-vod-video-on-demand-platform-with-rails-ffmpeg-34m9</link>
      <guid>https://forem.com/adamkatora/creating-a-vod-video-on-demand-platform-with-rails-ffmpeg-34m9</guid>
      <description>&lt;p&gt;If you want the TL;DR version, here's the &lt;a href="https://github.com/akatora28/rails-vod-example" rel="noopener noreferrer"&gt;github link&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Recently, I started working on a project where I wanted to allow users to upload videos, then allow them and other users to playback those videos. I was able to find numerous tutorials showing how to make Youtube clones in rails, but nearly every tutorial went along the lines of "upload video as .mp4, save the video to storage, play back video as mp4".  &lt;/p&gt;

&lt;p&gt;While this approach can work in limited instances, it has some serious drawbacks. For one, &lt;a href="https://flussonic.com/blog/news/html5-streaming/" rel="noopener noreferrer"&gt;mp4 files have no way to truly be streamed&lt;/a&gt;. Sure, there's modern tricks to fragment mp4 files to give the illusion of streaming, but this solution hardly holds up in low bandwidth network settings, and mobile support can be hit or miss.  &lt;/p&gt;

&lt;p&gt;Instead, the HTTP Live Streaming (HLS), format developed by Apple, provides a way for video to sent to the client in an adaptive format that allows quality to be automatically adjusted depending on the client's internet connection. HLS files are also segmented by default, which provides the benefit of reducing the bandwidth required to start playing video.  &lt;/p&gt;

&lt;p&gt;The below tutorial for building a rails VOD platform is by no means production ready, but it should serve as a good jumping off point for anyone who's looking to build out a more fully-featured rails video service.  &lt;/p&gt;

&lt;p&gt;So with that, let's get started.  &lt;/p&gt;




&lt;p&gt;The version of Ruby &amp;amp; Rails I'm using:&lt;br&gt;&lt;br&gt;
Ruby Version: 3.0.2&lt;br&gt;&lt;br&gt;
Rails Version: 6.1.4.1  &lt;/p&gt;

&lt;p&gt;For a sample mp4 video, I'm using &lt;a href="https://github.com/bower-media-samples/big-buck-bunny-1080p-30s/blob/master/video.mp4" rel="noopener noreferrer"&gt;this 1080p first 30s version&lt;/a&gt; of the famous blender made video, &lt;a href="https://peach.blender.org/" rel="noopener noreferrer"&gt;big buck bunny&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Setup
&lt;/h2&gt;

&lt;p&gt;The instructions I'll be providing for this setup  run the app inside a docker container.&lt;/p&gt;

&lt;p&gt;Originally, I was developing this project on MacOS, but ran into permissions issues with the streamio-ffmpeg gem accessing the system installed version of FFMPEG. Instead of wasting time troubleshooting that, I decided this was a good opportunity to dockerize my app.  &lt;/p&gt;

&lt;p&gt;To start create a folder with your desired project name, ie:&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;rails-vod-example &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;rails-vod-example


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create a Dockerfile in your project root:&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;touch &lt;/span&gt;Dockerfile


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once the  Dockerfile has been created, add the following code.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Dockerfile&lt;/em&gt;&lt;/strong&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; ruby:3.0.2&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;apt-get update &lt;span class="nt"&gt;-qq&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; ffmpeg nodejs &lt;span class="se"&gt;\
&lt;/span&gt;    npm

&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="s"&gt; . /app&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Gemfile /app/Gemfile&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; Gemfile.lock /app/Gemfile.lock&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;bundle &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; yarn

&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; ["bash"]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We'll then need to create a Gemfile with Rails in our project root so that we can run &lt;code&gt;rails new&lt;/code&gt; inside our container.&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;touch &lt;/span&gt;Gemfile


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And add the following to the newly created Gemfile (this will get overwritten once we run &lt;code&gt;rails new&lt;/code&gt; in the container)&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Gemfile&lt;/em&gt;&lt;/strong&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;source&lt;/span&gt; &lt;span class="s1"&gt;'https://rubygems.org'&lt;/span&gt;
gem &lt;span class="s1"&gt;'rails'&lt;/span&gt;, &lt;span class="s1"&gt;'~&amp;gt;6'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create a Gemfile.lock as well&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;touch &lt;/span&gt;Gemfile.lock


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next create docker-compose.yml file in your project root and add the following configuration files to it.   &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;touch &lt;/span&gt;docker-compose.yml


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;docker-compose.yml&lt;/em&gt;&lt;/strong&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
    &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash -c "rm -f tmp/pids/server.pid &amp;amp;&amp;amp; bundle exec rails s -p 3000 -b '0.0.0.0'"&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.:/app&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run this command to &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker-compose run &lt;span class="nt"&gt;--no-deps&lt;/span&gt; web rails new &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--force&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With the new project files generated run the following command which will change the ownership of the newly created rails files from root to your user. (Docker runs as root so the rails new command generates the files as the root user)&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;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt;:&lt;span class="nv"&gt;$USER&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then you'll need to reinstall the new Gemfile dependencies, run docker-compose build to re-run the bundle install&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker-compose build


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once that's complete, running docker-compose up should start the rails app and make it accessible on localhost:3000/&lt;/p&gt;




&lt;h2&gt;
  
  
  Building the bones of our app
&lt;/h2&gt;

&lt;p&gt;For storage, we're going to use the Shrine gem, and for video transcoding we'll use ffmpeg via the streamio-ffmpeg gem.&lt;/p&gt;

&lt;p&gt;Add these two lines to your Gemfile, then install them with &lt;code&gt;docker-compose build&lt;/code&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

gem 'shrine', '~&amp;gt; 3.0'
gem 'streamio-ffmpeg', '~&amp;gt; 3.0'


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we're going to use Rails scaffold to generate CRUD operations for our Video model. We add the &lt;code&gt;name:string&lt;/code&gt; to create a new name field for our video and &lt;code&gt;original_video_data:text&lt;/code&gt; to add the field Shrine needs for storing data about our uploaded file. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

rails g scaffold Video name:string original_video_data:text


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;If your curious about what the &lt;code&gt;orginal_video_data&lt;/code&gt; field is, and why it's suffixed with _data, I'd recommend reading the &lt;a href="https://shrinerb.com/docs/getting-started" rel="noopener noreferrer"&gt;Shrine Getting Started docs&lt;/a&gt; as our code so far closely follows that.  &lt;/p&gt;




&lt;p&gt;In addition to creating the controllers, models, views, etc. The scaffold command will also create the neccessary routes for our Videos to be accessible at the /videos endpoint. &lt;/p&gt;

&lt;p&gt;Let's add a default route so we don't have to type /videos everytime we want to test our application.&lt;/p&gt;

&lt;p&gt;Update your config/routes.rb code to look like the below:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;config/routes.rb&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;draw&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ss"&gt;to: &lt;/span&gt;&lt;span class="s2"&gt;"videos#index"&lt;/span&gt;
  &lt;span class="n"&gt;resources&lt;/span&gt; &lt;span class="ss"&gt;:videos&lt;/span&gt;
  &lt;span class="c1"&gt;# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Configuring Shrine:
&lt;/h2&gt;

&lt;p&gt;To configure Shrine, we need to start by creating a Shrine initializer. I'm not going to go into too much detail about how Shrine works here, but the gist of things is that the below commands create two Shrine stores, a cache and permanent.&lt;/p&gt;

&lt;p&gt;With the settings provided below Shrine is configured to use the local file system, and the cache and permanent storage are located in the Rails public/ directory and then placed into public/uploads &amp;amp; public/uploads/cache due to the prefix settings.&lt;/p&gt;

&lt;p&gt;Create a new file config/initializers/shrine.rb and add the following code:&lt;br&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;config/initializers/shrine.rb&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"shrine"&lt;/span&gt;

&lt;span class="c1"&gt;# File System Storage&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"shrine/storage/file_system"&lt;/span&gt;
&lt;span class="no"&gt;Shrine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;storages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
  &lt;span class="ss"&gt;cache: &lt;/span&gt;&lt;span class="no"&gt;Shrine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;prefix: &lt;/span&gt;&lt;span class="s2"&gt;"uploads/cache"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# temporary &lt;/span&gt;
  &lt;span class="ss"&gt;store: &lt;/span&gt;&lt;span class="no"&gt;Shrine&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Storage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;FileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;prefix: &lt;/span&gt;&lt;span class="s2"&gt;"uploads"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;       &lt;span class="c1"&gt;# permanent &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="no"&gt;Shrine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:activerecord&lt;/span&gt; 
&lt;span class="no"&gt;Shrine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:cached_attachment_data&lt;/span&gt; &lt;span class="c1"&gt;# for retaining the cached file across form redisplays &lt;/span&gt;
&lt;span class="no"&gt;Shrine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:restore_cached_data&lt;/span&gt; &lt;span class="c1"&gt;# re-extract metadata when attaching a cached file &lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;After configuring Shrine, let's test our rails app is running correctly by typing the command&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker-compose run web rails db:create db:migrate


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then start our rails server with:  &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker-compose up


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If all is well, you should see something similar to the below image:&lt;br&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3621xl49phpc0n54xz2.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs3621xl49phpc0n54xz2.png" alt="Rails App Video Index"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With everything running smoothly, the next step is to create our Uploader. Start by creating a new folder in the /app directory called uploaders, and create a new .rb file for our uploader called video_uploader.rb  &lt;/p&gt;

&lt;p&gt;In a minute, this is where we will add the processing logic for how to handle video uploads, but for now, let's start with the bare minimum by adding the following code to video_uploader.rb&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;app/uploaders/video_uploader.rb&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VideoUploader&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Shrine&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Before we go any further with the processing logic for our uploader, let's make sure it's connected to our Video model. Shrine makes this incredibly easy. In app/models/video.rb add the following code so that your model looks like the below:  &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Video&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;VideoUploader&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Attachment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:original_video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you'll recall, when we created our Video object earlier, we named the field in the database original_video_data, but in the model file we use just original_video instead. The _data suffix is a Shrine naming convention, hence the reason for it.&lt;/p&gt;

&lt;p&gt;In app/views/videos/_form.html.erb, update the form field for the original_video file field to the below code. According to Shrine docs, the hidden_field ensures that if a user updates a video object without uploading a new orginal_video file, the cached file gets used instead of being overwritten to an empty file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;app/views/videos/_form.html.erb&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:original_video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Video File"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hidden_field&lt;/span&gt; &lt;span class="ss"&gt;:original_video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_original_video_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_field&lt;/span&gt; &lt;span class="ss"&gt;:original_video&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The updated _form.html.erb should look like this:  &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;model: &lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any?&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"error_explanation"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;pluralize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt; prohibited this video from being saved:&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;ul&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;li&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;full_message&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/li&amp;gt;&lt;/span&gt;
        &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/ul&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text_field&lt;/span&gt; &lt;span class="ss"&gt;:name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"field"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;label&lt;/span&gt; &lt;span class="ss"&gt;:original_video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Video File"&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hidden_field&lt;/span&gt; &lt;span class="ss"&gt;:original_video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;value: &lt;/span&gt;&lt;span class="n"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cached_original_video_data&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;file_field&lt;/span&gt; &lt;span class="ss"&gt;:original_video&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"actions"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;%&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And finally, in our Videos Controller, we have to remember to permit the new :original_video parameter so that the file can actually be saved.  &lt;/p&gt;

&lt;p&gt;Update the video_params to this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="c1"&gt;# Only allow a list of trusted parameters through.&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;video_params&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:video&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:original_video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The full controller should now look like this:  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;app/controllers/videos_controller.rb&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VideosController&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationController&lt;/span&gt;
  &lt;span class="n"&gt;before_action&lt;/span&gt; &lt;span class="ss"&gt;:set_video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;only: &lt;/span&gt;&lt;span class="sx"&gt;%i[ show edit update destroy ]&lt;/span&gt;

  &lt;span class="c1"&gt;# GET /videos or /videos.json&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;index&lt;/span&gt;
    &lt;span class="vi"&gt;@videos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# GET /videos/1 or /videos/1.json&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# GET /videos/new&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;
    &lt;span class="vi"&gt;@video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# GET /videos/1/edit&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# POST /videos or /videos.json&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="vi"&gt;@video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&gt;"Video was successfully created."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;location: &lt;/span&gt;&lt;span class="vi"&gt;@video&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:new&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# PATCH/PUT /videos/1 or /videos/1.json&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;update&lt;/span&gt;
    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&gt;"Video was successfully updated."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:show&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :ok&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;location: &lt;/span&gt;&lt;span class="vi"&gt;@video&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;:edit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;render&lt;/span&gt; &lt;span class="ss"&gt;json: &lt;/span&gt;&lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;status: :unprocessable_entity&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# DELETE /videos/1 or /videos/1.json&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;destroy&lt;/span&gt;
    &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;
    &lt;span class="n"&gt;respond_to&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;redirect_to&lt;/span&gt; &lt;span class="n"&gt;videos_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;notice: &lt;/span&gt;&lt;span class="s2"&gt;"Video was successfully destroyed."&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="ss"&gt;:no_content&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;
    &lt;span class="c1"&gt;# Use callbacks to share common setup or constraints between actions.&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_video&lt;/span&gt;
      &lt;span class="vi"&gt;@video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Video&lt;/span&gt;&lt;span class="p"&gt;.&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;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Only allow a list of trusted parameters through.&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;video_params&lt;/span&gt;
      &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:video&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;permit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:original_video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With all the above added, we can now test that Shrine is uploading and saving our .mp4 file.  Make sure your rails server is running, then navigate to localhost:3000&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; In rails, the initializers get called... well, on initialization. That means if you make changes to your shrine.rb file, make sure to stop and restart your server so that the changes will take affect.  &lt;/p&gt;




&lt;p&gt;At the videos index, click "New Video", give your video a name, and then select an mp4 file to upload.&lt;/p&gt;

&lt;p&gt;Just remember, since we haven't done any frontend work (like adding a file upload progress bar) there won't be any feedback as the file gets uploaded. So just make sure to use a small .mp4 file for testing purposes.&lt;/p&gt;

&lt;p&gt;Once the file uploads, you should get a green confirmation message that the video was created successfully. Let's update our show view, so we can see more information about our video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;app/views/videos/show.html.erb&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notice"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;notice&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Name: &lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;@video.original_video_url&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;original_video_url&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'Edit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edit_video_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt; |
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'Back'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;videos_path&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Updating the show view to the above code and navigating to localhost:3000/videos/1 you should see page similar to the following:&lt;br&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckird52bkl9e5ft58abn.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fckird52bkl9e5ft58abn.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Shrine automatically generates a UUID for file uploads but retains the original filename in the _data field. That's why the filename is different.&lt;/p&gt;




&lt;p&gt;And if you navigate to your /public/uploads folder in your project, you should see that your .mp4 has been uploaded with it's Shrine ID as the filename. &lt;/p&gt;

&lt;p&gt;For housekeeping andtesting purposes, go ahead and hit "Back" then use the "Destroy" button to delete your file. You'll notice it removes the item from the database, and deletes the associated video file from the permanent storage. (You might notice that the cache file &amp;amp; folder is still there, but handling that is outside the scope of what I'm aiming to cover in this tutorial)&lt;/p&gt;




&lt;h2&gt;
  
  
  Building our HLS VOD Service
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt;  At the time of writing this, I had done some, albeit basic, refactoring to the code to make it a little more DRY. Instead of going step by step through the trial and error I went through to end up with this code, I've decided to just present the completed version as-is, then at the end explain some of the earlier roadblocks and why I made certain decisions&lt;/p&gt;




&lt;p&gt;With the basic CRUD and upload functionality of our VOD service in place, it's time build out the actual HLS processing logic in our VideoUploader class.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Getting started:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We'll need to extend Shrine with two plugins, processing &amp;amp; versions. The processing plugin exposes a method &lt;code&gt;process&lt;/code&gt; to our VideoUploader class that allows us to apply transforming processes to our file everytime a new file is uploaded.&lt;/p&gt;

&lt;p&gt;The versions plugin further extends the processing plugin by giving us the ability to store arrays of files in our object. The plugin being named "versions" can be a bit misleading, but as you'll see from our use-case, we can use it beyond just versioning files.&lt;/p&gt;

&lt;p&gt;At the top of the VideoUploader class, add the versions and processing plugins to Shrine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VideoUploader&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Shrine&lt;/span&gt;
  &lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:versions&lt;/span&gt;
  &lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:processing&lt;/span&gt;

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


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we're going to override the initialize method in our uploader to generate a uuid which we will use for our transcoding ouput folder &amp;amp; filename. Add the following code to your VideoUploader class below the plugins.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
    &lt;span class="vc"&gt;@@_uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we're going to override Shrine's generate_location method in order to customize the file path our transcoded videos are saved at. &lt;/p&gt;

&lt;p&gt;To briefly explain, generate_location gets called for each file that gets saved. Later in our code we'll be adding an array, called hls_playlist, to hold all the .ts and .m3u8 files that get generated by ffmpeg. So generate_location gets called for each one of those files that gets added. &lt;/p&gt;

&lt;p&gt;The generate_location function then checks if the file being saved is of type .ts or .m3u8, and if it is saves it with the name of the file. (In this case the filename will be the uuid with some additional information about the encoding options appended as we'll see later)&lt;/p&gt;

&lt;p&gt;Here's the code for generate_location to be added to the VideoUploader class.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_location&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="ss"&gt;record: &lt;/span&gt;&lt;span class="kp"&gt;nil&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="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'ts'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'m3u8'&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&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="nf"&gt;to_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next we're going to create a method to generate our HLS playlist, and add that newly created playlist to the master .m3u8 file. This method requires that an .m3u8 file already be open (which we pass in as the master_playlist param) and that a streamio-ffmpeg movie object already be created (which we pass in with the movie param)&lt;/p&gt;

&lt;p&gt;The path and uuid params are both generated from the uuid that we created in the initialize method.&lt;/p&gt;

&lt;p&gt;We set the options for ffmpeg in its own variable, and use the &lt;code&gt;custom&lt;/code&gt; attribute to pass in cli options to ffmpeg for encoding the hls stream. Using %W instead of %w allows us to pass in variables to the array via Ruby string interpolation.&lt;/p&gt;

&lt;p&gt;In the ffmpeg custom array, the flag &lt;code&gt;-vf scale=#{width}:-2&lt;/code&gt; allows us to specify a new video width while retaining the original videos aspect ratio. I'm not going to go over the rest of the ffmpeg settings in here, other than just to say these settings definitely need to be tweaked before actually being used.&lt;/p&gt;

&lt;p&gt;In the movie.transcode call the first option is the output path of the file (you can see we join the path variable to gets passed to the function, with the uuid then the transcoding settings of the file)&lt;/p&gt;

&lt;p&gt;After FFMPEG transcodes the movie, we use Ruby's Dir.glob to iterate over each of the generated .ts files and get the value of the highest bandwidth and calculate the average bandwidth before writing those and the rest of the .m3u8 information into the master.m3u8 playlist file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_hls_playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitrate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ffmpeg_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;validate: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;custom: &lt;/span&gt;&lt;span class="sx"&gt;%W(-profile:v baseline -level 3.0 -vf scale=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;:-2 -b:v &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bitrate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt; -start_number 0 -hls_time 2 -hls_list_size 0 -f hls)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;transcoded_movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-w&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bitrate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.m3u8"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ffmpeg_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;bandwidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;avg_bandwidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;avg_bandwidth_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-w&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bitrate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*.ts"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FFMPEG&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bitrate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bandwidth&lt;/span&gt;
            &lt;span class="n"&gt;bandwidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bitrate&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="n"&gt;avg_bandwidth&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bitrate&lt;/span&gt;
        &lt;span class="n"&gt;avg_bandwidth_counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;avg_bandwidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_bandwidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;avg_bandwidth_counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#EXT-X-STREAM-INF:BANDWIDTH=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bandwidth&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,AVERAGE-BANDWIDTH=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;avg_bandwidth&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,CODECS=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;avc1.640028,mp4a.40.5&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,RESOLUTION=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;transcoded_movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolution&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,FRAME-RATE=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;transcoded_movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame_rate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/uploads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcoded_movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&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;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Whew. still with me?&lt;/p&gt;

&lt;p&gt;Finally, we use the process method that we called earlier to run the video transcoding on our uploaded file. &lt;/p&gt;

&lt;p&gt;Using the versions plugin we create a variable &lt;code&gt;versions&lt;/code&gt; which is a hash that contains both the original file, and an array we're naming hls_playlist that will hold all of the .ts and .m3u8 files that ffmpeg generates.&lt;/p&gt;

&lt;p&gt;We use io.download to get the original file, then open up our new FFMPEG::Movie object as a variable &lt;code&gt;movie&lt;/code&gt;, and create the master.m3u8 playlist file and write the required first lines.&lt;/p&gt;

&lt;p&gt;Next we make a call to the method we just wrote to transcode our video, generate_hls_playlist&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;original: &lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;hls_playlist: &lt;/span&gt;&lt;span class="p"&gt;[]&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="nf"&gt;download&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tmp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir_p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exist?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FFMPEG&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;master_playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-master.m3u8"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#EXTM3U&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#EXT-X-VERSION:3&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#EXT-X-INDEPENDENT-SEGMENTS&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;generate_hls_playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;720&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2000k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;generate_hls_playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"4000k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hls_playlist&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-w*.m3u8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;m3u8&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hls_playlist&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m3u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*.ts"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hls_playlist&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
        &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rm_rf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;versions&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With all that code added, your video_uploader.rb file should look like the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;VideoUploader&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Shrine&lt;/span&gt;
    &lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:versions&lt;/span&gt;
    &lt;span class="n"&gt;plugin&lt;/span&gt; &lt;span class="ss"&gt;:processing&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;
        &lt;span class="vc"&gt;@@_uuid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;SecureRandom&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_location&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="ss"&gt;record: &lt;/span&gt;&lt;span class="kp"&gt;nil&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="n"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'ts'&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;extname&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'m3u8'&lt;/span&gt;
            &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&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="nf"&gt;to_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;extname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate_hls_playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bitrate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;ffmpeg_options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;validate: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;custom: &lt;/span&gt;&lt;span class="sx"&gt;%W(-profile:v baseline -level 3.0 -vf scale=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt;:-2 -b:v &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bitrate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sx"&gt; -start_number 0 -hls_time 2 -hls_list_size 0 -f hls)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;transcoded_movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transcode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-w&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bitrate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.m3u8"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;ffmpeg_options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;bandwidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;avg_bandwidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="n"&gt;avg_bandwidth_counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-w&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bitrate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*.ts"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FFMPEG&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bitrate&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bandwidth&lt;/span&gt;
                &lt;span class="n"&gt;bandwidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bitrate&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;
            &lt;span class="n"&gt;avg_bandwidth&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bitrate&lt;/span&gt;
            &lt;span class="n"&gt;avg_bandwidth_counter&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;

        &lt;span class="n"&gt;avg_bandwidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_bandwidth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;avg_bandwidth_counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#EXT-X-STREAM-INF:BANDWIDTH=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;bandwidth&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,AVERAGE-BANDWIDTH=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;avg_bandwidth&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,CODECS=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;avc1.640028,mp4a.40.5&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,RESOLUTION=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;transcoded_movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolution&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;,FRAME-RATE=&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;transcoded_movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame_rate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/uploads"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transcoded_movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&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;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="n"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;original: &lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;hls_playlist: &lt;/span&gt;&lt;span class="p"&gt;[]&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="nf"&gt;download&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
            &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tmp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir_p&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;unless&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exist?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;FFMPEG&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;original&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;master_playlist&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-master.m3u8"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#EXTM3U&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#EXT-X-VERSION:3&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"#EXT-X-INDEPENDENT-SEGMENTS&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;generate_hls_playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;720&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"2000k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;generate_hls_playlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"4000k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hls_playlist&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-w*.m3u8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;m3u8&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
                &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hls_playlist&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m3u8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;
            &lt;span class="no"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vc"&gt;@@_uuid&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;*.ts"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
                &lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hls_playlist&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;end&lt;/span&gt;

            &lt;span class="n"&gt;master_playlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;
            &lt;span class="no"&gt;FileUtils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rm_rf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;end&lt;/span&gt;
        &lt;span class="n"&gt;versions&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Restart your rails server, open up to the Videos index, create a new video, and let it upload.&lt;/p&gt;

&lt;p&gt;After the video uploads you should see the following rails error screen:&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwsxsb9mliydbwsjdvc1.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwsxsb9mliydbwsjdvc1.png" alt="Alt Text"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Believe it or not, this is actually what we want to see. When we added the :versions plugin to our VideoUploader class, it changed the way that we need to access the file from our erb code.&lt;/p&gt;

&lt;p&gt;Update the app/views/videos/show.html.erb to be the following. Note that since :hls_playlist is an array, we're calling .first.url, because when we created the :hls_playlist we set the master.m3u8 file to be the first element in the array.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;p&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"notice"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;notice&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;Name: &lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;p&amp;gt;&amp;lt;strong&amp;gt;&lt;/span&gt;@video.original_video[:hls_playlist].first.url&lt;span class="nt"&gt;&amp;lt;/strong&amp;gt;&lt;/span&gt; &lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;original_video&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hls_playlist&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'Edit'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edit_video_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt; |
&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="n"&gt;link_to&lt;/span&gt; &lt;span class="s1"&gt;'Back'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;videos_path&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With that update made, navigating back to the show page for your newly uploaded video should echo back the url of the master .m3u8 playlist file. That's great, but now how do we actually play it?&lt;/p&gt;

&lt;p&gt;In a real app, you'd want to pick a video player for your frontend of choice that supports HLS, but to quickly see what we're working with, I'm going to use a cdn version of HLS.js to get us up and running with a video player.&lt;/p&gt;

&lt;p&gt;Add the following code to your show view, it's the example code from the &lt;a href="https://github.com/video-dev/hls.js/" rel="noopener noreferrer"&gt;hls.js github&lt;/a&gt; modified to use our :hls_playlist uploaded file as the video source.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight erb"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdnjs.cloudflare.com/ajax/libs/hls.js/0.5.14/hls.min.js"&lt;/span&gt; &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha512-js37JxjD6gtmJ3N2Qzl9vQm4wcmTilFffk0nTSKzgr3p6aitg73LR205203wTzCCC/NZYO2TAxSa0Lr2VMLQvQ=="&lt;/span&gt; &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;"anonymous"&lt;/span&gt; &lt;span class="na"&gt;referrerpolicy=&lt;/span&gt;&lt;span class="s"&gt;"no-referrer"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;video&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"video"&lt;/span&gt; &lt;span class="na"&gt;controls&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/video&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;video&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;video&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;videoSrc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="cp"&gt;&amp;lt;%=&lt;/span&gt; &lt;span class="vi"&gt;@video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;original_video&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:hls_playlist&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt; &lt;span class="cp"&gt;%&amp;gt;&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;Hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isSupported&lt;/span&gt;&lt;span class="p"&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HLS Supported&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;hls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Hls&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoSrc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;hls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;attachMedia&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="c1"&gt;// HLS.js is not supported on platforms that do not have Media Source&lt;/span&gt;
  &lt;span class="c1"&gt;// Extensions (MSE) enabled.&lt;/span&gt;
  &lt;span class="c1"&gt;//&lt;/span&gt;
  &lt;span class="c1"&gt;// When the browser has built-in HLS support (check using `canPlayType`),&lt;/span&gt;
  &lt;span class="c1"&gt;// we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video&lt;/span&gt;
  &lt;span class="c1"&gt;// element through the `src` property. This is using the built-in support&lt;/span&gt;
  &lt;span class="c1"&gt;// of the plain video element, without using HLS.js.&lt;/span&gt;
  &lt;span class="c1"&gt;//&lt;/span&gt;
  &lt;span class="c1"&gt;// Note: it would be more normal to wait on the 'canplay' event below however&lt;/span&gt;
  &lt;span class="c1"&gt;// on Safari (where you are most likely to find built-in HLS support) the&lt;/span&gt;
  &lt;span class="c1"&gt;// video.src URL must be on the user-driven white-list before a 'canplay'&lt;/span&gt;
  &lt;span class="c1"&gt;// event will be emitted; the last video event that can be reliably&lt;/span&gt;
  &lt;span class="c1"&gt;// listened-for when the URL is not on the white-list is 'loadedmetadata'.&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;canPlayType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/vnd.apple.mpegurl&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;video&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;videoSrc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2qdmdlhj41o38o0270i.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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp2qdmdlhj41o38o0270i.png" alt="HLS video playing in Ruby on Rails App"&gt;&lt;/a&gt;&lt;br&gt;
Voila, we have HLS video!&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Notes:
&lt;/h2&gt;

&lt;p&gt;There's still a lot of work left to do for this to be anywhere near ready to go into a real application. Some of the biggest areas for improvements are adding frontend feedback on video upload progress &amp;amp; moving the video processing transcoding to a background job to free up the webserver resources.&lt;/p&gt;

&lt;p&gt;Another major area for improvement is that I've implemented no error / exception handling. Definitely something that should be added in.&lt;/p&gt;

&lt;p&gt;The actual m3u8 transcoding processes will also need to be setup as per your individual project's needs. In the example here, I transcoded the lowest quality version at a lower resolution to attempt to further cut down on bandwidth needs, but if you'd like to do something similar to youtube, where they have different resolutions ie 1080p, 720p, etc, it should be simple enough to create new arrays in the versions hash and name save the corresponding resolution to the variable. &lt;/p&gt;

&lt;p&gt;If I do a pt. 2 on this tutorial the next thing I'd like to tackle is hosting the videos on AWS S3 and using Cloudfront as a CDN. This also opens up more possibilities in terms of restricting video access only to authenticated users via signed urls and signed cookies.&lt;/p&gt;

</description>
      <category>rails</category>
      <category>ruby</category>
      <category>ffmpeg</category>
      <category>hls</category>
    </item>
  </channel>
</rss>
