<?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: Eleni Papanicolas</title>
    <description>The latest articles on DEV Community by Eleni Papanicolas (@e_papanicolas).</description>
    <link>https://dev.to/e_papanicolas</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%2F760512%2F679aac5d-8e5f-4eae-993a-8df5b1792102.jpg</url>
      <title>DEV Community: Eleni Papanicolas</title>
      <link>https://dev.to/e_papanicolas</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/e_papanicolas"/>
    <language>en</language>
    <item>
      <title>uploading images using cloudinary: a comparison between ruby/rails and node/express</title>
      <dc:creator>Eleni Papanicolas</dc:creator>
      <pubDate>Mon, 25 Apr 2022 21:56:54 +0000</pubDate>
      <link>https://dev.to/e_papanicolas/uploading-images-using-cloudinary-a-comparison-between-rubyrails-and-nodeexpress-4ke3</link>
      <guid>https://dev.to/e_papanicolas/uploading-images-using-cloudinary-a-comparison-between-rubyrails-and-nodeexpress-4ke3</guid>
      <description>&lt;p&gt;Uploading images on the front end, using a form in React, is fairly easy, when compared with the back end set up. I learned how to do it in Ruby on Rails first, and now using Node.js and Express. In both, despite pouring over numerous tutorials and tons of documentation, I found it wasn't exactly as straightforward as all these resources made it look. Through tons of debugging though, I finally found my solution. I wanted to share some thoughts and some of the differences I found when learning how to configure Cloudinary with different back end languages and frameworks.&lt;/p&gt;

&lt;h2&gt;
  
  
  FRONT END FIRST
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="nx"&gt;onSubmit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleSubmitNewImage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;encType&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;multipart/form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt; &lt;span class="nx"&gt;htmlFor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;
              &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image/*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/label&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;          &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here I have set up up a form which will render a file chooser. The &lt;code&gt;enctype="multipart/form"&lt;/code&gt; is necessary because when we are attaching an image to the request object, we lose the ability to use &lt;code&gt;JSON.stringify()&lt;/code&gt;, and we must use &lt;code&gt;new FormData()&lt;/code&gt; and append our image file. Then in the body we just send the formData as it is.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;FormData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&amp;lt;your-endpoint&amp;gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&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;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  CLOUDINARY
&lt;/h2&gt;

&lt;p&gt;Cloudinary is an exceptionally easy to use cloud storage solution for uploading images and storing a url path in a database. I used postgreSQL, a relational database, when I uploaded images using Rails, and I used mongoDB, a non relational database, when I did the same with Node and Express. Cloudinary worked great in both environments. I created an account for free &lt;a href="https://cloudinary.com/users/register/free"&gt;on Cloudinary's website&lt;/a&gt;, and they even provide a large amount of storage for free! After that I was navigated to my dashboard where I found the SDK Setup, which gave you code snippets for many languages, that help the configuration process.&lt;/p&gt;

&lt;h2&gt;
  
  
  RAILS
&lt;/h2&gt;

&lt;p&gt;For our Rails setup, I ran &lt;code&gt;gem install cloudinary&lt;/code&gt;, and then in a &lt;code&gt;storage.yml&lt;/code&gt; file, added &lt;code&gt;cloudinary: service: Cloudinary&lt;/code&gt;. Rails comes fully loaded with Active Storage as a built in method for handling image uploads, and running &lt;code&gt;rails active_storage:install&lt;/code&gt; and &lt;code&gt;rails db:migrate&lt;/code&gt;, abstracts away the set up for a polymorphic join table between &lt;code&gt;active_storage_blobs&lt;/code&gt; and &lt;code&gt;active_storage_attachments&lt;/code&gt;, which you will notice now exists in the schema. We won't touch these tables going forward, but this stores lots of information about the upload, and an attachment to it.&lt;/p&gt;

&lt;p&gt;In the model, we will use this reference to an attachment, where we set &lt;code&gt;has_one_attached :image&lt;/code&gt; instead of creating a column in the database. We add  &lt;code&gt;Rails.application.routes.url_helpers&lt;/code&gt; to the code at the top of the model, which can be used to generate URLs given a set of parameters. In the serializer, I will call the &lt;code&gt;get_image_url&lt;/code&gt; method as an attribute to send the client a url generated for &lt;code&gt;self.image&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationRecord&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url_helpers&lt;/span&gt;

  &lt;span class="n"&gt;has_one_attached&lt;/span&gt; &lt;span class="ss"&gt;:image&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_image_url&lt;/span&gt;
    &lt;span class="n"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then on my React front end, I can simply set an &lt;code&gt;&amp;lt;img src={user.get_image_url}&amp;gt;&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  NODE &amp;amp; EXPRESS
&lt;/h2&gt;

&lt;p&gt;Using Node, Express, and MongoDB (with Mongoose) has been a fun process. I ran into a lot of errors and got to have some fun debugging by going over lots of juicy documentation. I landed on using a few packages: &lt;code&gt;npm install cloudinary multer multer-storage-cloudinary&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In a non relational database, we still use a Schema to organize and validate data. Using mongoose seemed like a good way to go. A new Schema is quickly available by importing &lt;code&gt;mongoose&lt;/code&gt; and &lt;code&gt;{Schema}&lt;/code&gt;. The same way we only stored information about the uploaded image, instead of the image data in the database, we set the image object to contain a url, with a type of &lt;code&gt;String&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;UserSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Schema&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After creating the Schema, we are ready to configure Cloudinary. In the code below, &lt;code&gt;cloudinary.config&lt;/code&gt; refers to the section above on the SDK Setup. &lt;/p&gt;

&lt;p&gt;Multer is a piece of middleware for handling &lt;code&gt;multipart/form-data&lt;/code&gt;, primarily for uploading files. It creates a body object, and a file object and adds them to the request object. The file object is what we will use to upload the image, but the body will make sure that other pieces of data sent in the request are processed properly for their data types as well. Multer-storage-cloudinary is a storage engine that easily allows you to configure storage, using options passed in as &lt;code&gt;params&lt;/code&gt;. Choose from an &lt;a href="https://cloudinary.com/documentation/image_upload_api_reference#upload_method"&gt;extensive list&lt;/a&gt; of optional parameters. &lt;code&gt;Params&lt;/code&gt; itself is also optional, so the only requirement is the Cloudinary API object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;multer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cloudinary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudinary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;CloudinaryStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;multer-storage-cloudinary&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;cloud_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;eleni&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;your-api-key&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;api_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;your-api-secret&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CloudinaryStorage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cloudinary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;users&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;storage&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We created the &lt;code&gt;parser&lt;/code&gt; which is the last part of our middleware, and it is inserted into the route, where it will capture the incoming request, attach a file, and pass it along. Here in the route we create an &lt;code&gt;image&lt;/code&gt; object, and set the url (which we created in the Schema) to one of the properties of the file object, the path, which is a url pointing to where the image is stored on Cloudinary! And then we create the rest of the new user and send it to the client, where it can be accessed by adding &lt;code&gt;&amp;lt;img src={user.image.url}&amp;gt;&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&amp;lt;your-endpoint&amp;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;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;image&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&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;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  ONE LAST TIP
&lt;/h2&gt;

&lt;p&gt;My biggest &amp;amp; silliest hang up working this, was because in my &lt;code&gt;server.js&lt;/code&gt; file, I was using &lt;code&gt;body-parser&lt;/code&gt; and running &lt;code&gt;app.use(bodyParser.urlencoded({ extended: false }))&lt;/code&gt;. When I switched to using &lt;code&gt;app.use(bodyParser.urlencoded({ extended: true }))&lt;/code&gt; ... it was a pretty glorious moment! &lt;/p&gt;

&lt;h2&gt;
  
  
  THE RESULTS
&lt;/h2&gt;

&lt;p&gt;I honestly don't know which I prefer. I wrote this with hopes that it would help me decide if I preferred either, and the answer to that is still &lt;code&gt;undefined&lt;/code&gt; 😂. Both languages and environments handle image uploads beautifully. Rails and Active Storage require less packages and configuration, but Node and Express allowed more flexibility as far as control of the data. Comment and let me know which you like better!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>using actioncable for websockets with a streaming api endpoint</title>
      <dc:creator>Eleni Papanicolas</dc:creator>
      <pubDate>Fri, 25 Mar 2022 14:18:59 +0000</pubDate>
      <link>https://dev.to/e_papanicolas/using-actioncable-for-websockets-with-a-streaming-api-endpoint-1nog</link>
      <guid>https://dev.to/e_papanicolas/using-actioncable-for-websockets-with-a-streaming-api-endpoint-1nog</guid>
      <description>&lt;h2&gt;
  
  
  What are websockets?
&lt;/h2&gt;

&lt;p&gt;Firstly, we know that HTTP is a unidirectional communication protocol that uses a request and response cycle. Each time a new request or response is sent, a new connection must be established. We see this often in fetch calls to RESTful APIs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rRgoc7ND--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x2m7i4brmrt1achc3ozn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rRgoc7ND--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x2m7i4brmrt1achc3ozn.png" alt="Diagram of HTTP protocol vs websocket protocol" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like HTTP protocol uses &lt;strong&gt;http://&lt;/strong&gt; and &lt;strong&gt;https://&lt;/strong&gt;, websockets use &lt;strong&gt;ws://&lt;/strong&gt; and &lt;strong&gt;wss://&lt;/strong&gt;. This diagram shows the major difference between HTTP and websocket connections.&lt;/p&gt;

&lt;p&gt;After the request is sent in a websocket connection, instead of the connection closing and a response being initiated, the client and server have a handshake that allows the connection to be upgraded to websocket. This communication link is bidirectional, or full-duplex, and remains open so that messages can freely be exchanged between server and client without interruption until the connection is closed by either side.&lt;/p&gt;

&lt;h2&gt;
  
  
  What can we do with them?
&lt;/h2&gt;

&lt;p&gt;Websockets are used in all sorts of real-time applications. I recently developed a full stack application that lets users create event boards to follow hashtags, and use those boards to stream live tweets from Twitter by connecting to Twitter's Streaming API endpoint. I wanted to share how I got started using Ruby on Rails ActionCable for websockets on the back end and @rails/actioncable npm package for the React front end.&lt;/p&gt;

&lt;p&gt;Ruby on Rails comes with a built in package called ActionCable which provides seamless websocket integration on the back end. Once a channel has been established, we are able to easily call on ActionCable to broadcast anything we'd like! &lt;/p&gt;

&lt;p&gt;To create this project I signed up for &lt;a href="https://developer.twitter.com/en/docs/twitter-api/getting-started/getting-access-to-the-twitter-api"&gt;Twitter Developer Credentials&lt;/a&gt; and stored my API Keys in an &lt;code&gt;config/application.yml&lt;/code&gt; file which I hid with a &lt;code&gt;.gitignore&lt;/code&gt; file. I had decided on using Twitter's Filtered Stream endpoint, so I went to the &lt;a href="https://github.com/twitterdev/Twitter-API-v2-sample-code/blob/main/Filtered-Stream/filtered_stream.rb"&gt;Twitter-API-v2-sample-code&lt;/a&gt; repo to get started. The repo contains examples of all of the different streaming endpoints and sample code in multiple languages.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The filtered stream endpoint group enables developers to filter the real-time stream of public Tweets. This endpoint group’s functionality includes multiple endpoints that enable you to create and manage rules, and apply those rules to filter a stream of real-time Tweets that will return matching public Tweets. This endpoint group allows users to listen for specific topics and events in real-time, monitor the conversation around competitions, understand how trends develop in real-time, and much more.&lt;/em&gt;&lt;br&gt;
&lt;a href="https://developer.twitter.com/en/docs/twitter-api/tweets/filtered-stream/introduction"&gt;developer.twitter.com&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The sample Ruby code for connecting to the streaming endpoint looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Connects to the stream and returns data (Tweet payloads) in chunks&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="ss"&gt;timeout: &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;method: &lt;/span&gt;&lt;span class="s1"&gt;'get'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s2"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"v2FilteredStreamRuby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Bearer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@bearer_token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="vi"&gt;@request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Typhoeus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vi"&gt;@stream_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="vi"&gt;@request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_body&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="vi"&gt;@request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point I was able to connect to the stream in my Rails console by running &lt;code&gt;TwitterStream.stream_connect()&lt;/code&gt; which was very exciting. Further configuration is required to make use of the raw data our connection is receiving.&lt;/p&gt;

&lt;p&gt;First we need to create the &lt;code&gt;TweetChannel&lt;/code&gt;, which can be done manually or by running &lt;code&gt;rails g channel tweet&lt;/code&gt; to have Rails generate one for you. Then we need to add in a little more information to let the channel know where to stream from. I go over this more below in the section on the front end setup.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TweetChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Channel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
   &lt;span class="n"&gt;stream_from&lt;/span&gt; &lt;span class="s2"&gt;"tweet_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:rule&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my &lt;code&gt;EventsController&lt;/code&gt;, I have an action that is triggered by a front end fetch request, that calls &lt;code&gt;TwitterStream.stream_connect(event)&lt;/code&gt; and passes in an event object with some properties that I use to modify the way this application connects to the Twitter streaming endpoint. The starter docs provided by Twitter are a great place to start but of course every application will have unique needs, and there is so much more we can add in ourselves.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nc"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Connecting to Twitter Stream"&lt;/span&gt;
      &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s2"&gt;"expansions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"attachments.media_keys,author_id,entities.mentions.username,geo.place_id,in_reply_to_user_id,referenced_tweets.id,referenced_tweets.id.author_id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"tweet.fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"author_id,created_at,entities,attachments,geo,id,in_reply_to_user_id,lang,public_metrics"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"user.fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"name,username,verified,profile_image_url"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s2"&gt;"media.fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"preview_image_url,alt_text,url"&lt;/span&gt;

      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="ss"&gt;timeout: &lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="ss"&gt;method: &lt;/span&gt;&lt;span class="s1"&gt;'get'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
          &lt;span class="ss"&gt;headers: &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="s2"&gt;"User-Agent"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"v2FilteredStreamRuby"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Bearer &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="no"&gt;BEARER_TOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="ss"&gt;params: &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Typhoeus&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;STREAM_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on_body&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
        &lt;span class="no"&gt;ActionCable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;broadcast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tweet_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rule_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="ss"&gt;body: &lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
      &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we can see that above in the original sample code, Twitter had &lt;code&gt;puts chunk&lt;/code&gt; which printed to the console any incoming data, which I replaced with &lt;code&gt;ActionCable.server.broadcast("tweet_#{event.rule_id}", { body: chunk});&lt;/code&gt;, which is sending the params &lt;code&gt;event.rule_id&lt;/code&gt; to the &lt;code&gt;TweetChannel&lt;/code&gt; we created above, and &lt;em&gt;also&lt;/em&gt; sending the &lt;code&gt;chunk&lt;/code&gt; of raw data out to make its way to our front end. That single line of code is the game changer here, allowing us to simply broadcast anything coming through the websocket from Twitter, directly to the TweetChannel, and then on to our React front end.&lt;/p&gt;

&lt;p&gt;Lastly, let's make sure to put &lt;code&gt;mount ActionCable.server =&amp;gt; '/cable'&lt;/code&gt; into our &lt;code&gt;config/routes.rb&lt;/code&gt; to make sure we have somewhere to send a request for a websocket connection.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://guides.rubyonrails.org/action_cable_overview.html"&gt;Ruby on Rails docs&lt;/a&gt; has a lot of great information on how to use ActionCable in different parts of your application. But lets move over to the front end to see how all of this fits together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Front end
&lt;/h3&gt;

&lt;p&gt;The first step is to install the  &lt;code&gt;@rails/actioncable&lt;/code&gt; npm package so that we can connect the front end with our rails back end. Run &lt;code&gt;npm install @rails/actioncable&lt;/code&gt; and we are ready to go.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createConsumer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@rails/actioncable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;cable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;cable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ws://localhost:3000/cable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// lets the back end know the channel to broadcast on&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;channelObj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;TweetChannel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;rule&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;rule_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handlers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&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 you can add code to do something with&lt;/span&gt;
      &lt;span class="c1"&gt;// the data received from the websocket connection&lt;/span&gt;
         &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="nx"&gt;connected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;connected&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;disconnected&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;disconnected&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;cable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
         &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subscription&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
      &lt;span class="nx"&gt;cable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subscriptions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;channelObj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;handlers&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;subscription&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unsubscribe&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nx"&gt;cable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="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;Above is some very basic boilerplate code for getting started. Consumers &lt;strong&gt;require&lt;/strong&gt; an instance of the connection on the client side. Using &lt;code&gt;createConsumer()&lt;/code&gt; will get a connection to &lt;code&gt;/cable&lt;/code&gt; ready on the server by default. But it won't actually do anything until you have specified a subscription. With our handlers and our channel object we are getting our parameters ready to be passed in to &lt;code&gt;cable.current.subscription.create()&lt;/code&gt;. At the top of the file we create a variable &lt;code&gt;cable&lt;/code&gt; and assign it to &lt;code&gt;useRef()&lt;/code&gt;. This, combined with the code checking for &lt;code&gt;!cable.current&lt;/code&gt;, ensure that we do not create a new subscription every time the component is re-rendered. This is going to help avoid glitchy behavior, and especially if in use with a chat style app, this will help avoid duplicate or out of order messages. The &lt;code&gt;useEffect&lt;/code&gt; will make sure that this subscription is created, or found, on component renders.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;channelObject&lt;/code&gt; has 2 key/value pairs &lt;code&gt;channel: "TweetChannel"&lt;/code&gt; and &lt;code&gt;rule: event.rule_id&lt;/code&gt;, which correspond to the &lt;code&gt;TweetChannel&lt;/code&gt; that we created earlier. When the subscription is created on the front end, it sends the information to the &lt;code&gt;TweetChannel&lt;/code&gt; which it uses to &lt;code&gt;stream_from&lt;/code&gt; the correct websocket connection.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TweetChannel&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ApplicationCable&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Channel&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;subscribed&lt;/span&gt;
   &lt;span class="n"&gt;stream_from&lt;/span&gt; &lt;span class="s2"&gt;"tweet_&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:rule&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the received method of the handlers variable, I take the incoming data, create a new Tweet object, where it is then added to a Tweets array, and animated onto the screen.&lt;/p&gt;

&lt;p&gt;This project was the absolute most fun to create, and if you would like to see more details about it, check out this &lt;a href="https://www.youtube.com/watch?v=urTOZf8Z2A4"&gt;demo video&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Github repositories:&lt;/strong&gt;&lt;br&gt;
Front end: &lt;a href="https://github.com/e-papanicolas/tweet-front-end"&gt;https://github.com/e-papanicolas/tweet-front-end&lt;/a&gt;&lt;br&gt;
Back end: &lt;a href="https://github.com/e-papanicolas/tweet-back-end"&gt;https://github.com/e-papanicolas/tweet-back-end&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rails</category>
      <category>actioncable</category>
      <category>react</category>
      <category>websockets</category>
    </item>
    <item>
      <title>the full stack &amp;&amp; how it all comes together</title>
      <dc:creator>Eleni Papanicolas</dc:creator>
      <pubDate>Wed, 26 Jan 2022 22:12:28 +0000</pubDate>
      <link>https://dev.to/e_papanicolas/the-full-stack-how-it-all-comes-together-317a</link>
      <guid>https://dev.to/e_papanicolas/the-full-stack-how-it-all-comes-together-317a</guid>
      <description>&lt;p&gt;&lt;strong&gt;So, back end + front end = full stack ...right?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It's not as simple as that. A quick google search got this as the definition of full stack:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In technology development, full stack refers to an entire computer system or application from the front end to the back end and the code that connects the two. The back end of a computer system encompasses “behind-the-scenes” technologies such as the database and operating system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ok, so let's break this down together.&lt;/p&gt;

&lt;p&gt;There is a great analogy of a restaurant that helps in understanding the concept of front and back ends in development. As a former industry member, this one is fun for me to talk about! In a restaurant, the kitchen is considered the back of house and the dining area is the front of house, and so those who work in the kitchen work in back of house positions, and those who work in the dining area work in front of house positions. The whole restaurant in this situation would be our full stack application. In the restaurant, a server is going to take the order of the guest and send it to the kitchen, then receive the food when it is ready, and bring it back to the guest. In the same way, our front end, or our client, is the guest. Our back end is the kitchen. Our restaurant server is the middleware, or the code that connects the two ends. So from this we can see why back end + front end = full stack isn't so simple. We have lots of other moving parts as we get further into what each end fully encompasses.&lt;/p&gt;

&lt;p&gt;The code on the front end, or the client side, handles what users interact with. Front end languages include HTML, CSS, and JavaScript and there are even more frameworks to go along with them.&lt;/p&gt;

&lt;p&gt;The back end is everything that is not visible to the client. The server and the database are two separate pieces of the back end. Some popular back end languages are Python, Ruby and PHP, as well as corresponding frameworks. Popular databases include MySQL, PostgreSQL, and MongoDB. &lt;/p&gt;

&lt;p&gt;It can be a bit daunting to just get started with so many options and so many different pieces to fit together. Today we are going to go over a quick and easy way to get a full stack application up and running using JavaScript and React.js for the front end, and SQLite for our database. For the back end we are going to use Ruby, and the Sinatra library. &lt;/p&gt;

&lt;h1&gt;
  
  
  front end
&lt;/h1&gt;

&lt;p&gt;Let's get started! The front end seems like a good place to start, it has a little bit less going on. So first, in your terminal you will want to run &lt;code&gt;$ npx create-react-app &amp;lt;my-app-name-here&amp;gt;&lt;/code&gt;. &lt;em&gt;Note: Make sure you use npx and not npm to make sure that you are getting the latest version.&lt;/em&gt; This command will install packages and create a new folder with a basic file structure to help you get started. &lt;code&gt;$ cd &amp;lt;my-app-name-here&amp;gt;&lt;/code&gt; and run &lt;code&gt;$ npm start&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;You should see success messages in the terminal, and if you open &lt;code&gt;http://localhost:3000&lt;/code&gt; your app should be up and running with the initial files provided by React.&lt;/p&gt;

&lt;p&gt;React has robust &lt;a href="https://reactjs.org/"&gt;documentation here&lt;/a&gt;, as well as a great guide to &lt;a href="https://create-react-app.dev/docs/getting-started"&gt;quickly getting started&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After the creation of the project the file structure should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-app-name-here/
  README.md
  node_modules/
  package.json
  public/
    index.html
    favicon.ico
  src/
    App.css
    App.js
    App.test.js
    index.css
    index.js
    logo.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the exception of &lt;code&gt;public/index.html&lt;/code&gt; and &lt;code&gt;src/index.js&lt;/code&gt;, the rest is ours to change, edit, build, add to! Make sure to run &lt;code&gt;$ npm install&lt;/code&gt; to install any dependencies in the package.json file, and again anytime you add new ones. Once you have built out the front end some more, added components, used some hooks...it's time to get the back end up and running. &lt;/p&gt;

&lt;h1&gt;
  
  
  back end
&lt;/h1&gt;

&lt;p&gt;Let's take a look at the file structure first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LG6vkD8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fcdula95mmn3wlszffy0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LG6vkD8C--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fcdula95mmn3wlszffy0.png" alt="code file structure" width="880" height="696"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;app/controllers&lt;/code&gt; is where we store the code for writing the web server&lt;br&gt;
&lt;code&gt;app/models&lt;/code&gt; is where we store the models, which are responsible for accessing and updating data in the database&lt;br&gt;
&lt;code&gt;config&lt;/code&gt; folder will hold our environment setup, is where we require files and gems, and establish a connection to the database&lt;br&gt;
&lt;code&gt;config/database.yml&lt;/code&gt; is where we will establish a connection to the database&lt;br&gt;
&lt;code&gt;db/migrate&lt;/code&gt; is where our database migrations are stored, of which are responsible for creating and altering the structure of the database&lt;br&gt;
&lt;code&gt;db/schema.rb&lt;/code&gt; is auto-generated by the current state of the database and gives an overview of the structure of the database&lt;br&gt;
&lt;code&gt;db/seed.rb&lt;/code&gt; lets us easily add sample data to the database while it is being developed and tested&lt;br&gt;
&lt;code&gt;config.ru&lt;/code&gt; is the file that runs the code for writing the web server that is stored in the controllers &lt;br&gt;
&lt;code&gt;Gemfile&lt;/code&gt; is where we list all the gems our application depends on&lt;br&gt;
&lt;code&gt;Rakefile&lt;/code&gt; is where we can store code for common tasks so we can easily run them from the command line&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To get started, in a &lt;code&gt;Gemfile&lt;/code&gt;, we are using the following gems:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;sinatra, thin, rack-contrib, rack-cors, activerecord, sinatra-activerecord, rake, sqlite3, and require_all&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Run &lt;code&gt;$ bundle install&lt;/code&gt; to install all the dependencies. Anytime we add a gem we will need to rerun that command.&lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;config&lt;/code&gt; folder we will need an &lt;code&gt;environment.rb&lt;/code&gt; which will require all of our files and gems and ensure that everything is connected properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/environment.rb&lt;/span&gt;

&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'bundler/setup'&lt;/span&gt;
&lt;span class="no"&gt;Bundler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:default&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'RACK_ENV'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# Requires in all files in 'app' directory&lt;/span&gt;
&lt;span class="n"&gt;require_all&lt;/span&gt; &lt;span class="s1"&gt;'app'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  database
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;database.yml&lt;/code&gt; file is where we will give Active Record, one of our gems, all the details it needs to establish a connection to the database. We are using SQLite for this demo but Active Record also has support for other databases.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# config/database.yml&lt;/span&gt;

&lt;span class="ss"&gt;default: &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;
  &lt;span class="ss"&gt;adapter: &lt;/span&gt;&lt;span class="n"&gt;sqlite3&lt;/span&gt;
  &lt;span class="ss"&gt;pool: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sx"&gt;%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %&amp;gt;
  timeout: 5000

development:
  &amp;lt;&amp;lt;: *default
  database: db/development.sqlite3

test:
  &amp;lt;&amp;lt;: *default
  database: db/test.sqlite3

production:
  &amp;lt;&amp;lt;: *default
  database: db/production.sqlite3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have established a connection to the database, we can begin to create migrations that will create and alter the structure of our database. Rake will help run our migrations when we run &lt;code&gt;$ bundle exec rake db:create_migration NAME="&amp;lt;table-name&amp;gt;&lt;/code&gt;. A new file will appear in the &lt;code&gt;db/migrate&lt;/code&gt; folder. The names of the files should not be modified because they are created with a timestamp so that Active Record knows the proper order to run the migrations in. Once we have created our migration file we will need to add the necessary information to create the actual table and then we can run &lt;code&gt;$ bundle exec rake db:migrate&lt;/code&gt; to migrate those changes to the database. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;db/seeds.rb&lt;/code&gt; is where we will create dummy data to populate the database during development. It will be helpful for us to have this information to test out our models next.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;app/models&lt;/code&gt; will hold one file for each table in the database. In here we will define the class that will represent the model, and it will inherit from &lt;code&gt;ActiveRecord::Base&lt;/code&gt; to gain all of the methods the gem gives us. We can also add custom methods and associations to other tables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app/models/Item.rb&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;
  &lt;span class="n"&gt;belongs_to&lt;/span&gt; &lt;span class="ss"&gt;:location&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  server
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;config.ru&lt;/code&gt; will require the environment file, and will run our application controllers and write our server, which you can see above next to the file structure overview. &lt;/p&gt;

&lt;p&gt;Using Sinatra for this is quick and easy, as we did above, adding the &lt;code&gt;gem "sinatra", "~&amp;gt; 2.1"&lt;/code&gt; to our Gemfile. Then we can start defining our routes in &lt;code&gt;app/controllers&lt;/code&gt;. It is convention to separate the &lt;code&gt;application_controller&lt;/code&gt; routes into a single controller file for each model. So if there was a model named &lt;code&gt;Item&lt;/code&gt; in a table &lt;code&gt;items&lt;/code&gt;, the corresponding controller file would be &lt;code&gt;items_controller.rb&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yPV9_HG5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xo80gb3hi3ct8adg4dc8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yPV9_HG5--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xo80gb3hi3ct8adg4dc8.png" alt="app controller file" width="880" height="838"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this file, when defining the class for the controller, it is convention to use the capital case version of the file name; it will inherit from &lt;code&gt;Sinatra::Base&lt;/code&gt; so that we can use all of the methods included in the gem. As we can see, there are a few examples of CRUD actions using our new routes.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Rakefile&lt;/code&gt; is where we can define processes that are repetitive during development, but we do not want to have to do manually each time. Currently in this file there is a server command and a console command.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--TI3Z66aq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3wvacyc4quz00w3hwhxv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--TI3Z66aq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3wvacyc4quz00w3hwhxv.png" alt="rakefile" width="880" height="566"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now we get to connect everything with the front end. Once we have our back end server running, on the front end, in our React components we can define a quick get request to an endpoint on our back end which will get the list of all items that we defined in our controller file.&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;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`http://localhost:9292/items`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have success! A list of items is returned back to our front end. Now we can build out more endpoints on the back end, so that our front end can request more data, can request more organized, sorted, up to date data. So many possibilities!&lt;/p&gt;

&lt;p&gt;As you can see, the full stack is a lot more complex than just 1 + 1 = 2. This tutorial was made to provide a quick, straightforward way to get the bare bones of a full stack application up and running. &lt;strong&gt;Happy coding :)&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
      <category>sinatra</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>REACT-ing gracefully with controlled components</title>
      <dc:creator>Eleni Papanicolas</dc:creator>
      <pubDate>Tue, 21 Dec 2021 15:49:19 +0000</pubDate>
      <link>https://dev.to/e_papanicolas/react-ing-gracefully-with-controlled-components-5dgg</link>
      <guid>https://dev.to/e_papanicolas/react-ing-gracefully-with-controlled-components-5dgg</guid>
      <description>&lt;p&gt;&lt;strong&gt;I'm just a girl, searching for the single source of truth.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What are controlled components, and why? React form elements are similar to regular HTML forms in setup, but they are able to perform some extras for us when we use State. Unlike HTML forms where elements would typically maintain and update their own state based on user input, React uses mutable state that is kept in the &lt;code&gt;state&lt;/code&gt; variable and is updated with &lt;code&gt;setState()&lt;/code&gt;. But, using these methods together, is what brings us to that glorious single source of truth. When we tie the value of the element form to the state controlled by React, we form a controlled component. A controlled component &lt;em&gt;derives its input values from state.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We are able to use state with HTML elements like &lt;code&gt;&amp;lt;input /&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;select /&amp;gt;&lt;/code&gt;, and &lt;code&gt;&amp;lt;textarea /&amp;gt;&lt;/code&gt; when we pass it a value prop of the current state.&lt;/p&gt;

&lt;p&gt;Let's start with a quick form setup with two inputs below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;myControlledForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;favoriteColor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFavoriteColor&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&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="nx"&gt;favoriteColor&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;button&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Submit&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/form&lt;/span&gt;&lt;span class="err"&gt;&amp;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;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we have set up a state variable &lt;code&gt;name&lt;/code&gt; and it's setter function &lt;code&gt;setName&lt;/code&gt;. It has an initial state declared as an empty string, but which will be updated as soon as, and every time &lt;code&gt;setName&lt;/code&gt; is called. &lt;/p&gt;

&lt;p&gt;In the form elements themselves, we are tying the first input to the name state and the second input to the favorite color state. But we still need to wire up the &lt;code&gt;onChange&lt;/code&gt; event listener to an event handler callback function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleNameChange&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="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleColorChange&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="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By adding this event listener to an input as a prop, it will listen for change events. Each change, a callback function will fire to capture the user input value, and update its state variable.&lt;/p&gt;

&lt;p&gt;It is typical naming convention for your callback function to be named corresponding to the state variable it is handling the change for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleNameChange&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;setFirstName&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleColorChange&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;setLastName&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;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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;These functions use the &lt;code&gt;event.target.value&lt;/code&gt;, which is being provided by the inputs &lt;code&gt;onChange&lt;/code&gt; event handler to update the &lt;code&gt;state&lt;/code&gt; variable using its corresponding &lt;code&gt;setState&lt;/code&gt; function, which we declared above. When we update the state, we cause a re-render, and a cycle is completed. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why use controlled components though? Here are some key benefits:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Controlled inputs allow for more control over inputs and data.&lt;/li&gt;
&lt;li&gt;When we use state to store form input values, we can then share state with other components, and make changes in our applications. All components using this value will always have access to the exact same, current value.&lt;/li&gt;
&lt;li&gt;When we set up a form component, we can set the state in its parent component, and pass down variables and event handlers as props. This setup can allow for more dynamic changes in other parts of the application. &lt;/li&gt;
&lt;li&gt;We can also send the form data to other parts of our application for storage or for posting to a database. &lt;/li&gt;
&lt;li&gt;Controlled inputs can be validated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are some really cool implementations of controlled inputs; I like to think about filtering search results as you type. Just the same way we controlled this simple input form with &lt;code&gt;onChange&lt;/code&gt; and a callback, we can use these search values derived from state in a more scalable, expansive application to search and filter through all sorts of data.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
      <category>react</category>
    </item>
    <item>
      <title>how to prepare for flatiron school // resources i used</title>
      <dc:creator>Eleni Papanicolas</dc:creator>
      <pubDate>Tue, 30 Nov 2021 14:32:04 +0000</pubDate>
      <link>https://dev.to/e_papanicolas/how-to-prepare-for-flatiron-school-resources-i-used-3ela</link>
      <guid>https://dev.to/e_papanicolas/how-to-prepare-for-flatiron-school-resources-i-used-3ela</guid>
      <description>&lt;p&gt;Looking over the Flatiron School &lt;a href="https://flatironschool.com/campus-and-online-coding-bootcamp/"&gt;website&lt;/a&gt; and reading about the Software Engineering Live program, I felt a sense of excitement alongside a sense of dread. Questions swirled around my head for days as I pondered my decision to do this. Is it right for me? Is it wrong for me? Am I smart enough? Will I be able to absorb all of this information? But the one question that was above all other questions: &lt;em&gt;Am I prepared enough?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wouldn't be able to answer this question though, until I had begun the program. As I pored over the pre-work, I felt a sense of relief. I seemed to have a grasp on what was being asked of me. And as I power through Phase-1, that feeling, albeit unsteady, persists. &lt;/p&gt;

&lt;p&gt;That being said, I wanted to share some of the resources I used in my journey &lt;strong&gt;towards&lt;/strong&gt; Flatiron School.&lt;/p&gt;

&lt;p&gt;Years ago, a friend had made a career change into tech by teaching herself and using &lt;a href="https://www.codecademy.com/"&gt;Codecademy&lt;/a&gt;. So this year, when I decided myself to make a similar leap, this seemed like a natural starting point. After taking a few free lessons on the Codecademy website, I decided to upgrade to the Pro version. The options were simple: $40/month on a monthly subscription, or $20/month for a full year subscription. I immediately sprung for the yearly subscription and it was 100% worth the purchase. Codecademy has seemingly endless courses, ranging from Intro level, Skill Paths and even Career Paths. &lt;/p&gt;

&lt;p&gt;The courses I dabbled in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Build A Website with HTML, CSS, and Github Pages&lt;/li&gt;
&lt;li&gt;Learn Javascript&lt;/li&gt;
&lt;li&gt;Front-End Engineer Career Path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And these were only a few of the possibilities and options available. Subjects such as Data Science, Machine Learning, Game Development and Cyber Security are offered as Career and Skill Paths. Python, JavaScript, SQL, Ruby, PHP and Swift are among the many languages they also offer courses in. Learning on Codecademy certainly set me up for success in my continuing education and I plan to keep my subscription for further learning in the future.&lt;/p&gt;

&lt;p&gt;As I got more and more into the development community, I began listening to a few coding podcasts to hear from others in the coding community and try and understand how to relate the information to my own path forward. The &lt;a href="https://www.codenewbie.org/podcast"&gt;Code Newbie Podcast&lt;/a&gt; quickly became a favorite of mine. I loved listening to the host Saron interview her guests about their own journeys into tech, discuss the subject of the day, and then relate it to code newbies like myself. Listening to this podcast gave me different bits of information on various aspects of breaking into the tech world, and also led me to my next learning resource. &lt;/p&gt;

&lt;p&gt;One guest on the Code Newbie podcast, was someone I had previously read about and heard talk on another podcast, so when Wes Bos was featured on my favorite podcast I knew it was time to check his stuff out. They discussed a popular free mini-course that he had released called JavaScript 30, a 30 day code challenge making projects using vanilla JavaScript. That seemed a little daunting to me, but then I discovered he had released a Beginner Javascript course on &lt;a href="https://wesbos.com/"&gt;wesbos.com&lt;/a&gt;. It seemed like a no brainer for me, as I was trying to find ways to level up. And I am so so glad I went for it. His course includes 88 video files or the option to stream the course, as well as a full Github repo containig starter files and solutions. The videos are separated by distinct subject for ease of use when re-watching or trying to find certain information. What I liked was the ability to pause the videos or code along with him. He is a fantastic teacher, and projects sprinkled in throughout the course really help show how much you are learning. I took the premium Beginner JavaScript course, but he also offers free courses on CSS Grid and Flexbox, CLI, Markdown and Redux. Some of his premium courses include Learn Node, React for Beginners, Advanced React and GraphQL, and Master Gatsby. This course left me hungry for even more learning. Which led me to... you guessed it! &lt;strong&gt;FLATIRON!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By this time in my personal journey, I was really feeling like I had a precarious grasp on the fundamentals, but no idea how to tie it all together. Upon a recommendation from a friend who had been through Flatiron's Software Engineering Live, I decided to pursue the Full Stack route, with hopes that I could utilize everything I'd learned and once again, &lt;em&gt;level up&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Some other great resources that I have found helpful along the way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.freecodecamp.org/"&gt;FreeCodeCamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.mozilla.org/en-US/"&gt;MDN Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.w3schools.com/"&gt;W3 Schools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can't wait to share more with you about my Flatiron coding journey. &lt;em&gt;Stay tuned.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
