<?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: Adam Berg</title>
    <description>The latest articles on DEV Community by Adam Berg (@devtails).</description>
    <link>https://dev.to/devtails</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%2F760650%2Fd6c5b3db-57fa-4b60-bbc4-852ca700438f.jpg</url>
      <title>DEV Community: Adam Berg</title>
      <link>https://dev.to/devtails</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/devtails"/>
    <language>en</language>
    <item>
      <title>Using Github Actions to Deploy a React App and Express API Using Rsync in  15 seconds</title>
      <dc:creator>Adam Berg</dc:creator>
      <pubDate>Sat, 04 Dec 2021 21:41:28 +0000</pubDate>
      <link>https://dev.to/devtails/using-github-actions-to-deploy-a-react-app-and-express-api-using-rsync-in-15-seconds-m5c</link>
      <guid>https://dev.to/devtails/using-github-actions-to-deploy-a-react-app-and-express-api-using-rsync-in-15-seconds-m5c</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;I've been looking to revamp the deployment process for several projects that I'm working on and start building towards my preferred method of deployment.&lt;/p&gt;

&lt;p&gt;My biggest requirements are &lt;strong&gt;simplicity&lt;/strong&gt; and &lt;strong&gt;speed&lt;/strong&gt;.  I have used &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, &lt;a href="https://kubernetes.io/" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt;, &lt;a href="https://docs.docker.com/engine/swarm/" rel="noopener noreferrer"&gt;Docker Swarm&lt;/a&gt;, and various other methods of deployment in the past. I recognize these tools have their advantages, but have found that for small to medium sized projects they are more effort than they are worth to maintain.&lt;/p&gt;

&lt;p&gt;At the end of the day, all I need to do is build the code and copy the built files to the server. Before starting the project I told myself to get it under a minute, but I'm happy to report that Github Actions starts up much faster than Travis CI and brought this down to &lt;strong&gt;15 seconds to deploy a React frontend and express.js backend&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I've provided full instructions for how to recreate this entire project, but if you're just interested in the workflow part skip ahead to the My Workflow section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a Simple App to Demonstrate
&lt;/h2&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%2Fa7u0nszax2ql45ce1lfl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa7u0nszax2ql45ce1lfl.png" alt="Simple App Screenshot"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before I can demonstrate the workflow, we need to have something to deploy. Below are instructions for how the simple app is structured. Most of you are probably used to the templates provided by &lt;a href="https://reactjs.org/docs/create-a-new-react-app.html" rel="noopener noreferrer"&gt;Create React App&lt;/a&gt;, but here I provide some opinionated alternatives for how to structure the app. The same principles should be possible to transfer over to any existing setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating a Basic React App
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir github-actions-tutorial
cd github-actions-tutorial
yarn init
yarn add react react-dom
yarn add --dev @types/react @types/react-dom
mkdir -p client/src
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create index.tsx
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// client/src/index.tsx
import React from "react";
import ReactDom from "react-dom";
import { App } from "./App";

ReactDom.render(&amp;lt;App /&amp;gt;, document.getElementById("root"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create App.tsx
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// client/src/App.tsx
import React, { useEffect, useState } from "react";

export const App: React.FC = () =&amp;gt; {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;div&amp;gt;Hello Github Actions!&amp;lt;/div&amp;gt;
    &amp;lt;/&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Building React App with esbuild
&lt;/h3&gt;

&lt;p&gt;Now that we have a simple React app we are going to output a minified production build using &lt;a href="https://esbuild.github.io/" rel="noopener noreferrer"&gt;esbuild&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Install esbuild
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add --dev esbuild
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Add client:build script to package.json
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// package.json
{
  "name": "github-actions-tutorial",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "git@github.com:adamjberg/github-actions-tutorial.git",
  "author": "Adam Berg &amp;lt;adam@xyzdigital.com&amp;gt;",
  "license": "MIT",
  "scripts": {
    "client:build": "esbuild client/src/index.tsx --bundle --minify --outfile=built/app.js",
  },
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2"
  },
  "devDependencies": {
    "@types/react": "^17.0.37",
    "@types/react-dom": "^17.0.11",
    "esbuild": "^0.14.1"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;You can test this is working correctly by running &lt;code&gt;yarn client:build&lt;/code&gt; and you should see a &lt;code&gt;built/app.js&lt;/code&gt; file in the folder tree with the minified output.&lt;/p&gt;

&lt;p&gt;You are probably used to have a &lt;code&gt;yarn start&lt;/code&gt; script as well, but for the purposes of this tutorial we're going to skip it and test this all out directly in "production".&lt;/p&gt;
&lt;h4&gt;
  
  
  Create &lt;code&gt;public/index.html&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;html&amp;gt;

&amp;lt;head&amp;gt;
  &amp;lt;script src="/js/app.js" defer async&amp;gt;&amp;lt;/script&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
  &amp;lt;div id="root"&amp;gt;&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;

&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This will be the file that is served by our nginx static file server when clients hit the &lt;code&gt;http://github-actions-tutorial.devtails.xyz&lt;/code&gt; URL.&lt;/p&gt;
&lt;h3&gt;
  
  
  Prepping a Server
&lt;/h3&gt;

&lt;p&gt;I'm going to assume the reader has some knowledge about how to register a domain and create a server on some hosting platform. I already have a domain &lt;code&gt;devtails.xyz&lt;/code&gt; with &lt;a href="https://namecheap.pxf.io/GjgG5V" rel="noopener noreferrer"&gt;Namecheap&lt;/a&gt; and I have created a droplet with &lt;a href="https://m.do.co/c/4d01489e4069" rel="noopener noreferrer"&gt;Digital Ocean&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the example below, I have mapped &lt;code&gt;github-actions-tutorial.devtails.xyz&lt;/code&gt; to my Digital Ocean IP: &lt;code&gt;143.198.32.125&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;As long as you have the ability to ssh into your server, the following instructions should suffice regardless of your hosting platform.&lt;/p&gt;
&lt;h4&gt;
  
  
  SSH into server
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh root@143.198.32.125
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create github-actions-tutorial User
&lt;/h4&gt;

&lt;p&gt;To prevent our Github Action from getting root access to our server, we will create a sub-user called &lt;code&gt;github-actions-tutorial&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;useradd -s /bin/bash -d /home/github-actions-tutorial -m github-actions-tutorial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Install nginx
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apt-get install nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create Virtual Host File
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# /etc/nginx/sites-available
server {
  listen 80;
  server_name github-actions-tutorial.devtails.xyz;

  location / {
    root /home/github-actions-tutorial/static;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This tells nginx to route requests to the &lt;code&gt;github-actions-tutorial.devtails.xyz&lt;/code&gt; subdomain to the &lt;code&gt;static&lt;/code&gt; folder under our &lt;code&gt;github-actions-tutorial&lt;/code&gt; user.&lt;/p&gt;
&lt;h4&gt;
  
  
  Create &lt;code&gt;static&lt;/code&gt; folder on &lt;code&gt;github-actions-tutorial&lt;/code&gt; user
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;su github-actions-tutorial
mkdir static
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This allows us to avoid having our Github Action ssh into the server just to create this folder. This folder will house the &lt;code&gt;js/app.js&lt;/code&gt; and &lt;code&gt;index.html&lt;/code&gt;. The virtual host file set up previously tells nginx to serve files from the &lt;code&gt;static&lt;/code&gt; folder.&lt;/p&gt;
&lt;h3&gt;
  
  
  Creating a Basic Express REST API
&lt;/h3&gt;
&lt;h4&gt;
  
  
  Install express
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add express
yarn add @types/express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Create &lt;code&gt;server/src/server.tsx&lt;/code&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// server/src/server.tsx
import express from "express";

const app = express();

app.get("/api/message", (_, res) =&amp;gt; {
  return res.json({
    data: "Hello from the server!",
  });
});

app.listen(8080);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This creates a basic REST API with a single &lt;code&gt;/api/message&lt;/code&gt; route that we will use to demonstrate that it is running correctly.&lt;/p&gt;
&lt;h4&gt;
  
  
  Add server:build script to package.json
&lt;/h4&gt;

&lt;p&gt;We will re-use the esbuild package to build a bundle for our server code as well. For more details on this approach please see &lt;a href="https://devtails.xyz/bundling-your-node-js-express-app-with-esbuild" rel="noopener noreferrer"&gt;this post&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"server:build": "esbuild server/src/server.ts --bundle --minify --outfile=built/server.js --platform=node"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Add this right below the &lt;code&gt;client:build&lt;/code&gt; script. You can then run it to confirm working as expected with &lt;code&gt;yarn server:build&lt;/code&gt;.  It should output a bundled file to &lt;code&gt;built/server.js&lt;/code&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Add build script that runs both client and server builds
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"build": "yarn client:build &amp;amp;&amp;amp; yarn server:build"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Prepare the Server to Run the API
&lt;/h3&gt;

&lt;p&gt;There are a few one time configurations that need to be applied in order to prepare our server for deployment.&lt;/p&gt;
&lt;h4&gt;
  
  
  Switch to github-actions-tutorial user
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;su github-actions-tutorial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Install NVM
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Install Node
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;nvm install 16
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Install &lt;a href="https://pm2.keymetrics.io/" rel="noopener noreferrer"&gt;pm2&lt;/a&gt;
&lt;/h4&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i -g pm2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h4&gt;
  
  
  Update Virtual Host File to Route to API
&lt;/h4&gt;

&lt;p&gt;Again ssh into the &lt;code&gt;root&lt;/code&gt; user and update &lt;code&gt;/etc/nginx/sites-available/github-actions-tutorial.devtails.xyz&lt;/code&gt; file&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# /etc/nginx/sites-available/github-actions-tutorial.devtails.xyz
upstream github-actions-tutorial-api {
  server localhost:8080;
}

server {
  listen 80;
  server_name github-actions-tutorial.devtails.xyz;

  location /api {
    proxy_pass http://localhost:8080;
  }

  location / {
    root /home/github-actions-tutorial/static;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This tells nginx to route any URLs that start with &lt;code&gt;/api&lt;/code&gt; to the express app that we added.&lt;/p&gt;
&lt;h4&gt;
  
  
  Bootstrapping the pm2 process
&lt;/h4&gt;

&lt;p&gt;Before the final step &lt;code&gt;- run: ssh github-actions-tutorial "pm2 reload all"&lt;/code&gt; can run, you must first manually start your server with pm2. &lt;/p&gt;

&lt;p&gt;After running the Github Action for the first time, it should have copied the built &lt;code&gt;server.js&lt;/code&gt; file to &lt;code&gt;~/api/server.js&lt;/code&gt;.  You can then start this process with &lt;code&gt;pm2 start api/server.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now that it is running, the &lt;code&gt;pm2 reload all&lt;/code&gt; command will reload this server process so it can pick up the changes in your server code.&lt;/p&gt;
&lt;h2&gt;
  
  
  My Workflow
&lt;/h2&gt;

&lt;p&gt;Phew, with all that set up out of the way, we can now look at what our &lt;code&gt;Deploy&lt;/code&gt; workflow does.&lt;/p&gt;

&lt;p&gt;Below I'll break it down section by section&lt;/p&gt;
&lt;h3&gt;
  
  
  Define workflow name and triggers
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy

on:
  push:
    branches: [ main ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This creates a workflow called "Deploy" that will be run whenever a push is made to the &lt;code&gt;main&lt;/code&gt; branch.&lt;/p&gt;
&lt;h3&gt;
  
  
  Define build-and-deploy job
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This creates a job called &lt;code&gt;build-and-deploy&lt;/code&gt; that will run the latest ubuntu distribution.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env:
  SSH_KEY: ${{secrets.SSH_KEY}}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This adds a &lt;a href="https://docs.github.com/en/actions/security-guides/encrypted-secrets" rel="noopener noreferrer"&gt;Github Secret&lt;/a&gt; to the environment. We will use this in a later step to allow us to rsync to our specified server.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steps:
  - uses: actions/checkout@v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This checks out the code for the current commit.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- name: Use Node.js 16
  uses: actions/setup-node@v2
  with:
    node-version: 16
    cache: 'yarn'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This installs node 16 and specifies that the Workflow should cache files for yarn.  This cache ensures that if no packages are added or removed, &lt;code&gt;yarn install&lt;/code&gt; won't have to do anything. This saves a significant amount of time.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- run: yarn install
- run: yarn build
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These lines run the install and build which ultimately outputs all the files that we would like to deploy.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- run: mkdir ~/.ssh
- run: 'echo "$SSH_KEY" &amp;gt;&amp;gt; ~/.ssh/github-action'
- run: chmod 400 ~/.ssh/github-action
- run: echo -e "Host static\n\tUser github-actions-tutorial\n\tHostname 143.198.32.125\n\tIdentityFile ~/.ssh/github-action\n\tStrictHostKeyChecking No" &amp;gt;&amp;gt; ~/.ssh/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is the most complicated section.  What's happening here is that we are adding the &lt;code&gt;SSH_KEY&lt;/code&gt; secret to the &lt;code&gt;~/.ssh/github-action&lt;/code&gt; file.  The final line creates a &lt;code&gt;~/.ssh/config&lt;/code&gt; file that looks like the following:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host static
  User github-actions-tutorial
  IdentityFile ~/.ssh/github-action
  StrictHostKeyChecking No
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;With that set up, the rsync commands look quite simple:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- run: rsync -e ssh public static:~/static
- run: rsync -e ssh built/app.js static:~/static/js/app.js
- run: rsync -e ssh built/server.js static:~/api/server.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;-e ssh&lt;/code&gt; specifies to use rsync over ssh. We copy over all files from the &lt;code&gt;public&lt;/code&gt; folder. Then we copy over the &lt;code&gt;built/app.js&lt;/code&gt; to &lt;code&gt;~/static/js/app.js&lt;/code&gt;. Finally we copy &lt;code&gt;built/server.js&lt;/code&gt; to &lt;code&gt;~/api/server.js&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- run: ssh github-actions-tutorial "pm2 reload all"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This final line uses pm2 (which we installed earlier) to reload the server process.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;While I could get an even faster deployment just by running this on my local machine, having this run as a Github Action provides a big benefit for my open source projects.  In order to deploy a contributor's changes, I can simply merge their pull request in to the main branch without having to give direct server access to anyone else.&lt;/p&gt;

&lt;p&gt;There's plenty more that could be tidied up or improved, but in the spirit of a hackathon, I'm calling this "done" for now.  I now have a baseline of how long I should expect an app to be built and deployed using Github Actions.&lt;/p&gt;
&lt;h2&gt;
  
  
  Submission Category:
&lt;/h2&gt;

&lt;p&gt;DIY Deployments&lt;/p&gt;
&lt;h2&gt;
  
  
  Yaml File or Link to Code
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy

on:
  push:
    branches: [ main ]

jobs:
  build-and-deploy:

    runs-on: ubuntu-latest

    env:
      SSH_KEY: ${{secrets.SSH_KEY}}

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js 16
      uses: actions/setup-node@v2
      with:
        node-version: 16
        cache: 'yarn'
    - run: yarn install
    - run: yarn build
    - run: mkdir ~/.ssh
    - run: 'echo "$SSH_KEY" &amp;gt;&amp;gt; ~/.ssh/github-action'
    - run: chmod 400 ~/.ssh/github-action
    - run: echo -e "Host github-actions-tutorial\n\tUser github-actions-tutorial\n\tHostname 143.198.32.125\n\tIdentityFile ~/.ssh/github-action\n\tStrictHostKeyChecking No" &amp;gt;&amp;gt; ~/.ssh/config
    - run: rsync -e ssh public github-actions-tutorial:~/static
    - run: rsync -e ssh built/app.js github-actions-tutorial:~/static/js/app.js
    - run: rsync -e ssh built/server.js github-actions-tutorial:~/api/server.js
    - run: ssh github-actions-tutorial "pm2 reload all"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&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%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/adamjberg" rel="noopener noreferrer"&gt;
        adamjberg
      &lt;/a&gt; / &lt;a href="https://github.com/adamjberg/github-actions-tutorial" rel="noopener noreferrer"&gt;
        github-actions-tutorial
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/adamjberg/github-actions-tutorial" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



&lt;h2&gt;
  
  
  Additional Resources / Info
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/adamjberg/engram" rel="noopener noreferrer"&gt;engram&lt;/a&gt; is an Open Source project where I first prototyped this style of deploy.  It currently takes 3-4 minutes to deploy, which is why I'll be switching over to a workflow closer to the one provided here. &lt;/p&gt;

</description>
      <category>javascript</category>
      <category>actionshackathon21</category>
      <category>webdev</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building My First Command Line Interface (CLI) with Rust</title>
      <dc:creator>Adam Berg</dc:creator>
      <pubDate>Fri, 26 Nov 2021 00:07:11 +0000</pubDate>
      <link>https://dev.to/devtails/building-my-first-command-line-interface-cli-with-rust-84e</link>
      <guid>https://dev.to/devtails/building-my-first-command-line-interface-cli-with-rust-84e</guid>
      <description>&lt;p&gt;After telling myself over and over that today is the day I start learning rust. I finally successfully built a (very small) &lt;a href="https://github.com/adamjberg/engram-cli"&gt;cli&lt;/a&gt; for &lt;a href="https://engramhq.xyz"&gt;engram&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This post will cover some of the things I learned along the way. I mostly from a TypeScript/Node background and will make comparisons between the two where applicable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inventing Some Requirements
&lt;/h2&gt;

&lt;p&gt;I have found that having a tangible end goal increases my odds of project completion by nearly 100%. In this case, the goal is to create a command line program that simply POSTs requests to my personal notes application engram.&lt;/p&gt;

&lt;p&gt;This is not just a learning or for fun project as I recently realized that the command line is a great place for the input of quick notes. When I’m working, I sometimes find myself investigating something in the terminal and realize that I have a thought that I’d like to follow up on later, or a I’d like to store a command I just ran so I can remember what I did later on.&lt;/p&gt;

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

&lt;p&gt;Install rust with the command below found from their &lt;a href="https://www.rust-lang.org/learn/get-started"&gt;Getting started page&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;cargo new project-name creates a new folder called project-name with the bare minimum configuration for a rust project&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cargo.toml&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This is essentially the rust equivalent of a package.json file. Lists some metadata about the project and eventually allows you to specify any dependencies to be managed by the &lt;a href="https://doc.rust-lang.org/cargo/"&gt;cargo package manager&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[package]
name = "rust-new-project"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at [https://doc.rust-lang.org/cargo/reference/manifest.html](https://doc.rust-lang.org/cargo/reference/manifest.html)

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

&lt;/div&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;main.rs&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Who doesn’t love a good Hello, world! application?&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn main() {
  println!("Hello, world!");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  The Final Output
&lt;/h2&gt;

&lt;p&gt;I find it helpful to see the whole picture at first. I will then break down each line of code and introduce the rust concepts that are used.&lt;/p&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Cargo.toml&lt;/strong&gt;
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Cargo.toml
[package]
name = "eg"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at [https://doc.rust-lang.org/cargo/reference/manifest.html](https://doc.rust-lang.org/cargo/reference/manifest.html)

[dependencies]
reqwest = { version = "0.11", features = ["json", "cookies"] }
tokio = { version = "1", features = ["full"] }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;main.rs&lt;/strong&gt;
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# main.rs
use std::io;

use std::collections::HashMap;

#[tokio::main]
async fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
  let mut note = String::new();
  io::stdin().read_line(&amp;amp;mut note)?;

  let mut note_post_map = HashMap::new();
  note_post_map.insert("body", note);

  let client = reqwest::Client::new();
  let resp = client.post("https://engram.xyzdigital.com/api/notes")
    .json(&amp;amp;note_post_map)
    .send()
    .await?;

  if resp.status() != 200 {
    println!("failed to submit note");
    return Ok(());
  }

  Ok(())
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Breaking it Down Line by Line
&lt;/h2&gt;
&lt;h3&gt;
  
  
  &lt;strong&gt;Get input from stdin&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In order to submit a new note, I need to be able to accept input from the command line. This can be achieved with the std::io package.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use std::io;

...

// Initializes a new string
// the mut specifies that it is mutable (e.g. can be modified)
let mut note = String::new();

// Uses the stdin package to read a line from the input
// The &amp;amp; denotes that we are passing a reference to the note variable
// mut is required to specify that the reference can be mutated
// The ? at the end propagates the error to the outer function
// see [https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator](https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#a-shortcut-for-propagating-errors-the--operator) for more details on rust errors
io::stdin().read_line(&amp;amp;mut note)?;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Once you have added the above to the body of the main function, you will see a compiler error:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the &lt;code&gt;?&lt;/code&gt; operator can only be used in a function that returns &lt;code&gt;Result&lt;/code&gt; or &lt;code&gt;Option&lt;/code&gt; (or another type that implements &lt;code&gt;std::ops::FromResidual&lt;/code&gt;)&lt;br&gt;
 cannot use the &lt;code&gt;?&lt;/code&gt; operator in a function that returns &lt;code&gt;()&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One option would be to simply remove the “?”. This instead throws the warning: unused std::result::Result that must be used . This is because the read_line function may fail and ignoring this error may have negative consequences for your application.&lt;/p&gt;

&lt;p&gt;In order to fix the error without a warning we must update the return type of the main function to return the special rust &lt;a href="https://doc.rust-lang.org/std/result/"&gt;Result type&lt;/a&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
  ...
  Ok(());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The “()” type is called “&lt;a href="https://doc.rust-lang.org/std/primitive.unit.html"&gt;unit&lt;/a&gt;”. And can be thought of like void in other languages.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://doc.rust-lang.org/std/boxed/index.html"&gt;Box&lt;/a&gt; is a pointer type for heap allocation. As I currently understand it, the Error that may be thrown has a dynamic size (e.g. the message passed along with the error could be variable length). Therefore the Box specifies that the Error will be some dynamically allocated memory. Without this you would see the error below:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;the size for values of type &lt;code&gt;(dyn std::error::Error + ‘static)&lt;/code&gt; cannot be known at compilation time&lt;br&gt;
 doesn’t have a size known at compile-time&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, we add an Ok(()) at the end of the main function. This fulfils the Result type with the () unit type.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Write a POST Request in rust&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Install reqwest library&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A quick search pointed me to the &lt;a href="https://docs.rs/reqwest/0.11.6/reqwest/"&gt;reqwest library&lt;/a&gt; for handling sending HTTP requests. Installing the library is accomplished by adding the following to the Cargo.toml file.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Cargo.toml
...
[dependencies]
reqwest = { version = "0.11", features = ["json", "cookies"] }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;2. Create HashMap with body property&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My server expects a json object that looks like { body: "note contents" } and so we use the std::collections::HashMap library to make what is essentially a JavaScript Object.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;use std::collections::HashMap;

...

let mut note_post_map = HashMap::new();
note_post_map.insert("body", note);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;3. POST JSON data with reqwest&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;let client = reqwest::Client::new();
let res = client.post("http://engram.xyzdigital.com/api/notes")
    .json(&amp;amp;note_post_map)
    .send()
    .await**?**;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The await keyword is new here. This works similar to async/await in JavaScript. The function will not continue until the asynchronous result from the HTTP request has returned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Add async keyword to outer main function&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In order to support the use of await above, we need to add the async keyword to the main function&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Unfortunately, this gives the following error&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;main&lt;/code&gt; function is not allowed to be &lt;code&gt;async&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where the &lt;a href="https://github.com/tokio-rs/tokio"&gt;tokio library&lt;/a&gt; comes in to play. We add it to the Cargo.toml under [dependencies] .&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
[dependencies]
tokio = { version = "1", features = ["full"] }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can add [&lt;a href="https://docs.rs/tokio/0.2.2/tokio/attr.main.html"&gt;tokio::main&lt;/a&gt;] to our main function. From this &lt;a href="https://rust-lang.github.io/async-book/08_ecosystem/00_chapter.html"&gt;page&lt;/a&gt;, it appears an async runtime is required to actually execute async code. Tokio is one of the popular libraries that provides this async runtime.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#[tokio::main]
async fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
  ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;5. Checking the Result&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if resp.status() != 200 {
  println!("failed to submit note");
  return Ok(());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Finally some code that should look pretty understandable. The response object has a status method to extract the status code of the response. For now, I simple log out that it failed an exit the program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;I have started and stopped working with rust multiple times. Each time I stopped I noticed it was because I didn’t have a strong understanding of the syntax, which frustrated me. Writing this article has forced me to think a bit deeper about how it all works (I learned most of the terminology along the way). Hopefully something here clicks for you as well.&lt;/p&gt;

&lt;p&gt;I plan to continue writing and learning more rust and will add new posts with relevant learnings along the way. Mostly so when I look back in 3 years I can laugh at how much I didn’t really understand 😂.&lt;/p&gt;

</description>
      <category>rust</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
