<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: David Ojeda</title>
    <description>The latest articles on DEV Community by David Ojeda (@david_ojeda).</description>
    <link>https://dev.to/david_ojeda</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%2F18156%2F6e360d85-3ca0-41c4-a1e9-58fe52b10202.png</url>
      <title>DEV Community: David Ojeda</title>
      <link>https://dev.to/david_ojeda</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/david_ojeda"/>
    <language>en</language>
    <item>
      <title>Reduce Google Fonts file size by more than 80%</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Thu, 01 Oct 2020 04:16:10 +0000</pubDate>
      <link>https://dev.to/david_ojeda/reduce-google-fonts-file-size-by-more-than-80-4d7g</link>
      <guid>https://dev.to/david_ojeda/reduce-google-fonts-file-size-by-more-than-80-4d7g</guid>
      <description>&lt;p&gt;Sometimes we only need a font for a couple of words, like a logo or slogan. Why do we download the whole font file if we only need a few letters?&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how you can &lt;strong&gt;reduce your font file size by more than 80%&lt;/strong&gt;. This size reduction will be more effective the fewer characters you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Base HTML
&lt;/h2&gt;

&lt;p&gt;This is the base HTML we'll use. It contains the Roboto font and two headers. One with Roboto font-family and the other with the default font:&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="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"en"&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;meta&lt;/span&gt; &lt;span class="na"&gt;charset=&lt;/span&gt;&lt;span class="s"&gt;"UTF-8"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"viewport"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"width=device-width, initial-scale=1.0"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Document&lt;span class="nt"&gt;&amp;lt;/title&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://fonts.googleapis.com/css2?family=Roboto"&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;body&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h1&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"font-family: 'Roboto', sans-serif;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;David Ojeda&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;h2&amp;gt;&lt;/span&gt;Default font&lt;span class="nt"&gt;&amp;lt;/h2&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Measuring current font file size
&lt;/h2&gt;

&lt;p&gt;If you open this file with your browser and open the network tab of the Developer Console, you would see this:&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1601519324867%2Fd_Y4Z8vwV.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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1601519324867%2Fd_Y4Z8vwV.png" alt="Developer console "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;~657 bytes&lt;/strong&gt; for the Google Fonts API call and &lt;strong&gt;~11.1 kB&lt;/strong&gt; for the actual Roboto font.&lt;/p&gt;

&lt;p&gt;If you look at the HTML again, you can notice that we're only using the font for my name, David Ojeda. What if we could download only those letters? It turns out we can.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing the font URL
&lt;/h2&gt;

&lt;p&gt;We need to change the font URL from this:&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;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com/css2?family=Roboto"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to this:&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;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://fonts.googleapis.com/css2?family=Roboto&amp;amp;text=David%20Ojeda"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's new? The &lt;code&gt;&amp;amp;text=&lt;/code&gt; URL parameter. We'll only download the characters we need. Just remember, the text parameter value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is case-sensitive.&lt;/li&gt;
&lt;li&gt;Needs to be URL encoded. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Measuring updated font file size
&lt;/h2&gt;

&lt;p&gt;Let's measure the size of the file now:&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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1601519750312%2FAaVS9ysj7.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%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1601519750312%2FAaVS9ysj7.png" alt="Developer console "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;~359 bytes&lt;/strong&gt; for the Google Fonts API call and &lt;strong&gt;~1.3 kB&lt;/strong&gt; for the actual Roboto font. &lt;strong&gt;That's like 89% file reduction for the font&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;I found this implementation in the &lt;a href="https://developers.google.com/fonts/docs/css2" rel="noopener noreferrer"&gt;Google Fonts documentation&lt;/a&gt; days ago, and I wanted to share it so more people know about it.&lt;/p&gt;

&lt;p&gt;It saved me this 80-90% file size on my &lt;a href="https://perrodinero.blog/" rel="noopener noreferrer"&gt;personal finance blog&lt;/a&gt;, and I'm sure it can help others do the same.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thanks for reading me! 💙&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>webperf</category>
    </item>
    <item>
      <title>Queues: A Primer</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Wed, 30 Sep 2020 16:49:32 +0000</pubDate>
      <link>https://dev.to/david_ojeda/queues-a-primer-3584</link>
      <guid>https://dev.to/david_ojeda/queues-a-primer-3584</guid>
      <description>&lt;p&gt;Queues are a natural phenomenon in everyday life. You queue to buy food, to pay in the supermarket, or to get on a plane. &lt;strong&gt;But queues are also used in software to handle heavy processes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A queue can help devs like me and you store a request for later processing. It's a common practice to use a queue to handle data exports from a site, for example.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll learn
&lt;/h2&gt;

&lt;p&gt;In this post I'll compare queues in software development with queues in a fast food restaurant. You'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What's a queue&lt;/li&gt;
&lt;li&gt;Uses of a queue&lt;/li&gt;
&lt;li&gt;Caveats of queues&lt;/li&gt;
&lt;li&gt;Benefits of using a queue&lt;/li&gt;
&lt;li&gt;Common services you can use&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Queuing for food: First In, First Out (FIFO)
&lt;/h2&gt;

&lt;p&gt;When you go to a fast food place you need to first get in line to place your order.&lt;/p&gt;

&lt;p&gt;You wait in line until it's your turn to say whatever you're gonna have. Ideally, no one gets in the line; that would be disrespectful and will cause some complaints.&lt;/p&gt;

&lt;p&gt;This queue operates with a &lt;strong&gt;FIFO&lt;/strong&gt; principle. That means that the first person dispatched is the first to be in line. &lt;strong&gt;The first that gets in line is the first to get out&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You can picture each person in line as a heavy processing job request from a client to the application. An asynchronous worker processes each of these requests that are waiting in line. And by worker I mean something like a server that's not reachable by your customers.&lt;/p&gt;

&lt;p&gt;Each of these requests should respond in the order in which they were issued. &lt;strong&gt;But it's not always the case.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Preparing your food: Order might change
&lt;/h2&gt;

&lt;p&gt;Once you place your order, the cashier sends it to the cooks, so they can start preparing it. The orders get into another queue. &lt;/p&gt;

&lt;p&gt;Now, imagine there's only one cooker. You ordered a triple cheeseburger with extra onions, a vanilla milkshake, and small fries. The person after you only ordered some small fries. &lt;/p&gt;

&lt;p&gt;The cooker might say: let me serve the customer that only ordered fries first &lt;strong&gt;because that's quick&lt;/strong&gt;. That puts you after that customer in the queue even though you ordered first.&lt;/p&gt;

&lt;p&gt;In software development, queues are usually used with &lt;strong&gt;distributed systems&lt;/strong&gt;. And in these type of systems &lt;strong&gt;the order of the elements in the queue might change&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There are special types of queues that guarantee the order, but not every queue does it. So &lt;strong&gt;your application must not depend on the order of the requests&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;There's another problem you need to manage. What if there's more than one cooker and two or more of them start preparing the same order? It's very common for an application to have multiple workers that read the same queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistakes in food preparation: Handling idempotency
&lt;/h2&gt;

&lt;p&gt;If two cooks take your order they will each prepare you what you asked for. You'll end up with two cheeseburgers instead of one. There should be a way to avoid two cooks working on the same order.&lt;/p&gt;

&lt;p&gt;A caveat of a distributed queue is that &lt;strong&gt;two workers can read the same message at the same time&lt;/strong&gt;. So we say these requests should be idempotent. That is, the same operations requested multiple times should produce the same result.&lt;/p&gt;

&lt;p&gt;For example, a DELETE request on a REST API should delete a resource the first time it's called. This usually returns a 200 (OK) or 204 (No Content) response. &lt;strong&gt;The result should be the same&lt;/strong&gt; if that same request is called a second time: the deletion of the resource.&lt;/p&gt;

&lt;p&gt;Some systems only disable the resource instead of deleting it from the database. In that case, &lt;strong&gt;the result&lt;/strong&gt; (side effect in the application) and &lt;strong&gt;the response&lt;/strong&gt; (literal response from the API) will be the same.&lt;/p&gt;

&lt;p&gt;If the system deleted the resource the first time, then you'll receive a 404 (Not found) response on the second call. Here &lt;strong&gt;two identical requests had the same result, but a different response&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In both cases the delete request was idempotent. Two same requests resulted in the same outcome.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting your food: Notifying the end user
&lt;/h2&gt;

&lt;p&gt;It's common to receive a device that vibrates and lights on when your order is ready. That's how the restaurant notifies you that your order is ready for pickup.&lt;/p&gt;

&lt;p&gt;Workers also need a way to communicate to its users. Email is a great solution since most of the processing workers do is asynchronous.&lt;/p&gt;

&lt;p&gt;The only job of the queue is to receive and store requests until they're processed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why do we need queues?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Queues improve user experience
&lt;/h3&gt;

&lt;p&gt;You don't want to wait in front of the cashier until your order is ready; you don't know how long will it take. &lt;/p&gt;

&lt;p&gt;Similarly, you don't want your users to wait while looking at a loading spinner. Moreover, it can be taxing on the client-facing servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The HTTP request window is short&lt;/strong&gt;. Applications close the connection between server and client when a request is taking more than 30-60 seconds, resulting in a timeout.&lt;/p&gt;

&lt;p&gt;How can you handle a resource-intensive task that takes more than 60 seconds? &lt;strong&gt;You send it to a queue&lt;/strong&gt;, and a worker in the background can take that message and process it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queues are language agnostic
&lt;/h3&gt;

&lt;p&gt;It doesn't matter if you send a message to a queue with Python and receive it with JavaScript. The intermediary, called the message broker, will handle it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queues decouple your systems
&lt;/h3&gt;

&lt;p&gt;With queues, you can separate concerns and allow a different service or part of your application to handle specific requests.&lt;/p&gt;

&lt;p&gt;The messages in the queue will be waiting even if your workers fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Popular queue services and message brokers
&lt;/h2&gt;

&lt;p&gt;You don't have to implement the queue's internal logic, it's a solved problem. You can instead use one of these popular services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/sqs/"&gt;Amazon Simple Queue Service (SQS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rabbitmq.com/"&gt;RabbitMQ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://activemq.apache.org/"&gt;Apache ActiveMQ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've only used Amazon SQS, and it's fast to start working with it. It gets complicated as you dig deeper, but it's a great way to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I didn't tell you
&lt;/h2&gt;

&lt;p&gt;There are other factors to consider when working with queues and message brokers. The intention of this post is to only give a general overview of how the mechanism works.&lt;/p&gt;

&lt;p&gt;To give you an idea, you need to configure and/or handle, among many others:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Retry policy&lt;/li&gt;
&lt;li&gt;Failed messages&lt;/li&gt;
&lt;li&gt;Security of your queues&lt;/li&gt;
&lt;li&gt;Duration of messages in the queue&lt;/li&gt;
&lt;li&gt;Reliability and availability of your queues and message brokers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;Queues are a great addition to your software developer arsenal. If you need to do a resource-intensive task, a queue should be one of your options.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thanks for reading me! 👋&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;**Cover image taken from &lt;a href="https://undraw.co/"&gt;unDraw&lt;/a&gt;&lt;/p&gt;

</description>
      <category>queues</category>
      <category>distributedsystem</category>
      <category>beginners</category>
    </item>
    <item>
      <title>4 Time Zone Bugs I Ran Into</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Wed, 23 Sep 2020 16:25:48 +0000</pubDate>
      <link>https://dev.to/david_ojeda/4-time-zone-bugs-i-ran-into-jdj</link>
      <guid>https://dev.to/david_ojeda/4-time-zone-bugs-i-ran-into-jdj</guid>
      <description>&lt;p&gt;Software development is hard. Time zones are hard. Dealing with time zones in software development?  Yeah, &lt;strong&gt;harder&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are &lt;strong&gt;4&lt;/strong&gt; places where time zones might differ; and 4 personal bug stories for each case. I'll be referring to the same app for each story, the one I work with and maintain in my day-to-day job. This app works with Mexico's City time zone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time zone of your app
&lt;/h2&gt;

&lt;p&gt;Your app runs with a default time zone. It's usually the time zone of the server it runs on, but it can be different.&lt;/p&gt;

&lt;p&gt;In Java, you can define the time zone of the whole application when it boots. If for some reason you don't want to work with your server's timezone, this is the place to change it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug - Time in Chile off by one hour
&lt;/h3&gt;

&lt;p&gt;The app shows the date of creation of an object in many places. Three of these places were showing different times; &lt;strong&gt;two incorrect and one correct&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;One error was because I forgot to pass the user's timezone to the date formatter. Quick fix.&lt;/p&gt;

&lt;p&gt;The second error was weird. I couldn't identify why, so I compared it with the correct one.&lt;/p&gt;

&lt;p&gt;But the third case was only right because the date had been double parsed! Once on the server and a second time on the client (browser).&lt;/p&gt;

&lt;p&gt;So none of the three dates were actually correct. &lt;strong&gt;WTF?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After some headaches, I learned that each version of Java comes with a time zone data file. This file includes the latest information on the world's time zones, and the &lt;a href="https://www.iana.org/time-zones"&gt;Internet Assigned Numbers Authority (IANA)&lt;/a&gt; manages it. &lt;/p&gt;

&lt;p&gt;Time zone changes happen when governments decide to apply or not to apply daylight saving times (DST). &lt;/p&gt;

&lt;p&gt;In 2015, Chile decided to move from seasonal DST to permanent DST, and some JRE releases included this change. But then, in 2016 Chile decided to revert to how it was before; seasonal DST instead of permanent. &lt;strong&gt;What was the issue?&lt;/strong&gt; The app was using one of these JRE releases with an outdated time zone data file.&lt;/p&gt;

&lt;p&gt;You can read more about these &lt;a href="https://hi.service-now.com/kb_view.do?sysparm_article=KB0622033"&gt;DST issues with Java here&lt;/a&gt;.&lt;/p&gt;



&lt;h2&gt;
  
  
  Time zone of your server
&lt;/h2&gt;

&lt;p&gt;The operative system defines your server's time zone. I've always used Linux for production servers, and they come with UTC as the default time zone.&lt;/p&gt;

&lt;p&gt;If you need to change this time zone, make sure to do it before your application starts or it won't reflect the change.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug - App using the wrong default time zone from the server
&lt;/h3&gt;

&lt;p&gt;I was migrating some processes in our build and deployment pipeline. From configuring the app with every deploy to a pre-built AWS Amazon Machine Image (AMI) with HashiCorp's Packer.&lt;/p&gt;

&lt;p&gt;One step of the initial configuration was to change the server's time zone to America/Mexico_City, and I was aware of it. So I created a bash script that changed the time zone on the AMI we were going to use. The script worked well when I tested it on a Linux instance. No problem there.&lt;/p&gt;

&lt;p&gt;I proceeded to use this AMI in our staging environment and neither I nor my teammates noticed something off. So, to production!&lt;/p&gt;

&lt;p&gt;Customer's questions and complaints about dates behaving weird arrived minutes later 😥&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The issue?&lt;/strong&gt; The script that updated the server's time zone was failing silently and I missed double-checking it in the staging environment. The app wasn't using an explicit time zone, so it took the server's. And the server's time zone was UTC by default, and we needed America/Mexico_City. I fixed the script and, to make sure, updated the app's default time zone to the expected one.&lt;/p&gt;



&lt;h2&gt;
  
  
  Time zone of your database
&lt;/h2&gt;

&lt;p&gt;You can also change your database's time zone. I use AWS Relational Database Service (RDS) and the default time zone is UTC. You can update it from the parameter group of your cluster or individual instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug - Wrong database time zone
&lt;/h3&gt;

&lt;p&gt;Now I was doing a migration of our database. I anticipated myself by changing the database's time zone to America/Mexico_City because the app and server had it. Every part of the system should be in the same page, right? &lt;strong&gt;Wrong!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The database was perfectly okay being in UTC while the app and server were in America/Mexico_City. That's how it worked. &lt;/p&gt;

&lt;p&gt;This bug was not as critical as the previous ones because I caught it in our staging environment. &lt;/p&gt;



&lt;h2&gt;
  
  
  Time zone of your users
&lt;/h2&gt;

&lt;p&gt;If it wasn't enough, each one of your users can have a different time zone, and you have to take that into consideration when showing time sensitive-data.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bug - Many of them!
&lt;/h3&gt;

&lt;p&gt;I've encountered many bugs related to user's time zones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Missing time zone in date formatter.&lt;/li&gt;
&lt;li&gt;Incorrect time zone selection from the user.&lt;/li&gt;
&lt;li&gt;Missing DST time zone options for users to select.&lt;/li&gt;
&lt;li&gt;Storing dates with time zone modifications that get parsed again when retrieved.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Time zones are one of the most complicated topics you'll find while developing software. They're complex by themselves, and even more when you throw some code into the mix.&lt;/p&gt;

&lt;p&gt;I hope these short stories can help you avoid my mistakes in the future 🙌🏼&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Thanks for reading me! 💙&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>timezones</category>
      <category>programming</category>
    </item>
    <item>
      <title>I finished the #100DaysOfCode challenge in 140 days, here are my thoughts</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Fri, 07 Aug 2020 00:00:00 +0000</pubDate>
      <link>https://dev.to/david_ojeda/i-finished-the-100daysofcode-challenge-in-140-days-here-are-my-thoughts-52j1</link>
      <guid>https://dev.to/david_ojeda/i-finished-the-100daysofcode-challenge-in-140-days-here-are-my-thoughts-52j1</guid>
      <description>&lt;p&gt;I started the #100DaysOfCode challenge on March 21st and finished it today, August 7th. &lt;strong&gt;FINALLY!&lt;/strong&gt; 🥳 And yeah, that's way more than 100 days 🙃&lt;/p&gt;

&lt;p&gt;For those who don't know, the #100DaysOfCode challenge consists of &lt;strong&gt;coding at least one hour every day for the next 100 days&lt;/strong&gt;. Every day during this period you also must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tweet your progress using the #100DaysOfCode hashtag.&lt;/li&gt;
&lt;li&gt;Engage with at least two other people doing the challenge.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose of the challenge is to help you form a habit and to meet other people doing the same. &lt;a href="https://www.100daysofcode.com/"&gt;Check the full challenge description on their official page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When I started this challenge, I was at a point in life where I was looking to improve many of my current habits and to be more active on Twitter, and the challenge was a mix of both.&lt;/p&gt;

&lt;p&gt;I want to share some thoughts about this journey and the challenge itself since &lt;strong&gt;I have mixed feelings&lt;/strong&gt;. Before I go on, I want to say that this is &lt;strong&gt;my&lt;/strong&gt; perspective. The perspective from an experienced dev who already codes in its 9-5 and has multiple side projects, not all code related.&lt;/p&gt;

&lt;p&gt;Let's get to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public commit is real
&lt;/h2&gt;

&lt;p&gt;Yelling out loud that you'll commit and achieve something it's a powerful way to actually do it. And the challenge starts just like that, committing in public. It can help you build a habit if you didn't have one because it creates a sense of &lt;strong&gt;accountability&lt;/strong&gt;. Even if no one really cares.&lt;/p&gt;

&lt;p&gt;A more in deep view of this fact is in SWYX &lt;a href="https://www.swyx.io/writing/learn-in-public/"&gt;Learn In Public article&lt;/a&gt;. Highly recommended read 👌🏼&lt;/p&gt;

&lt;h2&gt;
  
  
  You meet great people along the way
&lt;/h2&gt;

&lt;p&gt;The challenge invites you to share and engage with other people going through the same journey, and you end up creating bonds. I want to thank them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Alex was my accountability buddy from the very beginning. He's a humble, great developer who's constantly learning and improving his craft. &lt;/li&gt;
&lt;/ul&gt;


&lt;div class="ltag__user ltag__user__id__20797"&gt;
    &lt;a href="/chiubaca" class="ltag__user__link profile-image-link"&gt;
      &lt;div class="ltag__user__pic"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9gXX3bpM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--Xgw_3sbY--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/20797/6f574203-af3d-49af-beaf-64c6eee5a9a6.jpg" alt="chiubaca image"&gt;
      &lt;/div&gt;
    &lt;/a&gt;
  &lt;div class="ltag__user__content"&gt;
    &lt;h2&gt;
&lt;a class="ltag__user__link" href="/chiubaca"&gt;Alex Chiu&lt;/a&gt;Follow
&lt;/h2&gt;
    &lt;div class="ltag__user__summary"&gt;
      &lt;a class="ltag__user__link" href="/chiubaca"&gt;Bringing ideas to life with code 🪄 | { JavaScript , TypeScript } = 👨‍💻 |  Learning in public 📖 | Building for fun &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Annie and I engaged in some of our tweets for the challenge, and it's been a pleasure to see her Twitter following skyrocket. &lt;a href="https://twitter.com/anniebombanie_"&gt;Do check her out&lt;/a&gt; for some mind-blowing CSS illustrations 🤯&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://twitter.com/AlfredoCambera?s=20"&gt;Alfredo&lt;/a&gt; and I are also accountability buddies. He loves DevOps related stuff and he's on his way to write consistently!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  It's daunting to do anything 100 times in a row
&lt;/h2&gt;

&lt;p&gt;Starting to form a habit by doing one thing, for at least one hour, for 100 days in a row, is like rolling out a snowball uphill. It will eventually grow so big that it will crush you.&lt;/p&gt;

&lt;p&gt;When I formed my habit of working out, it didn't start by saying: "I'm gonna exercise for one hour for the next 100 days". That's exactly how you &lt;strong&gt;don't create a habit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You'll probably start highly motivated the first few weeks, but believe me, life gets in the way. You'll eventually start to fall off because it's a &lt;strong&gt;HUGE&lt;/strong&gt; commitment. And it's especially hard if you are just starting out as a developer.&lt;/p&gt;

&lt;p&gt;It's like starting to run before you crawl.&lt;/p&gt;

&lt;p&gt;Instead, to create my exercise habit I said: "I'm gonna exercise for at least five minutes, three times a week". The secret is that five minutes is more manageable, and for that reason more likely to happen.&lt;/p&gt;

&lt;p&gt;Once you start, you'll find yourself doing more than five minutes because, well, you're already there with your gym clothes on. You build up from there, and now the snowball rolls downhill.&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing one day is not the end of the world
&lt;/h2&gt;

&lt;p&gt;I wish this previous sentence was part of the challenge's website.&lt;/p&gt;

&lt;p&gt;Whenever I couldn't find a time slot to work on the challenge I felt &lt;strong&gt;anxious&lt;/strong&gt;. Even if no one would even notice that I missed one day. Not even your all-time-friends: the bots.&lt;/p&gt;

&lt;p&gt;But you already committed publicly to the challenge! You feel pressured. And many days I would open my computer, do some silly change to my code, and tweet about it. Damn, I even felt stupid sharing changes so small that were nowhere near the 1-hour commit I should be doing:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GkWeplbg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/EZFcsOaWsAAqpEi.jpg" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--N4Ve6Wn6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1248350605518819329/jOR7mx-F_normal.jpg" alt="David Ojeda 🇲🇽 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        David Ojeda 🇲🇽
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @davidojedal
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Day 66 of &lt;a href="https://twitter.com/hashtag/100DaysOfCode"&gt;#100DaysOfCode&lt;/a&gt;&lt;br&gt;&lt;br&gt;Today I only added a small gradient to my social links 😅&lt;br&gt;&lt;br&gt;I'll probably finish this section tomorrow 💪🏼 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      06:41 AM - 28 May 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1265895861688688642" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1265895861688688642" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1265895861688688642" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;p&gt;That being said, take a break when needed. Nothing's gonna happen if you miss one or a couple of days. It's easy to burnout if you're also doing many other things at once. &lt;strong&gt;Your health is more important&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  I learned A LOT
&lt;/h2&gt;

&lt;p&gt;Not all 100 days of the challenge were related to a single subject or programming language; it was a huge mix of software-related work. Here are some things I did and learned during this journey.&lt;/p&gt;

&lt;h3&gt;
  
  
  Design
&lt;/h3&gt;

&lt;p&gt;This is one of my weakest points, so I started with it. Color palettes, Figma and Tailwind CSS:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bz98NKKx--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/ET4u0-6WAAARsog.png" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--N4Ve6Wn6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1248350605518819329/jOR7mx-F_normal.jpg" alt="David Ojeda 🇲🇽 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        David Ojeda 🇲🇽
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @davidojedal
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Day 4 of &lt;a href="https://twitter.com/hashtag/100DaysOfCode"&gt;#100DaysOfCode&lt;/a&gt;&lt;br&gt;&lt;br&gt; - Settled on a color palette for my side-project: Indigo and green as primary/accent colors.&lt;br&gt;&lt;br&gt;I don't know much about design, so I will change them later if not convinced- already tried four different palettes 😢. Dark theme colors still pending 🌑 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      16:07 PM - 24 Mar 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1242483155401048065" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1242483155401048065" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1242483155401048065" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  Nina, a writing app (side project)
&lt;/h3&gt;

&lt;p&gt;A web app to help me standardize my writing process:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/david_ojeda" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ReoN9sRQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--nskGqK3s--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/18156/6e360d85-3ca0-41c4-a1e9-58fe52b10202.png" alt="david_ojeda"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/david_ojeda/blog-post-writing-app-demo-83k" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Blog post writing app demo&lt;/h2&gt;
      &lt;h3&gt;David Ojeda ・ Mar 28 '20 ・ 1 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#showdev&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#writing&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h3&gt;
  
  
  Udacity's Front End Developer Nanodegree
&lt;/h3&gt;

&lt;p&gt;I finished it in less than a month to take advantage of a discount 😁:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
      &lt;div class="ltag__twitter-tweet__media"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iLMkvr7N--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/EVxxBhmWoAEntK6.jpg" alt="unknown tweet media content"&gt;
      &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--N4Ve6Wn6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1248350605518819329/jOR7mx-F_normal.jpg" alt="David Ojeda 🇲🇽 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        David Ojeda 🇲🇽
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @davidojedal
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Day 26 of &lt;a href="https://twitter.com/hashtag/100DaysOfCode"&gt;#100DaysOfCode&lt;/a&gt;&lt;br&gt;&lt;br&gt;I graduated from &lt;a href="https://twitter.com/udacity"&gt;@udacity&lt;/a&gt;'s front end nanodegree 👨🏼‍🎓🥳&lt;br&gt;&lt;br&gt;It was a nice refresher of SCSS, JavaScript, and Webpack. I learned a lot!  Thanks Udacity for this free month 🤘🏼 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      04:11 AM - 17 Apr 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1251000341128187906" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1251000341128187906" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1251000341128187906" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  AWS CDK
&lt;/h3&gt;

&lt;p&gt;I started another Udacity's nanodegree because they gave me another free month, this one was for DevOps. I couldn't finish it within the free month so I dropped from it, but I managed to learn anyways:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--N4Ve6Wn6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1248350605518819329/jOR7mx-F_normal.jpg" alt="David Ojeda 🇲🇽 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        David Ojeda 🇲🇽
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @davidojedal
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Day 49-51 of &lt;a href="https://twitter.com/hashtag/100DaysOfCode"&gt;#100DaysOfCode&lt;/a&gt;&lt;br&gt;&lt;br&gt;Managed to deploy a web app using the &lt;a href="https://twitter.com/hashtag/AWS"&gt;#AWS&lt;/a&gt; CDK.&lt;br&gt;&lt;br&gt;71 lines of TypeScript ➡️ 773 lines of CloudFormation template 🤯&lt;br&gt;&lt;br&gt;The app template includes: VPC, S3 bucket, ASG, launch configuration, ALB, custom user data, IAM role and security group 🔐
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      00:13 AM - 12 May 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1260000143845330944" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1260000143845330944" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1260000143845330944" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  Personal blog re-design
&lt;/h3&gt;

&lt;p&gt;It was time for a new design for my personal blog, and I wanted to try Eleventy for a while:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--N4Ve6Wn6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1248350605518819329/jOR7mx-F_normal.jpg" alt="David Ojeda 🇲🇽 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        David Ojeda 🇲🇽
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @davidojedal
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Day 83 of &lt;a href="https://twitter.com/hashtag/100DaysOfCode"&gt;#100DaysOfCode&lt;/a&gt;&lt;br&gt;&lt;br&gt;New blog design is finally out!! 🥳&lt;br&gt;&lt;br&gt;Check it at &lt;a href="https://t.co/1UXXFxuddq"&gt;davidojeda.dev&lt;/a&gt;&lt;br&gt;&lt;br&gt;I suck at design btw 😅 It's missing many details, but I needed to release it to continue creating content.&lt;br&gt;&lt;br&gt;It looks OK in mobile. It's missing animations on table/desktop size 👀
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      06:23 AM - 25 Jun 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1276038199668871168" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1276038199668871168" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1276038199668871168" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h3&gt;
  
  
  Personal finances blog re-design
&lt;/h3&gt;

&lt;p&gt;I also have a personal finance blog in Spanish, it's called &lt;a href="https://www.perrodinero.blog/"&gt;Perro Dinero&lt;/a&gt; 🐕💰. I use Squarespace because at the beginning all I wanted to do was to focus on writing. Now that I have the habit, I want more freedom, so I'm moving to Eleventy too:&lt;/p&gt;


&lt;blockquote class="ltag__twitter-tweet"&gt;
    &lt;div class="ltag__twitter-tweet__media ltag__twitter-tweet__media__two-pics"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qUT2zswB--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/media/EeolAh2XgAEY4sT.png" alt="unknown tweet media content"&gt;
    &lt;/div&gt;

  &lt;div class="ltag__twitter-tweet__main"&gt;
    &lt;div class="ltag__twitter-tweet__header"&gt;
      &lt;img class="ltag__twitter-tweet__profile-image" src="https://res.cloudinary.com/practicaldev/image/fetch/s--N4Ve6Wn6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://pbs.twimg.com/profile_images/1248350605518819329/jOR7mx-F_normal.jpg" alt="David Ojeda 🇲🇽 profile image"&gt;
      &lt;div class="ltag__twitter-tweet__full-name"&gt;
        David Ojeda 🇲🇽
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__username"&gt;
        @davidojedal
      &lt;/div&gt;
      &lt;div class="ltag__twitter-tweet__twitter-logo"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ir1kO05j--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-f95605061196010f91e64806688390eb1a4dbc9e913682e043eb8b1e06ca484f.svg" alt="twitter logo"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__body"&gt;
      Day 97 of &lt;a href="https://twitter.com/hashtag/100DaysOfCode"&gt;#100DaysOfCode&lt;/a&gt; &lt;br&gt;&lt;br&gt;Still migrating my blog posts to Markdown 😅&lt;br&gt;&lt;br&gt;Also did some minor style changes to my blog; liking it a lot so far! 
    &lt;/div&gt;
    &lt;div class="ltag__twitter-tweet__date"&gt;
      05:40 AM - 05 Aug 2020
    &lt;/div&gt;


    &lt;div class="ltag__twitter-tweet__actions"&gt;
      &lt;a href="https://twitter.com/intent/tweet?in_reply_to=1290885451990147072" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fFnoeFxk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-reply-action-238fe0a37991706a6880ed13941c3efd6b371e4aefe288fe8e0db85250708bc4.svg" alt="Twitter reply action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/retweet?tweet_id=1290885451990147072" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--k6dcrOn8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-retweet-action-632c83532a4e7de573c5c08dbb090ee18b348b13e2793175fea914827bc42046.svg" alt="Twitter retweet action"&gt;
      &lt;/a&gt;
      &lt;a href="https://twitter.com/intent/like?tweet_id=1290885451990147072" class="ltag__twitter-tweet__actions__button"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SRQc9lOp--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/twitter-like-action-1ea89f4b87c7d37465b0eb78d51fcb7fe6c03a089805d7ea014ba71365be5171.svg" alt="Twitter like action"&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Enjoy the journey
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Engage and learn from others&lt;/strong&gt;. I believe that's the most valuable takeaway of this challenge.&lt;/p&gt;

&lt;p&gt;The rules and hashtag encourage you to comment on other's people work; positive vibes only 🤗. You meet new people, you learn from them, you expand your network. Do what you like, slow the pace if needed, but always have fun and enjoy the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;The #100DaysOfCode challenge is a great initiative for new developers, it can help them to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a coding habit.&lt;/li&gt;
&lt;li&gt;Meet new people.&lt;/li&gt;
&lt;li&gt;Learn new languages.&lt;/li&gt;
&lt;li&gt;Showcase their work.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the challenge is more than just coding, it's about sharing and community ❤️&lt;/p&gt;

&lt;p&gt;I couldn't keep the 100 days streak or something even close because I already code every weekday. There were times when I didn't want to code on weekends, I wanted to disconnect. That's totally fine for me, but it didn't stop my anxiety.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So, was a great experience?&lt;/strong&gt; &lt;em&gt;It was!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If I went back in time would I do it again?&lt;/strong&gt; &lt;em&gt;I would!&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Am I gonna do round 2?&lt;/strong&gt; &lt;em&gt;Not at all.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I recommend it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you're starting out as a developer or want to create a side project or habit and you need public accountability, go for it!&lt;/p&gt;

&lt;p&gt;If you're an experienced developer with an already ongoing side project, the challenge might not be the best place to put your efforts on.&lt;/p&gt;




&lt;p&gt;Thanks a lot for reading me, and I hope these words can help you through your journey, be it that you're already on it or planning to start 👋&lt;/p&gt;

</description>
      <category>100daysofcode</category>
      <category>learning</category>
    </item>
    <item>
      <title>Blog post writing app demo</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Sat, 28 Mar 2020 18:08:52 +0000</pubDate>
      <link>https://dev.to/david_ojeda/blog-post-writing-app-demo-83k</link>
      <guid>https://dev.to/david_ojeda/blog-post-writing-app-demo-83k</guid>
      <description>&lt;p&gt;Hello Devs 👋🏼&lt;/p&gt;

&lt;p&gt;I want to showcase an app I've been working on for the past few weeks. It aims to solve the pains I have when writing my blog posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I can't remember the Markdown syntax to create a link or image. Is it () or [] first? 🤔&lt;/li&gt;
&lt;li&gt;Different post destinations use different formats; HTML, Markdown, etc.&lt;/li&gt;
&lt;li&gt;I don't like to use apps that don't respect my privacy, e.g., Grammarly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I made a video tour of my work so far; bare with me please since I'm terrible at video 😅&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/NICjX4w85jg"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h1&gt;
  
  
  A brief summary:
&lt;/h1&gt;

&lt;p&gt;A &lt;strong&gt;blog post writing app&lt;/strong&gt; to &lt;strong&gt;write your content once&lt;/strong&gt;, and &lt;strong&gt;publish it anywhere&lt;/strong&gt; you want.&lt;/p&gt;

&lt;p&gt;Main features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No specific syntax required, write with common text processor tools 👌🏼.&lt;/li&gt;
&lt;li&gt;Grammar and insensitive checks out of the box; nothing leaves your browser 🖍.&lt;/li&gt;
&lt;li&gt;Automatic browser storage to avoid losing your content 🔄.&lt;/li&gt;
&lt;li&gt;Easy and intuitive image uploads 🌠.&lt;/li&gt;
&lt;li&gt;Post &lt;strong&gt;directly to DEV.to as a draft&lt;/strong&gt; 😱.&lt;/li&gt;
&lt;li&gt;Export as Markdown or HTML ⬇️.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd like for this app to also help me follow a template or style, which I'm still trying to find. I'll see how to add that in the near future.&lt;/p&gt;

&lt;p&gt;Any feedback is really appreciated! 🙏🏼 And if you want to try it out, let me know! &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE: My app has a name! It's called Nina, like my dog 🐕.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Thanks a lot 💙&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>writing</category>
    </item>
    <item>
      <title>AWS S3: The Basics</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Thu, 26 Mar 2020 18:56:39 +0000</pubDate>
      <link>https://dev.to/david_ojeda/aws-s3-the-basics-1e4g</link>
      <guid>https://dev.to/david_ojeda/aws-s3-the-basics-1e4g</guid>
      <description>&lt;p&gt;&lt;em&gt;Disclaimer: This is the second revision of a &lt;a href="https://dev.to/david_ojeda/aws-s3-pt-1---the-basics-55bp"&gt;post I did before about this same topic&lt;/a&gt;. I feel that I could improve it, and now really commit to expanding on it in a series.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Storing and downloading files and data is a common activity for most applications. Where do you centralize this information? Amazon &lt;strong&gt;Simple Storage Service (S3)&lt;/strong&gt; is a great option!   &lt;/p&gt;

&lt;p&gt;In this post we'll learn the following:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  What Amazon S3 is.&lt;/li&gt;
&lt;li&gt;  How it works at a high level.&lt;/li&gt;
&lt;li&gt;  Why you might need it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's get to it!&lt;/p&gt;

&lt;h1&gt;
  
  
  What is S3?
&lt;/h1&gt;

&lt;p&gt;S3 is the object storage solution of &lt;strong&gt;Amazon Web Services&lt;/strong&gt; (&lt;strong&gt;AWS&lt;/strong&gt;) to reliably store and retrieve data. Some concrete examples of things you can store include:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Your entire static web site:

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;HTML, CSS, JS&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  Assets for your apps for later retrieval through a Content Delivery Network:

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Images, videos&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  Build files to keep a history and implement rollbacks:

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;.zip, .war&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  Data exports for your customers:

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;CSV, XLS, JSON&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;  Logs for later analytics processing:

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;.log, .txt&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;S3 is also one of the most used services of AWS since it provides integrations with many of its other offerings. &lt;/p&gt;

&lt;h1&gt;
  
  
  How is data stored?
&lt;/h1&gt;

&lt;p&gt;S3 stores our data as &lt;strong&gt;objects&lt;/strong&gt; inside &lt;strong&gt;buckets&lt;/strong&gt;. You can see &lt;strong&gt;buckets&lt;/strong&gt; as a top-level directory on your drive, followed by a &lt;strong&gt;key&lt;/strong&gt;, which is the path to our &lt;strong&gt;object&lt;/strong&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%2Fd1f6qu3m1nxo77.cloudfront.net%2Ftmp%2F2020-03-10%2F1583820501981-image.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%2Fd1f6qu3m1nxo77.cloudfront.net%2Ftmp%2F2020-03-10%2F1583820501981-image.png" alt="S3 directory structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we have objects in our buckets, we can retrieve them in many different ways. The following are the most common: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  From the AWS console.&lt;/li&gt;
&lt;li&gt;  With the AWS SDKs.&lt;/li&gt;
&lt;li&gt;  Through the REST API.&lt;/li&gt;
&lt;li&gt;  With a signed URL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll talk about each one in a different post.   &lt;/p&gt;

&lt;p&gt;Now, in terms of security, you probably don't want all your files to be publicly available. Data leaks are common, and it happens because customers leave their S3 buckets public.  &lt;/p&gt;

&lt;p&gt;S3 allows us to create policies and rules to explicitly define who has access to each bucket and/or object in our account. The available options to restrict/allow access are overwhelming and often confusing, even to experienced people, that's why you see many data leaks.  &lt;/p&gt;

&lt;p&gt;I will not dive into the permissions options in this post. Right now, our takeaway is that you can, somehow, restrict access to your data.  &lt;/p&gt;

&lt;h1&gt;
  
  
  S3 Consistency model
&lt;/h1&gt;

&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Consistency_model" rel="noopener noreferrer"&gt;consistency model&lt;/a&gt; is a set of rules that, if followed, guarantee consistent results of reading, writing and/or updating your data.  &lt;/p&gt;

&lt;p&gt;S3's consistency model is called &lt;strong&gt;Read-after-Write&lt;/strong&gt; consistency. For example, if you issue a PUT request to create an object on a bucket, the next GET request will &lt;strong&gt;ALWAYS&lt;/strong&gt; have the desired object.  &lt;/p&gt;

&lt;p&gt;However, if you try to issue requests in the following order, immediately one after the other:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; GET, when the object doesn't exist&lt;/li&gt;
&lt;li&gt; PUT &lt;/li&gt;
&lt;li&gt; GET &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You &lt;em&gt;MIGHT&lt;/em&gt; not found the desired object. The consistency model when issuing the request in this order is now called &lt;strong&gt;eventual&lt;/strong&gt; consistency. In other words:  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Read-after-Write consistency&lt;/strong&gt;:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  PUT /my-bucket/photo.png -&amp;gt; 200 &lt;em&gt;ok&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  GET /my-bucket/photo.png -&amp;gt; 200 &lt;em&gt;ok&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Eventual consistency&lt;/strong&gt;:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  GET /my-bucket/photo.png -&amp;gt; 404 &lt;em&gt;not found&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  PUT /my-bucket/photo.png -&amp;gt; 200 &lt;em&gt;ok&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  GET /my-bucket/photo.png -&amp;gt; 404 &lt;em&gt;not found&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some changes made to your S3 buckets need time to propagate and replicate through AWS servers. For example, when you delete an object you also get eventual consistency; you &lt;em&gt;MIGHT&lt;/em&gt; see an object listed on your bucket even though you already deleted it a couple of seconds ago. &lt;/p&gt;

&lt;h1&gt;
  
  
  Why do I need S3?
&lt;/h1&gt;

&lt;p&gt;If you need to store files or data that doesn't fit on a database, and you need them to be available from the internet, you probably need a solution like S3.  &lt;/p&gt;

&lt;p&gt;Why S3 specifically? Here are some reasons:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Virtually unlimited storage space.&lt;/li&gt;
&lt;li&gt;  Stored objects availability is 99.99% by default (We'll talk more about this in another post).&lt;/li&gt;
&lt;li&gt;  Objects' tight access restrictions depending on security requirements.&lt;/li&gt;
&lt;li&gt;  Pay for what you use: space occupied and requests.&lt;/li&gt;
&lt;li&gt;  Integration with most of the other AWS services.&lt;/li&gt;
&lt;li&gt;  Low entry barrier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;S3 has many more interesting features. The ones mentioned before are the ones I consider most important.  &lt;/p&gt;

&lt;h1&gt;
  
  
  Wrap up
&lt;/h1&gt;

&lt;p&gt;Whenever you are evaluating a storage solution to use, S3 will probably be on the top three. It's a highly secure, available, and performant service that can solve most of your storage problems.  &lt;/p&gt;

&lt;p&gt;In the next post we'll create our first buckets and objects with S3. Also, we'll cover how to properly secure them 🔐. Stay tuned!  &lt;/p&gt;

&lt;p&gt;Thanks for reading me 💜.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>s3</category>
    </item>
    <item>
      <title>Enable "Ignore load balancer 4xx errors" health rule on AWS Elastic Beanstalk using .ebextensions</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Fri, 20 Mar 2020 00:50:17 +0000</pubDate>
      <link>https://dev.to/david_ojeda/enable-ignore-load-balancer-4xx-errors-health-rule-on-aws-elastic-beanstalk-using-ebextensions-4ac9</link>
      <guid>https://dev.to/david_ojeda/enable-ignore-load-balancer-4xx-errors-health-rule-on-aws-elastic-beanstalk-using-ebextensions-4ac9</guid>
      <description>&lt;p&gt;If you are an &lt;strong&gt;Elastic Beanstalk (EB)&lt;/strong&gt; user, you are probably aware of a frequently &lt;a href="https://aws.amazon.com/releasenotes/release-aws-elastic-beanstalk-support-for-enhanced-health-rule-customization-on-july-25-2018/?tag=releasenotes%23keywords%23aws-elastic-beanstalk"&gt;requested feature that was released on July 25, 2018&lt;/a&gt;: To ignore application 4xx errors when determining your environment's health. It's was a new EB health rule that ignores 400-499 HTTP status codes when alerting if your environment instances are having trouble 🏥.  &lt;/p&gt;

&lt;p&gt;It is common for applications to receive many 4xx errors, for example, due to:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Client's API integrations using invalid credentials.&lt;/li&gt;
&lt;li&gt;  Client-side test tools.&lt;/li&gt;
&lt;li&gt;  Broken links that create 404 responses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I started to use this feature since the moment it was released.  &lt;/p&gt;

&lt;p&gt;Today I logged in into my EB console and switched the design to the latest version of the console. Everything okay. Then, as I was exploring the new console, I noticed a new configuration that was not previously available: &lt;strong&gt;To ignore load balancer 4xx errors&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;In this post, I will show you, through a story, how to enable this feature using &lt;a href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/ebextensions.html"&gt;&lt;em&gt;.ebextensions&lt;/em&gt;&lt;/a&gt;, because, infrastructure-as-code, you know 🤓. The funny part, I can't seem to find the documentation anywhere on AWS. Never happened before, right? 🙃.   &lt;/p&gt;

&lt;p&gt;Follow along!  &lt;/p&gt;

&lt;h1&gt;
  
  
  Digging through the docs 🗂
&lt;/h1&gt;

&lt;p&gt;The first thing I do when trying to configure this new feature is: go to the &lt;a href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/health-enhanced-rules.html"&gt;docs page where the almost identical feature is documented&lt;/a&gt;, that is, the ignore &lt;strong&gt;application&lt;/strong&gt; 4xx errors. I found that is the same page as it was before, with no extra information about a load balancer health rule. Citing the docs:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Currently, this is the only available enhanced heath rule customization. You can't configure enhanced health to ignore HTTP errors returned by an environment's load balancer, or other HTTP errors in addition to 4xx.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Liars! Just kidding 😝   &lt;/p&gt;

&lt;p&gt;Without luck on this doc page, I proceeded to look somewhere else:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Went through the &lt;a href="https://aws.amazon.com/releasenotes/?tag=releasenotes%23keywords%23aws-elastic-beanstalk"&gt;EB release notes&lt;/a&gt; to see if I missed the announcement. No luck.&lt;/li&gt;
&lt;li&gt;  Went to the &lt;a href="https://github.com/aws/elastic-beanstalk-roadmap/projects/1"&gt;EB public roadmap&lt;/a&gt; and found nothing there either.&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://twitter.com/DavidOjedaL/status/1240705686343798784"&gt;Asked on Twitter&lt;/a&gt;, but since no one follows all I got was a response from AWS support to look at their forum. Go &lt;a href="https://twitter.com/DavidOjedaL"&gt;follow me&lt;/a&gt; on Twitter! 👀.&lt;/li&gt;
&lt;li&gt;  Went to the EB forums and only found people asking how to work around the fact that there was no feature to ignore load balancer 4xx errors 🤦🏻‍♂️.&lt;/li&gt;
&lt;li&gt;  Asked a &lt;a href="https://stackoverflow.com/questions/60764891/how-to-enable-aws-elastic-beanstalk-health-rule-ignore-load-balancer-4xx-throu?noredirect=1#comment107510426_60764891"&gt;question in StackOverflow&lt;/a&gt; and got a comment saying that I should create a support ticket.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  What next❓
&lt;/h1&gt;

&lt;p&gt;I had already searched through a lot of docs without any progress. I posted the question on Twitter and StackOverflow, and I wasn't expecting any response soon. I thought about trying to guess the new field names and do a try-error session.  &lt;/p&gt;

&lt;p&gt;My current configuration document looks like this:&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;"Rules"&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;"Environment"&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;"Application"&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;"ApplicationRequests4xx"&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;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I were the software developer that created this feature, how would I call the new field? Some options:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  LoadBalancerRequests4xx&lt;/li&gt;
&lt;li&gt;  ALBRequests4xx&lt;/li&gt;
&lt;li&gt;  ELBRequests4xx&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But, should they be nested under "Application" or should it be another high-level field? By this time I was already hungry, so I went to cook and eat 👨🏻‍🍳🍲  &lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Enlightenment&lt;/strong&gt;! 🔮
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Taking a break helps&lt;/strong&gt;, you should consider it in your daily routine. Back to the topic...   &lt;/p&gt;

&lt;p&gt;I realized that even though I can't find a way to configure this feature through code, I can do it directly in the console. This would only be temporary because the environments re-create themselves on each deploy- any new environment would have this feature disabled.  &lt;/p&gt;

&lt;p&gt;And here is where I got an idea💡 What if I:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Enable the feature on the console.&lt;/li&gt;
&lt;li&gt; Save the environment configuration as an &lt;a href="https://docs.aws.amazon.com/elasticbeanstalk/latest/dg/environment-configuration-savedconfig.html"&gt;EB Saved Configuration&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt; Retrieve it with the EB CLI to see how the field is called at an API level.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's exactly what I did. Once the configuration was saved, I retrieved it:&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;eb config get NAME_OF_MY_CONFIGURATION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and 💥, the configuration showed itself:&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;"Rules"&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;"Environment"&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;"ELB"&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;"ELBRequests4xx"&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;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Application"&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;"ApplicationRequests4xx"&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;"Enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="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;There was a high-level field called "&lt;em&gt;ELB"&lt;/em&gt;, and a property "&lt;em&gt;ELBRequests4XX&lt;/em&gt;"; I was not that erred 👨🏻‍💻.  &lt;/p&gt;

&lt;p&gt;I added those new fields to my .config file on the &lt;em&gt;.ebextensions&lt;/em&gt; folder and everything worked as expected 👏🏼 Here is the final .config file I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;option_settings&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws:elasticbeanstalk:healthreporting:system&lt;/span&gt;
    &lt;span class="na"&gt;option_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigDocument&lt;/span&gt;
    &lt;span class="na"&gt;value&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;Rules"&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;Environment"&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;ELB"&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;ELBRequests4xx"&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;Enabled"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;false&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;Application"&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;ApplicationRequests4xx"&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;Enabled"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;false&lt;/span&gt;
      &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="pi"&gt;}&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;Version"&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;1&lt;/span&gt;
&lt;span class="pi"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Wrap up 🔄
&lt;/h1&gt;

&lt;p&gt;I can't imagine how complex is to add a feature to a system as big as AWS while maintaining all the docs updated. I don't blame them.  &lt;/p&gt;

&lt;p&gt;Nonetheless, we figured out at the end 🙌🏼. In short, to configure your EB environments to ignore load balancer 4xx errors, you need to add the previous .config file to your .ebextensions folder and deploy a new version.  &lt;/p&gt;

&lt;p&gt;Maybe the docs are updated by the time you read this, and the story will not be that fun 🙃 Anyhow, it was a blast to write.  &lt;/p&gt;

&lt;p&gt;I hope you enjoyed it. Thanks for reading me 💙&lt;/p&gt;

</description>
      <category>aws</category>
      <category>elasticbeanstalk</category>
    </item>
    <item>
      <title>What are you learning or planning to learn next? 📚</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Wed, 05 Feb 2020 23:14:44 +0000</pubDate>
      <link>https://dev.to/david_ojeda/what-are-you-learning-or-planning-to-learn-next-499d</link>
      <guid>https://dev.to/david_ojeda/what-are-you-learning-or-planning-to-learn-next-499d</guid>
      <description>&lt;p&gt;Are you struggling with something specific? &lt;/p&gt;

</description>
      <category>discuss</category>
    </item>
    <item>
      <title>Copy to clipboard button with Stimulus 2.0 (Beta)</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Tue, 28 Jan 2020 00:46:41 +0000</pubDate>
      <link>https://dev.to/david_ojeda/copy-to-clipboard-button-with-stimulus-2-0-beta-1nll</link>
      <guid>https://dev.to/david_ojeda/copy-to-clipboard-button-with-stimulus-2-0-beta-1nll</guid>
      <description>&lt;p&gt;&lt;a href="https://stimulusjs.org/handbook/introduction"&gt;&lt;strong&gt;Stimulus&lt;/strong&gt;&lt;/a&gt; is a JavaScript framework developed by a team at &lt;a href="https://basecamp.com/"&gt;Basecamp&lt;/a&gt;, and it aims to augment your existing HTML so things work without too much "connecting" code.&lt;/p&gt;

&lt;p&gt;Contrary to other frameworks, Stimulus doesn't take over your front-end, so you can add it without too much hassle to your already running app.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Its documentation is very clear and digestible&lt;/strong&gt;. Included in its handbook is an &lt;a href="https://stimulusjs.org/handbook/building-something-real"&gt;example of building a clipboard functionality&lt;/a&gt;, which I recommend you go through if you are trying Stimulus for the first time.&lt;/p&gt;

&lt;p&gt;Right now we are &lt;strong&gt;replicating&lt;/strong&gt; that functionality and adding a couple more things &lt;strong&gt;using a development build&lt;/strong&gt; specified in this Pull Request (PR)&lt;/p&gt;


&lt;div class="ltag_github-liquid-tag"&gt;
  &lt;h1&gt;
    &lt;a href="https://github.com/stimulusjs/stimulus/pull/202"&gt;
      &lt;img class="github-logo" alt="GitHub logo" src="https://res.cloudinary.com/practicaldev/image/fetch/s--566lAguM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg"&gt;
      &lt;span class="issue-title"&gt;
        The Values and Classes APIs
      &lt;/span&gt;
      &lt;span class="issue-number"&gt;#202&lt;/span&gt;
    &lt;/a&gt;
  &lt;/h1&gt;
  &lt;div class="github-thread"&gt;
    &lt;div class="timeline-comment-header"&gt;
      &lt;a href="https://github.com/sstephenson"&gt;
        &lt;img class="github-liquid-tag-img" src="https://res.cloudinary.com/practicaldev/image/fetch/s--DWtByW_i--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://avatars2.githubusercontent.com/u/2603%3Fv%3D4" alt="sstephenson avatar"&gt;
      &lt;/a&gt;
      &lt;div class="timeline-comment-header-text"&gt;
        &lt;strong&gt;
          &lt;a href="https://github.com/sstephenson"&gt;sstephenson&lt;/a&gt;
        &lt;/strong&gt; posted on &lt;a href="https://github.com/stimulusjs/stimulus/pull/202"&gt;&lt;time&gt;Nov 02, 2018&lt;/time&gt;&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="ltag-github-body"&gt;
      &lt;p&gt;This pull request introduces two new APIs to Stimulus: &lt;em&gt;Values&lt;/em&gt; and &lt;em&gt;Classes&lt;/em&gt;. These APIs are designed to improve upon, and ultimately obviate, the current &lt;a href="https://stimulusjs.org/reference/data-maps" rel="nofollow"&gt;Data Map API&lt;/a&gt;. We plan to ship them together in the upcoming Stimulus 2.0 release.&lt;/p&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Values&lt;/h2&gt;
&lt;p&gt;Most uses of the Data Map API in Basecamp fall under the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Storing small strings, such as URLs, dates, or color values&lt;/li&gt;
&lt;li&gt;Keeping track of a numeric index into a collection&lt;/li&gt;
&lt;li&gt;Bootstrapping a controller with a JSON object or array&lt;/li&gt;
&lt;li&gt;Conditioning behavior on a per-controller basis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, the Data Map API only works with string values. That means we must &lt;a href="https://stimulusjs.org/handbook/managing-state#reading-initial-state-from-the-dom" rel="nofollow"&gt;manually convert to and from other types&lt;/a&gt; as needed. The Values API handles this type conversion work automatically.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Value properties&lt;/h3&gt;
&lt;p&gt;The Values API adds support for a static &lt;code&gt;values&lt;/code&gt; object on controllers. The keys of this object are Data Map keys, and the values declare their data type:&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;export&lt;/span&gt; &lt;span class="pl-c1"&gt;default&lt;/span&gt; &lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-k"&gt;extends&lt;/span&gt; &lt;span class="pl-e"&gt;Controller&lt;/span&gt; {
  &lt;span class="pl-k"&gt;static&lt;/span&gt; values &lt;span class="pl-k"&gt;=&lt;/span&gt; {
    url&lt;span class="pl-k"&gt;:&lt;/span&gt; &lt;span class="pl-c1"&gt;String&lt;/span&gt;,
    refreshInterval&lt;span class="pl-k"&gt;:&lt;/span&gt; &lt;span class="pl-c1"&gt;Number&lt;/span&gt;,
    loadOnConnect&lt;span class="pl-k"&gt;:&lt;/span&gt; &lt;span class="pl-c1"&gt;Boolean&lt;/span&gt;
  }

  &lt;span class="pl-en"&gt;connect&lt;/span&gt;() {
    &lt;span class="pl-k"&gt;if&lt;/span&gt; (&lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;loadOnConnectValue&lt;/span&gt;) {
      &lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-c1"&gt;load&lt;/span&gt;()
    }
  }

  &lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-en"&gt;load&lt;/span&gt;() {
    &lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-c1"&gt;response&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;fetch&lt;/span&gt;(&lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;urlValue&lt;/span&gt;)
    &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;//&lt;/span&gt; ...&lt;/span&gt;
    &lt;span class="pl-c1"&gt;setTimeout&lt;/span&gt;(() &lt;span class="pl-k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-c1"&gt;load&lt;/span&gt;(), &lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;refreshIntervalValue&lt;/span&gt;)
  }
}&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Supported types and defaults&lt;/h3&gt;
&lt;p&gt;This pull request implements support for five built-in types:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Serialized attribute value&lt;/th&gt;
&lt;th&gt;Default value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JSON.stringify(array)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boolean&lt;/td&gt;
&lt;td&gt;&lt;code&gt;boolean.toString()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Number&lt;/td&gt;
&lt;td&gt;&lt;code&gt;number.toString()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JSON.stringify(object)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;{}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;Itself&lt;/td&gt;
&lt;td&gt;&lt;code&gt;""&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Each type has a default value. If a value is declared in a controller but its associated data attribute is missing, the getter property will return its type's default.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Controller properties&lt;/h3&gt;
&lt;p&gt;Stimulus automatically generates three properties for each entry in the object:&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Property name&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Boolean, Number, Object, String&lt;/td&gt;
&lt;td&gt;Getter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.[name]Value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads &lt;code&gt;data-[identifier]-[name]-value&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;Getter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.[name]Values&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads &lt;code&gt;data-[identifier]-[name]-values&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boolean, Number, Object, String&lt;/td&gt;
&lt;td&gt;Setter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.[name]Value=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Writes &lt;code&gt;data-[identifier]-[name]-value&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;Setter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.[name]Values=&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Writes &lt;code&gt;data-[identifier]-[name]-values&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Boolean, Number, Object, String&lt;/td&gt;
&lt;td&gt;Existential&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.has[Name]Value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tests for presence of &lt;code&gt;data-[identifier]-[name]-value&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Array&lt;/td&gt;
&lt;td&gt;Existential&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.has[Name]Values&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tests for presence of &lt;code&gt;data-[identifier]-[name]-values&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;p&gt;Note that array values are always pluralized, both as properties and as attributes.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Value changed callbacks&lt;/h3&gt;
&lt;p&gt;In addition to value properties, the Values API introduces &lt;em&gt;value changed callbacks&lt;/em&gt;. A value changed callback is a specially named method called by Stimulus whenever a value's data attribute is modified.&lt;/p&gt;
&lt;p&gt;To observe changes to a value, define a method named &lt;code&gt;[name]ValueChanged()&lt;/code&gt;. For example, a slideshow controller with a numeric &lt;code&gt;index&lt;/code&gt; property might define an &lt;code&gt;indexValueChanged()&lt;/code&gt; method to display the specified slide:&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;export&lt;/span&gt; &lt;span class="pl-c1"&gt;default&lt;/span&gt; &lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-k"&gt;extends&lt;/span&gt; &lt;span class="pl-e"&gt;Controller&lt;/span&gt; {
  &lt;span class="pl-k"&gt;static&lt;/span&gt; values &lt;span class="pl-k"&gt;=&lt;/span&gt; { index&lt;span class="pl-k"&gt;:&lt;/span&gt; &lt;span class="pl-c1"&gt;Number&lt;/span&gt; }

  &lt;span class="pl-en"&gt;indexValueChanged&lt;/span&gt;() {
    &lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-en"&gt;showSlide&lt;/span&gt;(&lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;indexValue&lt;/span&gt;)
  }

  &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;//&lt;/span&gt; ...&lt;/span&gt;
}&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Stimulus invokes each value changed callback once when the controller is initialized, and again any time the value's data attribute changes.&lt;/p&gt;
&lt;p&gt;Even if a value's data attribute is missing when the controller is initialized, Stimulus will still invoke its value changed callback. Use the existential property to determine whether the data attribute is present.&lt;/p&gt;
&lt;br&gt;
&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Classes&lt;/h2&gt;
&lt;p&gt;Another common use of the Data Map API is to store CSS class names.&lt;/p&gt;
&lt;p&gt;For example, Basecamp's copy-to-clipboard controller applies a CSS class to its element after a successful copy. To avoid inlining a long &lt;a href="https://css-tricks.com/bem-101/" rel="nofollow"&gt;BEM string&lt;/a&gt; in our controller, and to keep things loosely coupled, we declare the class in a &lt;code&gt;data-clipboard-success-class&lt;/code&gt; attribute:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic js-code-highlight"&gt;
&lt;pre&gt;&amp;lt;&lt;span class="pl-ent"&gt;div&lt;/span&gt; &lt;span class="pl-e"&gt;data-controller&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;clipboard&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
     &lt;span class="pl-e"&gt;data-clipboard-success-class&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;copy-to-clipboard--success&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&amp;gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;and access it using &lt;code&gt;this.data.get("successClass")&lt;/code&gt; in the controller:&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;element&lt;/span&gt;.&lt;span class="pl-smi"&gt;classList&lt;/span&gt;.&lt;span class="pl-c1"&gt;add&lt;/span&gt;(&lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-c1"&gt;data&lt;/span&gt;.&lt;span class="pl-c1"&gt;get&lt;/span&gt;(&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;successClass&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;))&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;The Classes API formalizes and refines this pattern.&lt;/p&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Class properties&lt;/h3&gt;
&lt;p&gt;The Classes API adds a static &lt;code&gt;classes&lt;/code&gt; array on controllers. As with &lt;a href="https://stimulusjs.org/reference/targets#definitions" rel="nofollow"&gt;targets&lt;/a&gt;, Stimulus automatically adds properties for each class listed in the array:&lt;/p&gt;
&lt;div class="highlight highlight-source-js js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;//&lt;/span&gt; clipboard_controller.js&lt;/span&gt;
&lt;span class="pl-k"&gt;export&lt;/span&gt; &lt;span class="pl-c1"&gt;default&lt;/span&gt; &lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-k"&gt;extends&lt;/span&gt; &lt;span class="pl-e"&gt;Controller&lt;/span&gt; {
  &lt;span class="pl-k"&gt;static&lt;/span&gt; classes &lt;span class="pl-k"&gt;=&lt;/span&gt; [ &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;success&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;, &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;supported&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; ]

  &lt;span class="pl-en"&gt;initialize&lt;/span&gt;() {
    &lt;span class="pl-k"&gt;if&lt;/span&gt; (&lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;/*&lt;/span&gt; ... &lt;span class="pl-c"&gt;*/&lt;/span&gt;&lt;/span&gt;) {
      &lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;element&lt;/span&gt;.&lt;span class="pl-smi"&gt;classList&lt;/span&gt;.&lt;span class="pl-c1"&gt;add&lt;/span&gt;(&lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;supportedClass&lt;/span&gt;)
    }
  }

  &lt;span class="pl-en"&gt;copy&lt;/span&gt;() {
    &lt;span class="pl-c"&gt;&lt;span class="pl-c"&gt;//&lt;/span&gt; ...&lt;/span&gt;
    &lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;element&lt;/span&gt;.&lt;span class="pl-smi"&gt;classList&lt;/span&gt;.&lt;span class="pl-c1"&gt;add&lt;/span&gt;(&lt;span class="pl-c1"&gt;this&lt;/span&gt;.&lt;span class="pl-smi"&gt;successClass&lt;/span&gt;)
  }
}&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;Property name&lt;/th&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Getter&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.[name]Class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Reads the &lt;code&gt;data-[identifier]-[name]-class&lt;/code&gt; attribute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existential&lt;/td&gt;
&lt;td&gt;&lt;code&gt;this.has[Name]Class&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tests whether the &lt;code&gt;data-[identifier]-[name]-class&lt;/code&gt; attribute is present&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h4&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Declarations are assumed to be present&lt;/h4&gt;
&lt;p&gt;When you access a class property in a controller, such as &lt;code&gt;this.supportedClass&lt;/code&gt;, you assert that the corresponding data attribute is present on the controller element. If the declaration is missing, Stimulus throws a descriptive error:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://user-images.githubusercontent.com/5355/62792040-685b7700-ba9c-11e9-94f3-a5271634a12f.png" rel="nofollow"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GTE_vMKF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://user-images.githubusercontent.com/5355/62792040-685b7700-ba9c-11e9-94f3-a5271634a12f.png" alt="Screenshot showing error message: &amp;quot;Missing attribute 'data-clipboard-supported-class'&amp;quot;"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If a class is optional, you must first use the existential property (e.g. &lt;code&gt;this.hasSupportedClass&lt;/code&gt;) to determine whether its declaration is present.&lt;/p&gt;
&lt;br&gt;

&lt;h2&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Unifying target attributes&lt;/h2&gt;
&lt;p&gt;We've made a change to the target attribute syntax to align them with values and classes, and also to make the controller identifier more prominent by moving it into the attribute name.&lt;/p&gt;
&lt;p&gt;The original syntax is:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic js-code-highlight"&gt;
&lt;pre&gt;&amp;lt;&lt;span class="pl-ent"&gt;div&lt;/span&gt; &lt;span class="pl-e"&gt;data-target&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;[identifier].[name]&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&amp;gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;and the updated syntax is:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic js-code-highlight"&gt;
&lt;pre&gt;&amp;lt;&lt;span class="pl-ent"&gt;div&lt;/span&gt; &lt;span class="pl-e"&gt;data-[identifier]-target&lt;/span&gt;=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;[name]&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&amp;gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;The original syntax is supported but deprecated&lt;/h3&gt;
&lt;p&gt;Stimulus 2.0 will support both syntaxes, but using the original syntax will display a deprecation message in the developer console. We intend to remove the original syntax in Stimulus 3.0.&lt;/p&gt;

&lt;h3&gt;
&lt;span class="octicon octicon-link"&gt;&lt;/span&gt;Try it out in your application&lt;/h3&gt;
&lt;p&gt;Update the Stimulus entry in &lt;code&gt;package.json&lt;/code&gt; to point to the latest development build:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;"stimulus": "https://github.com/stimulusjs/dev-builds/archive/b8cc8c4/stimulus.tar.gz"
&lt;/code&gt;&lt;/pre&gt;

    &lt;/div&gt;
    &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/stimulusjs/stimulus/pull/202"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;It &lt;strong&gt;includes new APIs that will be released with version 2.0&lt;/strong&gt; of the framework, so they are not yet available with the current stable production release.&lt;/p&gt;

&lt;h1&gt;
  
  
  What are we building?
&lt;/h1&gt;

&lt;p&gt;A one-time password "copy to clipboard" button what wraps the DOM Clipboard API.&lt;/p&gt;

&lt;p&gt;You can access the final working version on &lt;a href="https://glitch.com/edit/#!/trapezoidal-seer"&gt;Glitch&lt;/a&gt;:&lt;/p&gt;


&lt;div class="glitch-embed-wrap"&gt;
  &lt;iframe src="https://glitch.com/embed/#!/embed/trapezoidal-seer?path=index.html" alt="trapezoidal-seer on glitch"&gt;&lt;/iframe&gt;
&lt;/div&gt;


&lt;h1&gt;
  
  
  Starting off
&lt;/h1&gt;

&lt;p&gt;First, we are creating our base HTML where the one-time password will be and the actual button to copy it:&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;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    One-time password:
    &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;value=&lt;/span&gt;&lt;span class="s"&gt;"fbbb5593-1885-4164-afbe-aba1b87ea748"&lt;/span&gt; &lt;span class="na"&gt;readonly=&lt;/span&gt;&lt;span class="s"&gt;"readonly"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;
    Copy to clipboard
  &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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0zDqj-Lw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/bu8kact7stjzee0flm5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0zDqj-Lw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/bu8kact7stjzee0flm5a.png" alt='Text input with "copy to clipboard button" rendered HTML' width="386" height="34"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This doesn't do anything by itself; we need to add our Stimulus controller.&lt;/p&gt;

&lt;h1&gt;
  
  
  The controller definition
&lt;/h1&gt;

&lt;p&gt;In Stimulus, &lt;strong&gt;a controller is a JavaScript object that automatically connects to DOM elements that have certain identifiers&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let's define our clipboard controller. The main thing it needs to do? Grab the text on the input field and copy it to the clipboard:&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="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;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Stimulus&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="nx"&gt;start&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="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stimulus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// We'll get to this below&lt;/span&gt;
    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;targets&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&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;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Here goes the copy logic &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;Now, this is a valid controller that doesn't do anything because it's not connected to any DOM element yet.&lt;/p&gt;

&lt;h1&gt;
  
  
  Connecting the controller
&lt;/h1&gt;

&lt;p&gt;Adding a &lt;code&gt;data-controller&lt;/code&gt; attribute to our &lt;code&gt;div&lt;/code&gt; will enable the connection:&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;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"clipboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember the &lt;code&gt;static get targets()&lt;/code&gt; from above? That allows us to &lt;strong&gt;access DOM elements as properties in the controller&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Since there is already a &lt;code&gt;source&lt;/code&gt; target, we can now access any DOM element with the attribute &lt;code&gt;data-clipboard-target="source"&lt;/code&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;input&lt;/span&gt; &lt;span class="na"&gt;data-clipboard-target=&lt;/span&gt;&lt;span class="s"&gt;"source"&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;value=&lt;/span&gt;&lt;span class="s"&gt;"fbbb5593-1885-4164-afbe-aba1b87ea748"&lt;/span&gt; &lt;span class="na"&gt;readonly=&lt;/span&gt;&lt;span class="s"&gt;"readonly"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, we need the button to actually do something. We can link the "Copy to clipboard" button to the &lt;code&gt;copy&lt;/code&gt; action in our controller with another identifier: &lt;code&gt;data-action="clipboard#copy"&lt;/code&gt;. The HTML now looks like this:&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;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"clipboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
    One-time password:
    &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;data-clipboard-target=&lt;/span&gt;&lt;span class="s"&gt;"source"&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;value=&lt;/span&gt;&lt;span class="s"&gt;"fbbb5593-1885-4164-afbe-aba1b87ea748"&lt;/span&gt; &lt;span class="na"&gt;readonly=&lt;/span&gt;&lt;span class="s"&gt;"readonly"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"clipboard#copy"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    Copy to clipboard
  &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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our controller is now automatically connected to the DOM, and clicking the copy button will invoke the &lt;code&gt;copy&lt;/code&gt; function; let's proceed to write it.&lt;/p&gt;

&lt;h1&gt;
  
  
  The copy function
&lt;/h1&gt;

&lt;p&gt;This function is essentially a &lt;strong&gt;wrapper of the DOM Clipboard API&lt;/strong&gt;. The logic goes like this:&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;copy&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;sourceTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&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="p"&gt;[...]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We take the &lt;code&gt;source&lt;/code&gt; target we defined earlier, our text input that is, select its content, and use the Clipboard API to copy it to our clipboard.&lt;/p&gt;

&lt;p&gt;At this point, &lt;strong&gt;the functionality is practically done!&lt;/strong&gt; You can press the button and the one-time password is now available for you on your clipboard.&lt;/p&gt;

&lt;h1&gt;
  
  
  Moving further
&lt;/h1&gt;

&lt;p&gt;The copy button works now, but we can go further. &lt;strong&gt;What if the browser doesn't support the Clipboard API or JavaScript is disabled?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If that's the case, we are going to hide the copy button entirely.&lt;/p&gt;

&lt;h1&gt;
  
  
  Checking API availability
&lt;/h1&gt;

&lt;p&gt;We can check if the &lt;code&gt;copy&lt;/code&gt; command is available to us by doing this:&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="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryCommandSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&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;One of the best places to check this is when the Stimulus controller connects to the DOM. Stimulus gives us some nice &lt;strong&gt;lifecycle callbacks&lt;/strong&gt; so we can know when this happens. &lt;/p&gt;

&lt;p&gt;We can create a &lt;code&gt;connect&lt;/code&gt; function on our controller and it will be invoked whenever this controller connects to the DOM:&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;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryCommandSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
    &lt;span class="c1"&gt;// Proceed normally&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;One way to hide/show the copy button depending on the API availability is to initially load the page with the button hidden, and then displaying it if the API is available. &lt;/p&gt;

&lt;p&gt;To achieve this we can rely on CSS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.clipboard-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* Match all elements with .clipboard-button class inside the element with .clipboard--supported class */&lt;/span&gt;
&lt;span class="nc"&gt;.clipboard--supported&lt;/span&gt; &lt;span class="nc"&gt;.clipboard-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&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;Our button is now hidden from the beginning, and will only be visible when we add the &lt;code&gt;.clipboard--supported&lt;/code&gt; class to our &lt;code&gt;div&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To do it, we modify the connect lifecycle callback. &lt;/p&gt;

&lt;p&gt;Here is where we can start to see major differences from this latest development version. With the actual production version you would need to specify the CSS class in the controller, effectively doing this:&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;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryCommandSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&lt;/span&gt;&lt;span class="dl"&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;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clipboard--supported&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="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;&lt;strong&gt;There is a new, better way to achieve it.&lt;/strong&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Classes API
&lt;/h1&gt;

&lt;p&gt;Now, &lt;strong&gt;CSS classes can be actual properties of the controller&lt;/strong&gt;. To do so, we need to add some identifiers to our HTML and add a new array to our controller:&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;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"clipboard"&lt;/span&gt; &lt;span class="na"&gt;data-clipboard-supported-class=&lt;/span&gt;&lt;span class="s"&gt;"clipboard--supported"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"clipboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;application&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stimulus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;[...]&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt; &lt;span class="o"&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;supported&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryCommandSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&lt;/span&gt;&lt;span class="dl"&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;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&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;supportedClass&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;Great! Now we can access our supported class string from our controller with &lt;code&gt;this.supportedClass&lt;/code&gt;. &lt;strong&gt;This will help keep things loosely coupled.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The clipboard real-life example from Stimulus' handbook ends here. Now, to show the other newest additions and use the &lt;em&gt;Classes API&lt;/em&gt; once more, we're adding the following functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new style to the "Copy to clipboard" button once it has been clicked&lt;/li&gt;
&lt;li&gt;A refresh interval for the one-time password. This will generate a new password every 2.5 seconds&lt;/li&gt;
&lt;li&gt;A data attribute to keep track of how many times the password has been generated&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Values API
&lt;/h1&gt;

&lt;p&gt;This, along with the &lt;em&gt;Classes API&lt;/em&gt;, is one of the new additions to Stimulus. Before this API you would need to add arbitrary values to your controller with the Data Map API, that is, adding &lt;code&gt;data-[identifier]-[variable-name]&lt;/code&gt; to your DOM element, and then parsing that value in your controller. &lt;/p&gt;

&lt;p&gt;This created boilerplate such as getters and setters with calls to &lt;code&gt;parseFloat()&lt;/code&gt;, &lt;code&gt;parseInt()&lt;/code&gt;, &lt;code&gt;JSON.stringify()&lt;/code&gt;, etc. This is how it will work with the &lt;em&gt;Values API&lt;/em&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;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"clipboard"&lt;/span&gt; &lt;span class="na"&gt;data-clipboard-supporte-class=&lt;/span&gt;&lt;span class="s"&gt;"clipboard--supported"&lt;/span&gt; &lt;span class="na"&gt;data-clipboard-refresh-interval-value=&lt;/span&gt;&lt;span class="s"&gt;"2500"&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"clipboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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;application&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stimulus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="p"&gt;[...]&lt;/span&gt;

  &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;refreshInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryCommandSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&lt;/span&gt;&lt;span class="dl"&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;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&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;supportedClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// Access refreshInterval value directly&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;refreshIntervalValue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 2500&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;&lt;strong&gt;Accessing your controller values is now cleaner since you don't need to write your getters and setters, nor do you need to parse from String to the type you need.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Moving forward, let's write the one-time password refresh.&lt;/p&gt;

&lt;h1&gt;
  
  
  Implementing password generation
&lt;/h1&gt;

&lt;p&gt;We're going to define a new function to create a new random password. &lt;a href="https://www.arungudelli.com/tutorial/javascript/how-to-create-uuid-guid-in-javascript-with-examples/"&gt;I grabbed this random UUID generator snippet from the internet&lt;/a&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="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e11&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;018&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&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;c&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding it to our Stimulus controller:&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;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryCommandSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&lt;/span&gt;&lt;span class="dl"&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;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&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;supportedClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasRefreshIntervalValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;generateNewPassword&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;refreshIntervalValue&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;// copy function&lt;/span&gt;

  &lt;span class="nx"&gt;generateNewPassword&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;sourceTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e11&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;018&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&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;c&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&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;We use &lt;code&gt;setInterval&lt;/code&gt; to refresh our password text field each 2500ms since that's the value we defined in the DOM. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our refresh feature is now working!&lt;/strong&gt; Some things still missing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add new style when copy button is clicked&lt;/li&gt;
&lt;li&gt;Keep track of how many times a password is generated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Giving all we have learned so far, this is what's need to be done:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a new CSS class to the stylesheet, DOM element, and controller&lt;/li&gt;
&lt;li&gt;Add this new class when the button is clicked, and remove it when the password is refreshed&lt;/li&gt;
&lt;li&gt;Add to a counter when the password refreshes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how it will look at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* CSS */&lt;/span&gt;

&lt;span class="nc"&gt;.clipboard-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.clipboard--supported&lt;/span&gt; &lt;span class="nc"&gt;.clipboard-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.clipboard--success&lt;/span&gt; &lt;span class="nc"&gt;.clipboard-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;palegreen&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- HTML --&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;data-controller=&lt;/span&gt;&lt;span class="s"&gt;"clipboard"&lt;/span&gt; 
     &lt;span class="na"&gt;data-clipboard-refresh-interval-value=&lt;/span&gt;&lt;span class="s"&gt;"2500"&lt;/span&gt;
     &lt;span class="na"&gt;data-clipboard-supported-class=&lt;/span&gt;&lt;span class="s"&gt;"clipboard--supported"&lt;/span&gt; 
     &lt;span class="na"&gt;data-clipboard-success-class=&lt;/span&gt;&lt;span class="s"&gt;"clipboard--success"&lt;/span&gt;      
     &lt;span class="na"&gt;data-clipboard-times-generated-value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt; 
     &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;label&amp;gt;&lt;/span&gt;
        One-time password: &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;data-clipboard-target=&lt;/span&gt;&lt;span class="s"&gt;"source"&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;value=&lt;/span&gt;&lt;span class="s"&gt;"fbbb5593-1885-4164-afbe-aba1b87ea748"&lt;/span&gt; &lt;span class="na"&gt;readonly=&lt;/span&gt;&lt;span class="s"&gt;"readonly"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/label&amp;gt;&lt;/span&gt;

      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;data-action=&lt;/span&gt;&lt;span class="s"&gt;"clipboard#copy"&lt;/span&gt;               
              &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"clipboard-button"&lt;/span&gt; &lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        Copy to Clipboard
      &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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt; &lt;span class="c1"&gt;// JavaScript&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;application&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Stimulus&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="nx"&gt;start&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="nx"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;clipboard&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stimulus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Controller&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nx"&gt;targets&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="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;source&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;static&lt;/span&gt; &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;              
        &lt;span class="na"&gt;refreshInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;timesGenerated&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Number&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;classes&lt;/span&gt; &lt;span class="o"&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;supported&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;success&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

      &lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;                 
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;queryCommandSupported&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;copy&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&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;supportedClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                
        &lt;span class="p"&gt;}&lt;/span&gt;                            
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hasRefreshIntervalValue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;generateNewPassword&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;refreshIntervalValue&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="nx"&gt;copy&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;sourceTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;select&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copy&lt;/span&gt;&lt;span class="dl"&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;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&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;successClass&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;generateNewPassword&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;sourceTarget&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="nx"&gt;e3&lt;/span&gt;&lt;span class="o"&gt;+-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="nx"&gt;e11&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;018&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&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;c&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getRandomValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&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;element&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;classList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;remove&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;successClass&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;timesGeneratedValue&lt;/span&gt;&lt;span class="o"&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;// NEW! Read about it below&lt;/span&gt;
      &lt;span class="nx"&gt;timesGeneratedValueChanged&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;              
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timesGeneratedValue&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;timesGeneratedValue&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;3&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;You still there?&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="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;Apart from what we've already discussed about the &lt;em&gt;Values API&lt;/em&gt;, there is also something new: &lt;strong&gt;Value changed callbacks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These callbacks are called whenever a value changes, and also once when the controller is initialized. They are connected automatically given we follow the naming convention of &lt;code&gt;[valueName]ValueChanged()&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;We use it to log a message each time the password has been refreshed three times, but they can help with state management in a more complex use case. &lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;I've created multiple Stimulus controllers for my daily job, and I must say that I always end up pleased with the results. Stimulus encourages you to keep related code together and, combined with the additional HTML markup required, ends up making your code much more readable.&lt;/p&gt;

&lt;p&gt;If you haven't tried it yet, I highly recommend going for it! It offers a different perspective, one of magic 🧙🏻‍♂️. &lt;/p&gt;

&lt;p&gt;Thanks for reading me 👋🏼.&lt;/p&gt;

</description>
      <category>stimulus</category>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>25 Days of Serverless - Day 02</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Wed, 04 Dec 2019 05:38:23 +0000</pubDate>
      <link>https://dev.to/david_ojeda/25-days-of-serverless-day-02-i2k</link>
      <guid>https://dev.to/david_ojeda/25-days-of-serverless-day-02-i2k</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover image taken from &lt;a href="https://github.com/microsoft/25-days-of-serverless/blob/master/week-1/challenge-2/README.md"&gt;Microsoft's 25 Days of Serverless repo&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;It's time for the second post of this series, hence the solution to the second challenge of the 25 Days of Serverless! Let's get to it 💪🏼.&lt;/p&gt;

&lt;h1&gt;
  
  
  Challenge 2: Task Scheduler ☕️
&lt;/h1&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/microsoft/25-days-of-serverless/blob/master/week-1/challenge-2/README.md"&gt;description from their GitHub repo&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lucy's Dilema&lt;/p&gt;

&lt;p&gt;Today we find ourselves in Stockholm, where a little girl named Lucy needs our help!&lt;/p&gt;

&lt;p&gt;Every December 13th, Lucy is tasked with wearing a crown with six lit candles and delivering coffee to all of her family members — her mother, father, sister, and brother. Each candle only lasts ten minutes before burning out, and she needs to be careful to keep the candles lit during the delivery time!&lt;/p&gt;

&lt;p&gt;Lucy is somewhat forgetful, though, and the stolen servers mean Lucy's usual reminder app isn't working! With only a few weeks to go before her big night, Lucy is worried how she'll remember everything she needs to do and keep her timing in order. She thought about using sticky notes with color codes to remind her of the things she needs to do, but what if they get mixed up? How can she optimize her tasks using serverless technology?&lt;/p&gt;

&lt;p&gt;It takes Lucy 25 minutes to make a large pot of coffee that will serve everyone, and about four minutes to deliver two cups of coffee (remember that she only has two hands to deliver them!). As mentioned, the candles will need to be relit every ten minutes.&lt;/p&gt;

&lt;p&gt;Create a task scheduler that will tell Lucy exactly when she should relight candles, pour coffee into cups, and deliver batches of coffee. How you want to notify Lucy is up to you: maybe you can send her an SMS via Twilio, or build a webapp that uses WebSockets and browser notifications?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then there is a section with tips on when to notify what:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;8:00 AM - start the coffee, set out 4 cups&lt;/p&gt;

&lt;p&gt;8:25 AM - pour two cups&lt;/p&gt;

&lt;p&gt;8:30 AM - light the candles&lt;/p&gt;

&lt;p&gt;8:35 AM - deliver the coffee to Mom and Dad&lt;/p&gt;

&lt;p&gt;8:39 AM - return to kitchen, fill two more cups&lt;/p&gt;

&lt;p&gt;8:40 AM - relight the candles&lt;/p&gt;

&lt;p&gt;8:45 AM - deliver the coffee to Sister and Brother&lt;/p&gt;

&lt;p&gt;8:49 AM - return to kitchen, take a break!&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  My solution
&lt;/h2&gt;

&lt;p&gt;Following the same path as the previous challenge, &lt;strong&gt;we're using the AWS SAM CLI to create a serverless app&lt;/strong&gt;. This time the following services are used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lambda&lt;/li&gt;
&lt;li&gt;CloudWatch Events&lt;/li&gt;
&lt;li&gt;Simple Notification Service (SNS)&lt;/li&gt;
&lt;li&gt;Identity Access Management (IAM) Roles&lt;/li&gt;
&lt;li&gt;CloudFormation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating the app
&lt;/h3&gt;

&lt;p&gt;We created an app with a quick start template again, only to have the file structure ready to go ✅.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;All resources are defined on the &lt;code&gt;template.yaml&lt;/code&gt; file. Let's first go through the Lambda serverless function definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;25DaysOfServerlessDay02&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::Serverless::Function&lt;/span&gt;
  &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;CodeUri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;day-02/&lt;/span&gt;
    &lt;span class="na"&gt;Handler&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app.lambdaHandler&lt;/span&gt;
    &lt;span class="na"&gt;Runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs12.x&lt;/span&gt;
    &lt;span class="na"&gt;Role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;25DaysOfserverlessDay02Role.Arn&lt;/span&gt;
    &lt;span class="na"&gt;Events&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;day02&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Schedule&lt;/span&gt;
        &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;Schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cron(0,25,30,35,39,40,45,49 7 13 12 ? *)&lt;/span&gt;
          &lt;span class="na"&gt;Name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;day-02-schedule&lt;/span&gt;
          &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Schedule to remember when to serve coffee&lt;/span&gt;
          &lt;span class="na"&gt;Enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The values that are different from the previous challenge are the &lt;code&gt;Role&lt;/code&gt; and &lt;code&gt;Events&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Role&lt;/code&gt; value uses a CloudFormation function to get the ARN of an IAM role that we're going to create in this same &lt;code&gt;template.yaml&lt;/code&gt; file; we'll get to that in a little bit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This role is the one that the Lambda function will be allowed to assume and it's needed for SNS.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Event&lt;/code&gt; is where we define the CloudWatch event. Remember, from the tips above, that we want to notify Lucy in specific minutes of the hour. CloudWatch allows us to define a schedule using a cron expression so we can run this Lambda function on those required minutes. Here is the expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;0,25,30,35,39,40,45,49 7 13 12 ? &lt;span class="k"&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can check the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions"&gt;AWS specifics of the cron format here&lt;/a&gt;. To be sure on what dates this cron expression will run, you can go to the AWS Console, on the CloudWatch Events &amp;gt; Rules section:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2wFzsCDF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2pfpz11mo7bcjlac1mh0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2wFzsCDF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/2pfpz11mo7bcjlac1mh0.png" alt="AWS Console. CloudWatch Events Rules to show when cron expression will run"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By the way, our cron expression runs on the 7th hour since &lt;strong&gt;AWS works with UTC times&lt;/strong&gt;, and Stockholm's timezone is UTC + 1:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6eLfkDBl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/bb9t7tla6yxmxdkknsgs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6eLfkDBl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/bb9t7tla6yxmxdkknsgs.png" alt="Stockholm's timezone"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That will do for the Lambda function. The other resource is the IAM role used to run the function. Here is its definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;25DaysOfserverlessDay02Role&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AWS::IAM::Role&lt;/span&gt;
    &lt;span class="na"&gt;Properties&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;AssumeRolePolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;
          &lt;span class="s"&gt;"Version":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"2012-10-17",&lt;/span&gt;
          &lt;span class="s"&gt;"Statement":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;
            &lt;span class="s"&gt;{&lt;/span&gt;
              &lt;span class="s"&gt;"Effect":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Allow",&lt;/span&gt;
              &lt;span class="s"&gt;"Principal":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;
                &lt;span class="s"&gt;"Service":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"lambda.amazonaws.com"&lt;/span&gt;
              &lt;span class="s"&gt;},&lt;/span&gt;
              &lt;span class="s"&gt;"Action":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"sts:AssumeRole"&lt;/span&gt;
            &lt;span class="s"&gt;}&lt;/span&gt;
          &lt;span class="s"&gt;]&lt;/span&gt;
        &lt;span class="s"&gt;}'&lt;/span&gt;
      &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow to send SMS through SNS&lt;/span&gt;
      &lt;span class="na"&gt;ManagedPolicyArns&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole&lt;/span&gt;                      
      &lt;span class="na"&gt;Policies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;PolicyName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sns_allow_send_sms&lt;/span&gt;
          &lt;span class="na"&gt;PolicyDocument&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;Version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2012-10-17&lt;/span&gt;
            &lt;span class="na"&gt;Statement&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;Effect&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Allow&lt;/span&gt;
                &lt;span class="na"&gt;Action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sns:Publish'&lt;/span&gt;
                &lt;span class="na"&gt;Resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
      &lt;span class="na"&gt;RoleName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;25DaysOfserverlessDay02Role&lt;/span&gt;

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



&lt;p&gt;There are three important keys here: &lt;code&gt;AssumeRolePolicyDocument&lt;/code&gt;, &lt;code&gt;ManagedPolicyArns&lt;/code&gt;, and &lt;code&gt;Policies&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The first, &lt;code&gt;AssumeRolePolicyDocument&lt;/code&gt; specifies the trust policy associated with this role, in other words, it defines which entities can assume the role. In this case, the Lambda service. &lt;/p&gt;

&lt;p&gt;Next, the &lt;code&gt;ManagedPolicyArns&lt;/code&gt; is a list of policies already managed by AWS that you can attach to this new role. The specified policy gives basic execution permissions to our function. You can look for these policies on the AWS Console, under the IAM service:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--uxANg2tL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pd7yeh3jj5z5f6obj8w7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--uxANg2tL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/pd7yeh3jj5z5f6obj8w7.png" alt="AWS IAM managed policies"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, the &lt;code&gt;Policies&lt;/code&gt; section is a custom policy defined by us that allows our role to publish text messages using SNS.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Lamda function code
&lt;/h3&gt;

&lt;p&gt;Now the actual code solution for the challenge 👀 We start by getting the minute when the function was called. We can safely assume that it's running on one of the specified minutes since that's what we defined in the cron schedule:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;getMinutes&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;keyMinutes&lt;/span&gt; &lt;span class="o"&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Start the coffee, set out 4 cups&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pour two cups&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Light the candles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deliver the coffee to Mom and Dad&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;39&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Return to kitchen, fill two more cups&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Relight the candles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Deliver the coffee to Sister and Brother&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="mi"&gt;49&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Return to kitchen, take a break&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;let&lt;/span&gt; &lt;span class="nx"&gt;smsPayload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;keyMinutes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;minutes&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&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;Here is a JSON example of the event object used at the beginning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;"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;"cdc73f9d-aea9-11e3-9d5a-835b769c0d9c"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Scheduled Event"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws.events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789012"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1970-01-01T00:39:00Z"&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="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resources"&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;"arn:aws:events:us-east-1:123456789012:rule/ExampleRule"&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;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To get the minute we first transform the &lt;code&gt;time&lt;/code&gt; String from the &lt;code&gt;event&lt;/code&gt; object into a date, and then we use &lt;code&gt;getMinutes()&lt;/code&gt; since that's the time granularity we need. &lt;/p&gt;

&lt;p&gt;We then create an object whose keys are the minutes when the function will run, and whose values are the reminders to Lucy. Now, into actually sending them.&lt;/p&gt;

&lt;p&gt;We're using SNS since it's readily available on our Lambda function through the AWS SDK. We create the client at the beginning of the Lamda file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;aws&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;aws-sdk&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;and we use it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="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;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;smsPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;PhoneNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;+523331412794&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;publishText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&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;SNS&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2010-03-31&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;publish&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;promise&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Sending an SMS with SNS only requires the String payload and the phone number 📲.&lt;/p&gt;

&lt;p&gt;With that, we have a scheduled Lambda function that reminds Lucy what to do next with minute precision ⏳.&lt;/p&gt;

&lt;p&gt;You can find all the &lt;a href="https://github.com/davidojedalopez/25-days-of-serverless-day-01/tree/day-02"&gt;final files on my GitHub repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can now go and build and deploy this serverless app with the AWS SAM CLI like in the previous challenge 🥳&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;This concludes with the Day 02 challenge from 25 Days of Serverless. &lt;/p&gt;

&lt;p&gt;Now that it's my second time using the AWS SAM CLI, I could move faster and get straight to what was needed to solve the problem. &lt;/p&gt;

&lt;p&gt;I loved how you can &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-generate-event.html"&gt;create events with the CLI&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-cli-command-reference-sam-local-invoke.html"&gt;use them to locally test your Lamda functions&lt;/a&gt;. Definitely helps a lot since you don't have to deploy everything to test your changes.&lt;/p&gt;




&lt;p&gt;Thanks a lot for reading me! 💚&lt;/p&gt;

</description>
      <category>25daysofserverless</category>
      <category>serverless</category>
      <category>aws</category>
    </item>
    <item>
      <title>25 Days of Serverless - Day 01</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Mon, 02 Dec 2019 23:26:38 +0000</pubDate>
      <link>https://dev.to/david_ojeda/25-days-of-serverless-day-01-3g4b</link>
      <guid>https://dev.to/david_ojeda/25-days-of-serverless-day-01-3g4b</guid>
      <description>&lt;p&gt;&lt;em&gt;Cover image taken from &lt;a href="https://github.com/microsoft/25-days-of-serverless/blob/master/week-1/challenge-1/README.md"&gt;Microsoft's 25 Days of Serverless repo&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Yesterday while scrolling through Twitter I found an interesting opportunity to learn about the current state of serverless technologies, it's called &lt;strong&gt;&lt;a href="https://25daysofserverless.com/"&gt;25 Days of Serverless&lt;/a&gt;&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It's an initiative from Microsoft that consists of one challenge each day from the 1st through the 25th of December. &lt;/p&gt;

&lt;p&gt;I decided to give it a try, but using AWS as the cloud provider since it's what I use every day. &lt;strong&gt;The point of the challenge is to learn, not to show what provider is the best.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So fasten your seatbelts since I'll be posting my solutions for the next 25 days 🤘🏼&lt;/p&gt;

&lt;h1&gt;
  
  
  Challenge 1: A Basic Function
&lt;/h1&gt;

&lt;p&gt;Here is the description from their GitHub repo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🎶 "I had a little dreidel&lt;br&gt;
I made it out of sand&lt;br&gt;
And when I tried to spin it&lt;br&gt;
It crumbled in my hand!" 🎶&lt;/p&gt;

&lt;p&gt;Your first stop is Tel Aviv, Israel, where everybody is concerned about Hanukkah! Not only have all the dreidels been stolnen, but so have all of the servers that could replicate spinning a top!&lt;/p&gt;

&lt;p&gt;Have no fear, though: you have the capability to spin not only dreidels, but to spin up serverless applications that can spin a dreidel just as well as you can!&lt;/p&gt;

&lt;p&gt;Your task for today: create a REST API endpoint that spins a dreidel and randomly returns נ (Nun), ג (Gimmel), ה (Hay), or ש (Shin). This sounds like a great opportunity to use a serverless function to create an endpoint that any application can call!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  My solution
&lt;/h2&gt;

&lt;p&gt;I used the AWS &lt;a href="https://aws.amazon.com/serverless/sam/"&gt;Serverless Application Model (SAM)&lt;/a&gt; CLI since it provides a pre-defined template with built-in best practices for serverless applications and functions. Internally it uses the following services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Lambda&lt;/li&gt;
&lt;li&gt;AWS API Gateway&lt;/li&gt;
&lt;li&gt;AWS CloudFormation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creating the app
&lt;/h3&gt;

&lt;p&gt;Since it is my &lt;strong&gt;first time developing a serverless application&lt;/strong&gt; I went with the &lt;a href="https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html"&gt;Hello World tutorial&lt;/a&gt;. It guides you through the &lt;strong&gt;minimum steps needed to have a serverless endpoint up and running&lt;/strong&gt;; most of the tutorial is pure magic from the AWS SAM CLI 🧙🏻‍♂️.&lt;/p&gt;

&lt;p&gt;So, following this tutorial, I went with a quick start template and the nodejs12.x runtime:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;sam init
Which template &lt;span class="nb"&gt;source &lt;/span&gt;would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1 

Which runtime would you like to use?
    1 - nodejs12.x
    2 - python3.8
    3 - ruby2.5
    4 - go1.x
    5 - java11
    6 - dotnetcore2.1
    7 - nodejs10.x
    8 - nodejs8.10
    9 - nodejs6.10
    10 - python3.7
    11 - python3.6
    12 - python2.7
    13 - java8
    14 - dotnetcore2.0
    15 - dotnetcore1.0
Runtime: 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This command by itself will create a project structure and the files needed for a serverless app with a single endpoint and Lambda function.&lt;/p&gt;

&lt;p&gt;It looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;tree
&lt;span class="nb"&gt;.&lt;/span&gt;
├── README.md
├── events
│   └── event.json                &lt;span class="c"&gt;# Sample event for testing&lt;/span&gt;
├── hello-world
│   ├── app.js                    &lt;span class="c"&gt;# AWS Lambda logic&lt;/span&gt;
│   ├── package.json              &lt;span class="c"&gt;# Dependencies&lt;/span&gt;
│   └── tests
│       └── unit
│           └── test-handler.js
└── template.yaml                 &lt;span class="c"&gt;# SAM template for AWS resources&lt;/span&gt;

4 directories, 6 files
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Building the app
&lt;/h3&gt;

&lt;p&gt;The AWS SAM CLI facilitates this step too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;sam build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This creates the actual artifacts that will be deployed to AWS, which in this case are the AWS Lambda function code and the CloudFormation template. &lt;/p&gt;

&lt;p&gt;If you have Docker installed you can &lt;strong&gt;test your API endpoint locally before deploying to AWS&lt;/strong&gt;. Supposing you have so, you can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;sam &lt;span class="nb"&gt;local &lt;/span&gt;start-api
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and navigate to the given endpoint on your localhost to see the 'Hello World' response!&lt;/p&gt;

&lt;p&gt;At this point, you are ready to deploy your Hello World serverless app, but that's not what we have been asked to do 🤔&lt;/p&gt;

&lt;p&gt;From the initial challenge spec: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your task for today: create a REST API endpoint that spins a dreidel and randomly returns נ (Nun), ג (Gimmel), ה (Hay), or ש (Shin). This sounds like a great opportunity to use a serverless function to create an endpoint that any application can call!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We have to modify our &lt;code&gt;app.js&lt;/code&gt; file to solve this problem. My solution goes like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lambdaHandler&lt;/span&gt; &lt;span class="o"&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;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&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;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;possibleResults&lt;/span&gt; &lt;span class="o"&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;נ&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;ג&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;ה&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;ש&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&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;statusCode&lt;/span&gt;&lt;span class="dl"&gt;'&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;body&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="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="na"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;possibleResults&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;possibleResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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;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;return&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;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I created an array with the four possible results and made the endpoint randomly return one of them in JSON format.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying the app
&lt;/h3&gt;

&lt;p&gt;According to the tutorial you can now go ahead and deploy the app with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight shell"&gt;&lt;code&gt;sam deploy &lt;span class="nt"&gt;--guided&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;but I got an error saying that the IAM role for the Lambda function was not found. As far as I know, I gave permission to the AWS SAM CLI to create the necessary roles to run the app. I also tried creating the IAM role first before deploying the function; however, I got the same error. &lt;/p&gt;

&lt;p&gt;I ended up deleting this piece of code from the &lt;code&gt;Outputs&lt;/code&gt; section of the &lt;code&gt;template.yaml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;HelloWorldFunctionIamRole&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;Description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Implicit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;IAM&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Role&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;for&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Hello&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;World&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;function"&lt;/span&gt;
    &lt;span class="na"&gt;Value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;!GetAtt&lt;/span&gt; &lt;span class="s"&gt;HelloWorldFunctionRole.Arn&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;and this allowed the deployment process to create an IAM role for the function. &lt;/p&gt;

&lt;p&gt;With that out of the way, the deploy command should work correctly and return you an HTTPS endpoint from API Gateway to call your API 🥳&lt;/p&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/davidojedalopez/25-days-of-serverless-day-01"&gt;my GitHub repo with the final version of all the files&lt;/a&gt;. You can notice that I changed how the endpoint and resources are named, but nothing else.&lt;/p&gt;




&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;p&gt;This concludes the first day challenge of 25 Days of Serverless.&lt;/p&gt;

&lt;p&gt;At first, I was overwhelmed by the amount of boilerplate created just to run a single AWS Lambda function, but later I realized that all that code includes very useful things like the HTTPS API endpoint access and the advantage of having every resource codified; similar to &lt;em&gt;Infrastructure-as-Code&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I also noticed that some files for testing were created. I haven't yet tried to create tests for a serverless app. That should come soon though!&lt;/p&gt;




&lt;p&gt;Thanks a lot for reading me! I learned a lot just by doing this first challenge, can't wait for the rest! 💙&lt;/p&gt;

</description>
      <category>25daysofserverless</category>
      <category>serverless</category>
      <category>aws</category>
    </item>
    <item>
      <title>My privacy setup 🔐</title>
      <dc:creator>David Ojeda</dc:creator>
      <pubDate>Tue, 20 Aug 2019 23:42:54 +0000</pubDate>
      <link>https://dev.to/david_ojeda/my-privacy-setup-3k49</link>
      <guid>https://dev.to/david_ojeda/my-privacy-setup-3k49</guid>
      <description>&lt;p&gt;&lt;strong&gt;One of the most precious things in this modern world is data, our own personal data.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://i.giphy.com/media/10fAvEln0lB8Lm/giphy.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://i.giphy.com/media/10fAvEln0lB8Lm/giphy.gif" alt="Knowledge is power Game Of Thrones GIF"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With data, companies can persuade us to do, buy, like, and do whatever fits their interests- and most of the time those interests not really matches ours.&lt;/p&gt;

&lt;p&gt;I'm confident that you have already been in this situation: you are talking to a friend about, let's say, going to Disneyland, and a couple hours later while happily scrolling through your Instagram feed you see a sponsored Disneyland add. &lt;strong&gt;It's not a coincidence&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Or maybe you were chatting with a friend through Whatsapp, talking about babies. Just to receive an email from Amazon about some baby clothes offers. &lt;strong&gt;It's not a coincidence&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Many apps read, hear, and collect more information than we can imagine.&lt;/strong&gt; Instagram hears what you say, Whatsapp reads what you write, Google knows where you are, and all that data is shared among so many other 3rd parties looking for profit. &lt;strong&gt;Knowledge is power&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you think, "I don't really care, I have nothing to hide", &lt;strong&gt;it's not about hiding something&lt;/strong&gt;. It's about unauthorized entities knowing everything about you. It's your life.&lt;/p&gt;

&lt;p&gt;And in order to protect your data, your privacy, I put together this post with the details of my own privacy setup. It includes some rules I follow, as well as apps that help me protect my privacy.&lt;/p&gt;




&lt;p&gt;Some general rules that apply to most of the things you do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't give away your information just because it's easy.&lt;/strong&gt; If you are signing up for a new service, I recommend you to investigate it before. You don't want to share details about you with a company that is known to sell them to someone else&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;For some temporary signups, you don't need real information.&lt;/strong&gt; Some establishments, like coffee shops, ask you for your email address and name to connect to their WiFi, but most times you don't need a real email address since they don't ask for confirmation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Check allowed permissions of apps you install&lt;/strong&gt;. There are apps that ask for way more than they need. If you can match every permission with a specific feature of the app, then you are probably good to go. &lt;a href="https://lifehacker.com/why-does-this-android-app-need-so-many-permissions-5991099" rel="noopener noreferrer"&gt;Here is a Lifehacker guide for more information on the topic&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review privacy settings&lt;/strong&gt;. Many apps have a privacy section on its settings where you can change what data can they collect or if targeted ads should be shown to you. Here's Twitter's for example:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ft8nxxn9aa5ptefiy5zqq.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%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Ft8nxxn9aa5ptefiy5zqq.png" alt="Twitter's privacy settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now let's get to the apps I use!&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://brave.com/" rel="noopener noreferrer"&gt;Brave&lt;/a&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F3a36yfzdvmxcn3pi6bth.png" alt="Brave logo"&gt;
&lt;/h2&gt;

&lt;p&gt;An open source privacy-first browser that comes with ad and tracker blocker by default. I have never missed Chrome or Firefox since I made the switch. &lt;/p&gt;

&lt;p&gt;It's based on Chromium, so all your Developer Tools options are available to you. It also lets you open new tabs with Tor, for a truly private experience. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://duckduckgo.com/about" rel="noopener noreferrer"&gt;DuckDuckGo&lt;/a&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fwkvk5325np71enfttr8n.png" alt="DuckDuckGo"&gt;
&lt;/h2&gt;

&lt;p&gt;A privacy-first search engine. DuckDuckGo doesn't store any of your searches, nor does it track you in any way. Every search you make is as it was your first search ever!&lt;/p&gt;

&lt;p&gt;It does not have some quick responses as Google does, but it's totally worth it anyway. I have never missed Google Search either. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.signal.org/" rel="noopener noreferrer"&gt;Signal&lt;/a&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fuppqj8u5f3ht0n24jtb5.png" alt="Signal app logo"&gt;
&lt;/h2&gt;

&lt;p&gt;Signal is a messaging app that, you guessed it, it's privacy-first; it offers E2E encryption on everything. Plus, it's open source and a nonprofit organization. This app is even recommended by Edward Snowden!&lt;/p&gt;

&lt;p&gt;The downside I have found so far is that not many people are willing to switch or use yet another messaging platform. Whatsapp is way to strong here in México at least.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://play.google.com/store/apps/details?id=com.samruston.permission&amp;amp;hl=en_US" rel="noopener noreferrer"&gt;Bouncer&lt;/a&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2Fle8kwoixrl4oxqlx491k.png" alt="Bouncer logo"&gt;
&lt;/h2&gt;

&lt;p&gt;This one is only for Android users. Bouncer is an app that allows you to grant permissions temporarily. It will automatically deactivate permissions after you have used the feature that required them. &lt;/p&gt;

&lt;p&gt;For example, if you were to share your location using Whatsapp, you would need to accept the location permission first, then Bouncer will ask you &lt;em&gt;once&lt;/em&gt; if you would like to remove or keep the permission after you have finished using it. As soon as you exit the app, Bouncer will remove location permissions from Whatsapp so it can't access them in the background. It's awesome!&lt;/p&gt;

&lt;p&gt;Moreover, Bouncer doesn't even require internet permission, so you shouldn't fear of it sharing your information.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.torproject.org/" rel="noopener noreferrer"&gt;Tor&lt;/a&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F2e88r004x14pdkm0z6ab.png" alt="Tor Project"&gt;
&lt;/h2&gt;

&lt;p&gt;One of the world's strongest tool for privacy right now. It comes in many forms such as Tor Browser, Tor OS and private tabs with Tor from Brave, and it allows you to navigate the internet freely and with privacy thanks to its multiple layers of encryption.&lt;/p&gt;

&lt;p&gt;It is slower than normal since the traffic goes through a series of relays, but for some people such as bloggers or journalists that must remain private, it's a jewel 💎.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://nordvpn.com" rel="noopener noreferrer"&gt;NordVPN&lt;/a&gt; &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fthepracticaldev.s3.amazonaws.com%2Fi%2F5iis11jwf5veeek5sevt.png" alt="NordVPN logo"&gt;
&lt;/h2&gt;

&lt;p&gt;A VPN will allow you to protect your IP address and to make sure no one can see which websites you visit. It can also help you block ads and protect you on public W-Fi networks. &lt;/p&gt;

&lt;p&gt;I use NordVPN and it works just great! It offers hundreds of servers around the world and some more specialty servers such as double VPN, P2P and Onion over VPN.&lt;/p&gt;




&lt;p&gt;There are some specific apps/companies that I try to avoid whenever possible. Here are some of them:&lt;/p&gt;

&lt;h2&gt;
  
  
  Google
&lt;/h2&gt;

&lt;p&gt;I personally don't trust many giant tech companies like Google. They make really good products, I can't deny that. However, their business model thrives when users give away their privacy. &lt;/p&gt;

&lt;p&gt;Take Gboard for example. This keyboard will have access to your camera, microphone, location and basically to anything you type with it. I admit the keyboard has really great features, but I would rather use something else than this keyboard just from the fact it comes from Google. &lt;/p&gt;

&lt;p&gt;I do use Google Maps frequently, I just have many features disabled such as recommendations and location history. &lt;/p&gt;

&lt;h2&gt;
  
  
  Social networks
&lt;/h2&gt;

&lt;p&gt;This should be a no-brainer. Social networks profit from gathering personal information and then using it for ad targeting. I don't have Instagram nor Facebook, but I still have Twitter. I don't use the native apps though, I always check it on the website be it on mobile or desktop.&lt;/p&gt;

&lt;p&gt;Leaving your social networks might feel impossible to you since you might have family and friends with whom you talk to or at least follow, and that's okay. I would recommend just not to share too many personal details on your posts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing out
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Your privacy is rapidly becoming a privilege rather than a right, that's why I use all these apps and tips to keep mine.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I don't pretend to cover everything there is about online privacy on this post, but hopefully it sparks your curiosity to learn and do more to protect what's yours. I still have plenty of things to do, and my next objective is to completely stop using Google Products.&lt;/p&gt;

&lt;p&gt;Here are many resources I have studied/read/watched to keep myself informed about this. I highly recommend to &lt;strong&gt;check the DuckDuckGo resources below&lt;/strong&gt;, they have immense high quality information about why you should care about privacy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DuckDuckGo resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://spreadprivacy.com" rel="noopener noreferrer"&gt;Spread Privacy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spreadprivacy.com/three-reasons-why-the-nothing-to-hide-argument-is-flawed/" rel="noopener noreferrer"&gt;Three reasons why the nothing to hide argument is flawed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spreadprivacy.com/tag/privacy-newsletter/" rel="noopener noreferrer"&gt;Privacy newsletter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href="https://ethical.net/resources/" rel="noopener noreferrer"&gt;Ethical alternatives&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href="https://www.imdb.com/title/tt3774114/?ref_=fn_al_tt_1" rel="noopener noreferrer"&gt;Snowden's documentary&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href="https://www.amazon.com/Permanent-Record-Edward-Snowden/dp/1250237238?SubscriptionId=AKIAILSHYYTFIVPWUY6Q&amp;amp;tag=duckduckgo-brave-20&amp;amp;linkCode=xm2&amp;amp;camp=2025&amp;amp;creative=165953&amp;amp;creativeASIN=1250237238" rel="noopener noreferrer"&gt;Permanent Record, by Edward Snowden&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href="https://darknetdiaries.com" rel="noopener noreferrer"&gt;Darknet Diaries podcast&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;a href="https://spreadprivacy.com/delete-google-search-history/" rel="noopener noreferrer"&gt;Delete Google Search History&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Cover image was generated using &lt;a href="https://github.com/PJijin/Cover-Image-Generator/" rel="noopener noreferrer"&gt;Cover-Image-Generator&lt;/a&gt; 💙&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;I hope you find some of what I have shared here useful, and if you know something else I should be doing please leave me a comment!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>privacy</category>
    </item>
  </channel>
</rss>
