<?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: Alen Duda</title>
    <description>The latest articles on DEV Community by Alen Duda (@aduda091).</description>
    <link>https://dev.to/aduda091</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%2F516097%2F6d810329-0f7d-4b4d-a826-048ef2259901.jpeg</url>
      <title>DEV Community: Alen Duda</title>
      <link>https://dev.to/aduda091</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aduda091"/>
    <language>en</language>
    <item>
      <title>Bypassing CORS via custom proxy backend</title>
      <dc:creator>Alen Duda</dc:creator>
      <pubDate>Mon, 13 Dec 2021 07:53:07 +0000</pubDate>
      <link>https://dev.to/bornfightcompany/bypassing-cors-via-custom-proxy-backend-2po</link>
      <guid>https://dev.to/bornfightcompany/bypassing-cors-via-custom-proxy-backend-2po</guid>
      <description>&lt;p&gt;As a frontend developer, sooner or later you will encounter a CORS error, something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 to XMLHttpRequest at 'https://...' from origin 'https://...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

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

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu6jrxx4qxwmart8xf7i.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxu6jrxx4qxwmart8xf7i.jpg" alt="CORS error in console"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CORS intro
&lt;/h2&gt;

&lt;p&gt;Plainly, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS" rel="noopener noreferrer"&gt;Cross-Origin Resource Sharing&lt;/a&gt; is a security mechanism which enables web browsers to access data from &lt;em&gt;domain2.com&lt;/em&gt; while browsing &lt;em&gt;domain1.com&lt;/em&gt;. It can also be used to restrict access only to predefined domains. Basically, it requires the backend and frontend to be on the same server or to specifically set allowed origins which can access the backend.&lt;/p&gt;

&lt;p&gt;CORS is disabled by default and, if you have access to the server-side code, there are &lt;a href="https://enable-cors.org/server.html" rel="noopener noreferrer"&gt;ways to enable it&lt;/a&gt;. If you are in a school group project with a backend dev, be sure to remind him/her to enable CORS or you might be stuck with mock data (speaking from experience).&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;I first encountered the red CORS error in the browser console on a university project one saturday night when I tried to connect to our Java Spring backend and couldn't get it to work, even though it worked from Postman. Since Java (and specifically Spring) was (and is) almost as Ancient Greek to me, I wanted to try a way to bypass this error. Since CORS is on the browser level, an idea popped up: why not build a simple(r) JS backend which does the same API request, but has CORS enabled so I could connect to it instead of the original Java backend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Express backend
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://expressjs.com/" rel="noopener noreferrer"&gt;Express.js&lt;/a&gt; is the first node.js web framework I encountered and is well-suited for this task. We will create a minimal node/express backend application which uses &lt;a href="https://axios-http.com/docs/intro" rel="noopener noreferrer"&gt;axios &lt;/a&gt;as the http library and the &lt;a href="https://www.npmjs.com/package/cors" rel="noopener noreferrer"&gt;cors&lt;/a&gt; package to enable CORS on our server (otherwise this whole ordeal would be pointless).&lt;/p&gt;

&lt;h3&gt;
  
  
  Project setup and package installation
&lt;/h3&gt;

&lt;p&gt;After creating a folder for our project, open a terminal and navigate to it. We initialize the most basic package.json file with&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;npm init -y&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once done, we install the required packages:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;npm i express cors axios&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Before starting to code, we need a file which will be run. Common names are &lt;code&gt;server.js&lt;/code&gt; or &lt;code&gt;app.js&lt;/code&gt;. Since this project will have all the code in a single file (not the best practice, but for demonstration purposes), we can simply use &lt;code&gt;index.js&lt;/code&gt;. Create that file and modify the package.json file so the scripts key looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

 &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node index"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  Coding time
&lt;/h3&gt;

&lt;p&gt;Finally, time to code! Open &lt;code&gt;index.js&lt;/code&gt; (or whatever you called it in the previous step) so we can create our server. I will copy all the code required here, along with the comments for (almost) each line.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

&lt;span class="c1"&gt;// packages import&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&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;cors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cors&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;axios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;axios&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// enable CORS&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// set the port on which our app wil run&lt;/span&gt;
&lt;span class="c1"&gt;// important to read from environment variable if deploying&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// basic string route to prevent Glitch error&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// the route we're working with&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="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="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// replace with a custom URL as required&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;backendUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsonplaceholder.typicode.com/users&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// return the data without modification&lt;/span&gt;
    &lt;span class="nx"&gt;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;backendUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&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;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&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;// console text when app is running&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Server listening at http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And that is it! You can use the above code and upload it to &lt;a href="https://glitch.com" rel="noopener noreferrer"&gt;Glitch&lt;/a&gt;, for example, so it can be hosted and accessed if you deploy your frontend app. That's why we require reading the PORT from environment variable (if available) and set a root route to return a simple string, otherwise Glitch would belive the app has an error since nothing is returned.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;"/users"&lt;/code&gt; route contains the main code we need to connect to the backend which doesn't have CORS access enabled and returns the same, unmodified data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional bonus: data modification
&lt;/h3&gt;

&lt;p&gt;While you can return the data as-is, nothing stops you from modifying the original response to be more adapted to your frontend app's needs. If there is a lot of data and modifications required, that could improve the frontend app's performance on lower-end devices and slower connections, since less &lt;em&gt;noise&lt;/em&gt; data will be received and less modifications are required client-side.&lt;/p&gt;

&lt;p&gt;Example response from original backend API:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fblit2ijzl5etd0vdqn43.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fblit2ijzl5etd0vdqn43.jpg" alt="Original API response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code snippet for modifying this is pretty straightforward (assuming the response has the same data structure as above):&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;axios&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;backendUrl&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&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;=&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;lastEpisodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&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="nx"&gt;lastAvailableEpisodes&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;shows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;lastEpisodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;episode&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentItemId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;audioFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;episode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;audio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;
        &lt;span class="p"&gt;}));&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shows&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;Example custom API response after modification:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvjapn34f0kzbp7ztpsav.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvjapn34f0kzbp7ztpsav.jpg" alt="Custom API response"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I believe you agree that the second response is much cleaner and easier to follow.&lt;/p&gt;

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

&lt;p&gt;This was a very basic example of using a custom, bare-bones backend as a proxy to bypass CORS-restricted content you would generally have access to. It also follows a so-called &lt;em&gt;happy path&lt;/em&gt;, meaning there is no error handling, but that would detract from the topic. The whole process from creating the project, modifying the response and deployment to Glitch can take less than 10 minutes, which is much quicker than waiting for your backend-dev colleague to wake up the next morning when the inspiration is gone.&lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>cors</category>
      <category>node</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Deploying and building React projects to GitHub Pages with GH Actions</title>
      <dc:creator>Alen Duda</dc:creator>
      <pubDate>Mon, 02 Aug 2021 08:30:45 +0000</pubDate>
      <link>https://dev.to/bornfightcompany/deploying-and-building-react-projects-to-github-pages-with-gh-actions-8ek</link>
      <guid>https://dev.to/bornfightcompany/deploying-and-building-react-projects-to-github-pages-with-gh-actions-8ek</guid>
      <description>&lt;p&gt;For my private mini-projects, I prefer to use &lt;a href="https://parceljs.org/"&gt;Parcel bundler&lt;/a&gt; over &lt;a href="https://create-react-app.dev/"&gt;Create React App&lt;/a&gt; just because there is less overhead and clutter to clean up. However, by using CRA and the &lt;a href="https://github.com/tschaub/gh-pages"&gt;appropriate package&lt;/a&gt;, deploying can be very simple. This post will tell you how to set up build and deploy when using a custom project structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a React/Parcel project
&lt;/h2&gt;

&lt;p&gt;By following steps from &lt;a href="https://blog.jakoblind.no/react-parcel/"&gt;this post&lt;/a&gt; we can be up and running in a few minutes. As always with React, you are free to organize the project structure as needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build for GitHub Pages
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Enable GH Pages inside repo settings (use &lt;strong&gt;docs&lt;/strong&gt; folder, as detailed in &lt;a href="https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site"&gt;the documentation&lt;/a&gt; )&lt;/li&gt;
&lt;li&gt;Specify a build script - Parcel should create built files inside &lt;code&gt;/docs&lt;/code&gt; folder.
Example build script inside package.json: &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;"build-github": "rm -rf docs/* &amp;amp;&amp;amp; parcel build index.html --public-url ./ -d docs"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These two commands: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;delete the existing docs folder and all its contents (to make sure we don't have previously built files lying around)&lt;/li&gt;
&lt;li&gt;run the Parcel build command, as always using &lt;em&gt;index.html&lt;/em&gt; as entry point, but specifying the built files to go inside &lt;em&gt;/docs&lt;/em&gt; folder, while setting the output files to read from that folder (&lt;code&gt;./&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To test this, we can try running &lt;code&gt;npm run build-github&lt;/code&gt; command and committing the changes, pushing them to master. A green checkmark should appear on your GH repository near the latest commit, indicating all went well. The default URL template for GH Pages is &lt;code&gt;https://&amp;lt;your-github-username&amp;gt;.github.io/&amp;lt;your-github-repository&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the build with every push to master
&lt;/h2&gt;

&lt;p&gt;To avoid having to run that command manually, we should automate this step by using GitHub Actions, another free service from GitHub.&lt;/p&gt;

&lt;p&gt;In your repository's project root, create a folder named &lt;code&gt;.github&lt;/code&gt; and inside it another folder called &lt;code&gt;workflows&lt;/code&gt;. There, you should create a file named &lt;code&gt;build.yml&lt;/code&gt; (this name is optional).&lt;/p&gt;

&lt;p&gt;Example build.yml file:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build gh-pages&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;master&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-gh-pages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;      
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build-github&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Commit files&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;auto-commit-action&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;stefanzweifel/git-auto-commit-action@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;commit_message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build project for github pages&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push changes&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.auto-commit-action.outputs.changes_detected == 'true'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ad-m/github-push-action@master&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;No changes detected&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;steps.auto-commit-action.outputs.changes_detected == 'false'&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "No changes!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After pushing this file to your remote repository, GitHub will read it and follow steps inside it: on every push to the branch master, it will run the steps detailed in &lt;code&gt;build-gh-pages&lt;/code&gt; job (name optional) - do a clean npm install (&lt;code&gt;npm ci&lt;/code&gt;) and run the &lt;code&gt;npm run build-github&lt;/code&gt; command we created earlier. Make sure you specify the correct node version for your project, as well as replacing the build-github npm command with your custom naming, if required. Feel free to modify the &lt;code&gt;commit_message&lt;/code&gt; variable as well!&lt;/p&gt;

&lt;p&gt;To test this, simply make some changes to your project and push them to the remote repository. An orange circle should appear near the latest commit message on GH repo page, where you can track the progress of the build process. Once ready, a green checkbox will replace the circle and your changes should be visible.&lt;/p&gt;

&lt;p&gt;For me, this mini-automation process was a great introduction to GH Actions. I borrowed some code and inspiration from my colleagues &lt;a href="https://dev.to/shockwavee"&gt;Davor&lt;/a&gt; and &lt;a href="https://dev.to/wnbsmart"&gt;Maroje&lt;/a&gt; with only minor customization required. I hope it will help someone and spark an interest into the wonderful world of automation!&lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>github</category>
      <category>parcel</category>
      <category>react</category>
    </item>
    <item>
      <title>Installing fonts on a remote server</title>
      <dc:creator>Alen Duda</dc:creator>
      <pubDate>Mon, 17 May 2021 18:33:12 +0000</pubDate>
      <link>https://dev.to/bornfightcompany/installing-fonts-on-a-remote-server-km9</link>
      <guid>https://dev.to/bornfightcompany/installing-fonts-on-a-remote-server-km9</guid>
      <description>&lt;p&gt;Recently we encountered an interesting situation after migrating our web application to a new server - all the generated &lt;em&gt;PDFs started to look slightly different&lt;/em&gt; than before. This was unexpected as no changes were made to fonts, yet after a short research the conclusion was that the &lt;strong&gt;fonts were different&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We used Adobe Acrobat to check for font names and found three different results for old server, new server and locally generated PDFs. This pointed to the fact that the generic &lt;em&gt;sans-serif&lt;/em&gt; font family was declared and the tool we use for PDF generation (&lt;a href="https://wkhtmltopdf.org/" rel="noopener noreferrer"&gt;wkhtmltopdf&lt;/a&gt;) used the default sans-serif font available on the operating system the application was running on (&lt;code&gt;Helvetica&lt;/code&gt; on local Mac OS and &lt;code&gt;DejaVu Sans&lt;/code&gt; on new CentOS Linux 7 server). Since the documents were required to look the same and wkhtmltopdf has issues with Webfonts, the agreed solution was to install the same old font and set it as the default sans-serif on the new remote server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gathering information and preparing directories
&lt;/h2&gt;

&lt;p&gt;After SSH-ing to the remote server, we checked which operating system was present:&lt;/p&gt;

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

&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/os-release


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

&lt;/div&gt;

&lt;p&gt;We also used the following commands to check&lt;/p&gt;

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

fc-match &lt;span class="nt"&gt;-a&lt;/span&gt;  &lt;span class="c"&gt;# print which fonts are used&lt;/span&gt;
fc-list &lt;span class="c"&gt;# print where font files are located&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;This gave us enough information for further steps.&lt;/p&gt;

&lt;p&gt;On CentOS, custom font files for each user need to be placed inside &lt;code&gt;~/.fonts&lt;/code&gt; directory. Since that directory didn't exist, we created it with&lt;/p&gt;

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

&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="c"&gt;# make sure we are in home directory&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; .fonts


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

&lt;/div&gt;
&lt;h2&gt;
  
  
  Uploading and installing the font
&lt;/h2&gt;

&lt;p&gt;After the .fonts directory is created, we needed to upload the font files in that directory. This can be done in multiple ways, but here we will mention the two simplest (if you don't have sudo access).&lt;/p&gt;
&lt;h3&gt;
  
  
  Option 1: &lt;code&gt;wget&lt;/code&gt; from a third-party server
&lt;/h3&gt;

&lt;p&gt;This is a great option if the font in question is available on a publicly available third-party server like &lt;a href="https://ttfonts.net/" rel="noopener noreferrer"&gt;TTFonts&lt;/a&gt;. Inside the .fonts directory we run:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

wget &lt;span class="nt"&gt;--no-check-certificate&lt;/span&gt; &lt;span class="s2"&gt;"http://ttfonts.net/sfonts/2/27260_NimbusSanL.ttf"&lt;/span&gt;
wget &lt;span class="nt"&gt;--no-check-certificate&lt;/span&gt; &lt;span class="s2"&gt;"https://ttfonts.net/sfonts/2/27258_NimbusSanLBold.ttf"&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Notice we used both regular and bold font files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 2: &lt;code&gt;scp&lt;/code&gt; from local machine
&lt;/h3&gt;

&lt;p&gt;In case you have the needed font files available on your machine, you need to upload them to the remote. Our colleague Maroje recently wrote a &lt;a href="https://dev.to/bornfightcompany/transferring-files-between-local-machine-and-aws-instance-4efg"&gt;great article&lt;/a&gt; about how to do just that and it was very useful.&lt;/p&gt;

&lt;p&gt;For this option, open a terminal on your local machine and navigate to the directory which contains your font files.&lt;br&gt;
Since we use SSH on our server and login without a password, we used the following command to copy all .ttf files in current directory to remote server:&lt;/p&gt;

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

scp &lt;span class="nt"&gt;-i&lt;/span&gt; /Users/username/.ssh/public_key.pub &lt;span class="k"&gt;*&lt;/span&gt;.ttf remote-user@remote-host-ip:~/.fonts


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

&lt;/div&gt;

&lt;p&gt;For more information please see Maroje's post as he explained it in more detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the alias
&lt;/h2&gt;

&lt;p&gt;With the font files present on the server, they are ready to be used... but our situation required us to specify that font as the default sans-serif.&lt;/p&gt;

&lt;p&gt;This was done by navigating to &lt;code&gt;~/.config&lt;/code&gt; directory and creating a new &lt;code&gt;fontconfig&lt;/code&gt; directory to hold our font alias file, which we create using the ubiquitous &lt;em&gt;nano&lt;/em&gt; editor.&lt;/p&gt;

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

&lt;span class="nb"&gt;cd&lt;/span&gt; ~/.config
&lt;span class="nb"&gt;mkdir &lt;/span&gt;fontconfig
&lt;span class="nb"&gt;cd &lt;/span&gt;fontconfig
nano fonts.conf


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

&lt;/div&gt;

&lt;p&gt;The content of fonts.conf file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;

&lt;span class="cp"&gt;&amp;lt;?xml version='1.0'?&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;&amp;lt;!DOCTYPE fontconfig SYSTEM 'fonts.dtd'&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;fontconfig&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;alias&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;family&amp;gt;&lt;/span&gt;sans-serif&lt;span class="nt"&gt;&amp;lt;/family&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;prefer&amp;gt;&amp;lt;family&amp;gt;&lt;/span&gt;NimbusSanL&lt;span class="nt"&gt;&amp;lt;/family&amp;gt;&amp;lt;/prefer&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/alias&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/fontconfig&amp;gt;&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;And that's it! The new font started being used immediately, even though several references advised to use:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;br&gt;
 bash&lt;br&gt;
fc-cache -f -v

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;This was a short tutorial-like post on font installation on a remote Linux machine without GUI. As a mostly front-end dev, this was somewhat out of my comfort zone but very interesting and I was happy with the result. Hopefully this helps somebody and I will put the references for more info.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  References
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jichu4n.com/posts/how-to-set-default-fonts-and-font-aliases-on-linux/" rel="noopener noreferrer"&gt;https://jichu4n.com/posts/how-to-set-default-fonts-and-font-aliases-on-linux/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ohana.windwardstudios.com/m/76878/l/879660-how-do-i-install-fonts-in-linux" rel="noopener noreferrer"&gt;https://ohana.windwardstudios.com/m/76878/l/879660-how-do-i-install-fonts-in-linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://askubuntu.com/questions/1138020/how-to-install-nimbussanl-regu-font-in-ubuntu-18-04" rel="noopener noreferrer"&gt;https://askubuntu.com/questions/1138020/how-to-install-nimbussanl-regu-font-in-ubuntu-18-04&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://www.w3schools.com/css/css_font.asp" rel="noopener noreferrer"&gt;Cover image source&lt;/a&gt;&lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>bash</category>
      <category>font</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Using Google Sheets as a simple database with Papa Parse</title>
      <dc:creator>Alen Duda</dc:creator>
      <pubDate>Mon, 15 Mar 2021 09:23:18 +0000</pubDate>
      <link>https://dev.to/bornfightcompany/using-google-sheets-as-a-simple-database-with-papa-parse-2k7o</link>
      <guid>https://dev.to/bornfightcompany/using-google-sheets-as-a-simple-database-with-papa-parse-2k7o</guid>
      <description>&lt;h2&gt;
  
  
  Inspiration
&lt;/h2&gt;

&lt;p&gt;On a recent meeting, a fellow dev commented on a piece of software he saw as being "just like Windows 95". This brought up some nostalgia inside me and I quickly remembered there are several libraries available for recreating that classic look and feel.&lt;/p&gt;

&lt;p&gt;One of the first results was &lt;a href="https://github.com/React95/React95" rel="noopener noreferrer"&gt;React95&lt;/a&gt; and the first link inside the showcase was &lt;a href="https://github.com/ggdaltoso/95Recipes" rel="noopener noreferrer"&gt;a recipe app&lt;/a&gt; from one of the library's contributors. The readme stated it used Google Sheets as a database, which I found interesting and decided to investigate further.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tabletop
&lt;/h2&gt;

&lt;p&gt;The promising library user in the aforementioned example was &lt;a href="https://github.com/jsoma/tabletop" rel="noopener noreferrer"&gt;Tabletop&lt;/a&gt;. I was excited while reading their &lt;a href="https://www.npmjs.com/package/tabletop" rel="noopener noreferrer"&gt;npm&lt;/a&gt; page, but the Github readme stated how the package is getting deprecated by Google's changes. It's a shame since that package seemed to offer many utility features for listing data from spreadsheets out of the box (including reading data from multiple sheets). Luckily, they provided an alternative (albeit much more general-purpose, but still usable for &lt;strong&gt;simple&lt;/strong&gt; examples).&lt;br&gt;
The alternative in question is...&lt;/p&gt;
&lt;h2&gt;
  
  
  Papa Parse
&lt;/h2&gt;

&lt;p&gt;Upon first look, &lt;a href="https://www.papaparse.com/" rel="noopener noreferrer"&gt;Papa Parse&lt;/a&gt; seems to be a general-purpose CSV parser with a no-nonsense quick-start documentation that is readable at a glance. The parser will return JSON data which can be used for any purpose required, e.g.:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;creating tables&lt;/li&gt;
&lt;li&gt;visualization via charts&lt;/li&gt;
&lt;li&gt;iterating through data to create blocks (by using a templating engine)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Creating a data source
&lt;/h2&gt;

&lt;p&gt;The premise is simple: create a new Google Spreadsheet, enter the headers (column names) and fill the table with data (rows).&lt;/p&gt;

&lt;p&gt;Next, we need to publish the worksheet: go to File -&amp;gt; Publish to the Web.&lt;/p&gt;

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

&lt;p&gt;Make sure to click the &lt;strong&gt;Start publishing&lt;/strong&gt; button. We need to parse CSV files, so we need to pass the link to CSV option to our code. If we only have a single sheet, this should suffice. However, if there are multiple sheets, we need to get links to each one by selecting them from the dropdown (and making sure CSV is selected).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ses0b6d5h81w7t7w8l4.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ses0b6d5h81w7t7w8l4.jpg" alt="Copying each sheet's CSV link"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Example
&lt;/h2&gt;

&lt;p&gt;To demonstrate one way of using Google Sheets as a data source, we will use 2 sheets and a &lt;a href="https://codepen.io/aduda091/pen/NWbLogo" rel="noopener noreferrer"&gt;Codepen repo&lt;/a&gt;. The data is simple: I listed some movies and shows I've watched recently. Data will be shown inside two tables with minimal styling. To simplify HTML generation, Vue.js will be used.&lt;/p&gt;

&lt;p&gt;The Sheet is available &lt;a href="https://docs.google.com/spreadsheets/d/13eQrrEF6ASPHsMk2Mbigrsqtj2vZt7eedTZuEQRKUgo/edit?usp=sharing" rel="noopener noreferrer"&gt;here&lt;/a&gt; and consists of two sheets: Movies and Shows. They have almost the same structure, with the only difference being Shows' Last watched season replacing Movies' Year column.&lt;/p&gt;

&lt;p&gt;I followed the instructions above to get separate URLs for each sheet's CSV file and saved them to variables.&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;moviesUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://docs.google.com/spreadsheets/d/e/2PACX-1vTtKbv_S8Fdo3HLhm64Tc94WZ6FuqtzqePIjuejNFJxKkUvAE8JF8V2KgKoz1n5jQUDfL8A3F-QoDWk/pub?gid=0&amp;amp;single=true&amp;amp;output=csv&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;showsUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://docs.google.com/spreadsheets/d/e/2PACX-1vTtKbv_S8Fdo3HLhm64Tc94WZ6FuqtzqePIjuejNFJxKkUvAE8JF8V2KgKoz1n5jQUDfL8A3F-QoDWk/pub?gid=1364847678&amp;amp;single=true&amp;amp;output=csv&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;Next, a Vue instance is created and its data is initialized to empty arrays for movies and shows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Vue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;el&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &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="na"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
      &lt;span class="na"&gt;shows&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;Upon creation, Vue is told to use Papa Parse to get each CSV file and set it to its state. As a side note in this example, I used an arrow function as a callback for movies and a regular anonymous function for shows. Arrow function retained the lexical scope of &lt;em&gt;this&lt;/em&gt;, while the regular function required &lt;em&gt;this&lt;/em&gt; to be saved (&lt;em&gt;this&lt;/em&gt; should refer to the Vue instance in order to correctly set data)&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;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;function &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="nf"&gt;fetchMovies&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchShows&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;fetchMovies&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;Papa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;moviesUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;download&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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;movies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&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="nf"&gt;fetchShows&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;_this&lt;/span&gt; &lt;span class="o"&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;Papa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;showsUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;download&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;_this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;results&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="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The presentation part is very simple - a single &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; container for Vue instance and two tables with a template which iterates over the fetched data.&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;id=&lt;/span&gt;&lt;span class="s"&gt;"app"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"movies.length"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Title&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Year&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;URL&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"movie in movies"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{movie.Title}}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{movie.Year}}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;:href=&lt;/span&gt;&lt;span class="s"&gt;"movie.Url"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{movie.Url}}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;table&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"shows.length"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;thead&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Title&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;Last watched season&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;th&amp;gt;&lt;/span&gt;URL&lt;span class="nt"&gt;&amp;lt;/th&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/thead&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;tbody&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;tr&lt;/span&gt; &lt;span class="na"&gt;v-for=&lt;/span&gt;&lt;span class="s"&gt;"show in shows"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{show.Title}}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&lt;/span&gt;{{show["Last watched season"]}}&lt;span class="nt"&gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;td&amp;gt;&amp;lt;a&lt;/span&gt; &lt;span class="na"&gt;:href=&lt;/span&gt;&lt;span class="s"&gt;"show.Url"&lt;/span&gt; &lt;span class="na"&gt;target=&lt;/span&gt;&lt;span class="s"&gt;"_blank"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{{show.Url}}&lt;span class="nt"&gt;&amp;lt;/a&amp;gt;&amp;lt;/td&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/tr&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/tbody&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/table&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;Notice how the shows' Last watched season column retains spaces just like we defined inside Google Sheets.&lt;/p&gt;

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

&lt;p&gt;This article is purposefully simplistic and is only used as an intro to what might be possible by using Google Sheets as a data source. Its collaborative nature makes it easy to make changes which make the frontend update (similar to a very basic CMS) while retaining the ability to limit read/write access as usual.&lt;/p&gt;

&lt;p&gt;However, there are limitations - speed, possible rate limiting and the dependency on Google to keep the CSV option available in the future. As such, this will never replace a proper database and backend combination, but can still be useful for quick prototyping and fun mini-projects, especially when the data is already available.&lt;/p&gt;

</description>
      <category>engineeringmonday</category>
      <category>javascript</category>
      <category>papaparse</category>
      <category>spreadsheets</category>
    </item>
  </channel>
</rss>
