<?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: Spencer Lepine</title>
    <description>The latest articles on DEV Community by Spencer Lepine (@spencerlepine).</description>
    <link>https://dev.to/spencerlepine</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%2F1405790%2F88ff7414-7b3f-454c-abd5-0f3d744fc2a8.png</url>
      <title>DEV Community: Spencer Lepine</title>
      <link>https://dev.to/spencerlepine</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/spencerlepine"/>
    <language>en</language>
    <item>
      <title>Demystifying Bash and Zsh on Mac</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 14 Oct 2024 10:08:43 +0000</pubDate>
      <link>https://dev.to/spencerlepine/demystifying-bash-and-zsh-on-mac-4dgc</link>
      <guid>https://dev.to/spencerlepine/demystifying-bash-and-zsh-on-mac-4dgc</guid>
      <description>&lt;p&gt;If you’re a mac-based developer like me, and you never quite understood the difference between Bash and Zsh, this article is for you. In my experience, configuring the shell and $PATH has always been a confusing and fragile task. &lt;/p&gt;

&lt;p&gt;Let’s demystify the shell and define best practices for configuring your local machine.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; - Zsh and Bash or both unix shells, Zsh is a beefed up version. macOS defaults to Zsh, linux defaults to Bash. For a local machine, configure Zsh. For scripting and deployments, use Bash (cross-platform).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  History
&lt;/h2&gt;

&lt;p&gt;Bash is the original unix shell that has been around since 1989. It’s deeply embedded into both Linux and macOS systems. Apple began moving towards Zsh (pronounced “Zee Shell”) due to licensing changes. Zsh is an extended and improved version of Bash. Eventually, in the release of macOS Catalina in 2019, Zsh became the default shell for macOS.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux default shell: &lt;strong&gt;Bash&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;macOS default shell: &lt;strong&gt;Zsh&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzn78y5xiyy5fylf8yhv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzn78y5xiyy5fylf8yhv.png" alt="Bash vs Zsh Doge meme" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Terminal: Which to Use?
&lt;/h2&gt;

&lt;p&gt;It’s recommended to stick with Zsh as your default shell for local development. It includes more modern features and better community support.&lt;/p&gt;

&lt;p&gt;You’ll want to configure both your terminal shell and code editor default shell. To find out what your currently using, open the terminal and run the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SHELL&lt;/span&gt;
/bin/zsh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Scripting: Which to Use?
&lt;/h2&gt;

&lt;p&gt;It’s recommended to use Bash for scripting and deployment. This is best for compatibility and cross-platform usage, as most Linux systems still use Bash.&lt;/p&gt;

&lt;p&gt;No configuration is needed, you’ll just add a simple shebang to the top of your script. Here’s an example &lt;code&gt;hello.sh&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# This is a comment. It won't be executed.&lt;/span&gt;

&lt;span class="c"&gt;# Define a function&lt;/span&gt;
greet&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Hello, World!"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Call the function&lt;/span&gt;
greet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# make the script executable&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# run the script&lt;/span&gt;
./hello.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One-time Laptop Setup
&lt;/h2&gt;

&lt;p&gt;Ensure your local machine has the proper Zsh setup by reviewing the following steps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It’s highly recommend to install Homebrew as well, which helps you avoid manually configuring the $PATH for tools/languages (e.g. JavaScript, Python, Java).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ol&gt;
&lt;li&gt;Open your terminal and set your default shell to Zsh
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;chsh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which zsh&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2.Create the &lt;code&gt;.zshrc&lt;/code&gt; config file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;

&lt;span class="c"&gt;# Enable color support&lt;/span&gt;
autoload &lt;span class="nt"&gt;-Uz&lt;/span&gt; compinit
compinit

&lt;span class="c"&gt;# Set prompt&lt;/span&gt;
&lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%F{cyan}%n@%m %F{green}%~ %F{yellow}%% %f'&lt;/span&gt;

&lt;span class="c"&gt;# Enable command completion&lt;/span&gt;
setopt complete_in_word
setopt auto_cd
setopt correct

&lt;span class="c"&gt;# Set History Options&lt;/span&gt;
&lt;span class="nv"&gt;HISTSIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10000
&lt;span class="nv"&gt;SAVEHIST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10000
setopt append_history
setopt share_history
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install Homebrew (macOS package manager)
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh]&lt;span class="o"&gt;(&lt;/span&gt;https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.zshrc
&lt;span class="c"&gt;# verify homebrew added the $PATH variable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;

&lt;span class="c"&gt;# ...&lt;/span&gt;

&lt;span class="c"&gt;# Homebrew Path&lt;/span&gt;
&lt;span class="c"&gt;# export PATH="/usr/local/bin:$PATH" # For Intel Macs (legacy)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  &lt;span class="c"&gt;# For Apple Silicon Macs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Verify Homebrew and Zsh are configured
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SHELL&lt;/span&gt;
/bin/zsh
&lt;span class="nv"&gt;$ &lt;/span&gt;brew &lt;span class="nt"&gt;--version&lt;/span&gt;
Homebrew 4.3.17
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
/opt/homebrew/bin:...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Set the default shell for your code editor&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For example, if you’re using &lt;em&gt;Visual Studio Code&lt;/em&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open Visual Studio Code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open the search tool (hit &lt;code&gt;Cmd + Shift + P&lt;/code&gt; )&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Search “&lt;strong&gt;Open User Settings (JSON)&lt;/strong&gt;”, hit enter&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensure the Zsh shell is set:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"terminal.integrated.defaultProfile.osx"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"zsh"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Migrating to a New Laptop
&lt;/h2&gt;

&lt;p&gt;If you want to mirror an existing Zsh configuration to a new mac, it’s pretty straight-forward. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;[old laptop] Export/save your existing &lt;code&gt;.zshrc&lt;/code&gt; config
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.zshrc&lt;/span&gt;
&lt;span class="c"&gt;# Misc&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;EDITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;vim
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/bin:/usr/local/bin:&lt;span class="nv"&gt;$PATH&lt;/span&gt;

&lt;span class="c"&gt;# Enable color support&lt;/span&gt;
autoload &lt;span class="nt"&gt;-Uz&lt;/span&gt; compinit
compinit

&lt;span class="c"&gt;# Set prompt&lt;/span&gt;
&lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%F{cyan}%n@%m %F{green}%~ %F{yellow}%% %f'&lt;/span&gt;

&lt;span class="c"&gt;# Enable command completion&lt;/span&gt;
setopt complete_in_word
setopt auto_cd
setopt correct

&lt;span class="c"&gt;# Set History Options&lt;/span&gt;
&lt;span class="nv"&gt;HISTSIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10000
&lt;span class="nv"&gt;SAVEHIST&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10000
setopt append_history
setopt share_history

&lt;span class="c"&gt;# Homebrew Path&lt;/span&gt;
&lt;span class="c"&gt;# export PATH="/usr/local/bin:$PATH" # For Intel Macs (legacy)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/opt/homebrew/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;  &lt;span class="c"&gt;# For Apple Silicon Macs&lt;/span&gt;

&lt;span class="c"&gt;# Node.js&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NVM_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.nvm"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/nvm.sh"&lt;/span&gt;  &lt;span class="c"&gt;# This loads nvm&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/bash_completion"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NVM_DIR&lt;/span&gt;&lt;span class="s2"&gt;/bash_completion"&lt;/span&gt;  &lt;span class="c"&gt;# This loads nvm bash_completion&lt;/span&gt;
nvm use v20
&lt;span class="c"&gt;# Node.js end&lt;/span&gt;

&lt;span class="c"&gt;# pnpm&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PNPM_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/Users/notspencer/Library/pnpm"&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;":&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;":&lt;/span&gt;&lt;span class="nv"&gt;$PNPM_HOME&lt;/span&gt;&lt;span class="s2"&gt;:"&lt;/span&gt;&lt;span class="k"&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;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PNPM_HOME&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="c"&gt;# pnpm end&lt;/span&gt;

&lt;span class="c"&gt;# oh-my-zsh&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ZSH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/Users/notspencer/.oh-my-zsh"&lt;/span&gt;
&lt;span class="nv"&gt;ZSH_THEME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"robbyrussell"&lt;/span&gt;
&lt;span class="nv"&gt;plugins&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;git&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; &lt;span class="nv"&gt;$ZSH&lt;/span&gt;/oh-my-zsh.sh
&lt;span class="c"&gt;# oh-my-zsh end&lt;/span&gt;

&lt;span class="c"&gt;# pyenv&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PYENV_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.pyenv"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PYENV_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; pyenv 1&amp;gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;pyenv init &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;pyenv init -&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PYENV_ROOT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.pyenv"&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nv"&gt;$PYENV_ROOT&lt;/span&gt;/bin &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PYENV_ROOT&lt;/span&gt;&lt;span class="s2"&gt;/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;pyenv init -&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="c"&gt;# pyenv end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;[old laptop] Generate a list of your existing Homebrew packages
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;brew list
&lt;span class="o"&gt;==&amp;gt;&lt;/span&gt; Formulae
wget python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;[new laptop] Configure Zsh on your new machine
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Set your default shell to Zsh&lt;/span&gt;
chsh &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;which zsh&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# copy paste your existing .zshrc config&lt;/span&gt;
nano ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# apply the config, look for any errors or missing installs&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;[new laptop] Install any missing tools/languages as needed.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# install homebrew&lt;/span&gt;
/bin/bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh]&lt;span class="o"&gt;(&lt;/span&gt;https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# install packages&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;wget python
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What is PATH?
&lt;/h2&gt;

&lt;p&gt;You may have noticed your Zsh config using $PATH, but what is it exactly?&lt;/p&gt;

&lt;p&gt;Whenever you install various tools on your local machine like homebrew, executable files are downloaded in &lt;code&gt;/bin&lt;/code&gt; directories (short for binary). PATH is an environment variable for your operating system to find these executable files. It’s a list of directories, separated by colon ( &lt;code&gt;:&lt;/code&gt; ),  including the default &lt;code&gt;/usr/local/bin&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$PATH&lt;/span&gt;
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:&amp;lt;HOMEBREW_PATH&amp;gt;:&amp;lt;CUSTOM_PATH&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While most of the time, you only need to set it up once, it’s possible you’ll need to append another path if a software/tool comes with an executable in a custom directory.&lt;/p&gt;

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

&lt;p&gt;As a developer, it’s not required to be an expert on Zsh and Bash, but it’s useful to have context on where and why we use them. This article has covered the use-cases for each shell, as well as steps to configure your local machine. By following the best practices, we can avoid breaking our existing configuration, and use the blueprint to quickly set up a new machine.&lt;/p&gt;

</description>
      <category>bash</category>
      <category>zsh</category>
      <category>macbook</category>
      <category>shell</category>
    </item>
    <item>
      <title>GET, POST, PUT &amp; DELETE with Next.js App Router</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Sat, 31 Aug 2024 11:16:25 +0000</pubDate>
      <link>https://dev.to/spencerlepine/get-post-put-delete-with-nextjs-app-router-5do0</link>
      <guid>https://dev.to/spencerlepine/get-post-put-delete-with-nextjs-app-router-5do0</guid>
      <description>&lt;p&gt;This article presents boilerplate code for API routes using &lt;strong&gt;Next.js App Router&lt;/strong&gt; (&lt;a href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers" rel="noopener noreferrer"&gt;docs&lt;/a&gt;), for a todo-list CRUD (Create, Read, Update, Delete) application. The App Router supports both client-side and server-side rendering, allowing for complex routing and dynamic site content.&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NodeJS &amp;gt;= v18&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Find the example code on GitHub: &lt;a href="https://github.com/spencerlepine/nextjs-app-router-todo" rel="noopener noreferrer"&gt;https://github.com/spencerlepine/nextjs-app-router-todo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alternatively, you can clone and bootstrap a Next.js project directly with this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;npx&lt;/span&gt; &lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt; &lt;span class="nx"&gt;nextjs&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/spencerlepine/nextjs-app-router-todo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Endpoints
&lt;/h2&gt;

&lt;p&gt;We’ll implement the following endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;
&lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;
&lt;span class="nx"&gt;PUT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;itemId&lt;/span&gt;
&lt;span class="nx"&gt;DELETE&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nx"&gt;todos&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;itemId&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GET
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Next.js v14 App Router&lt;/span&gt;
&lt;span class="c1"&gt;// GET /users/:userId/todos&lt;/span&gt;
&lt;span class="c1"&gt;// app/api/users/[userId]/todos/route.ts&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;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// v14.x.x&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;todoItemDb&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;@/lib/db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TodoItem&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&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;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt; &lt;span class="nl"&gt;query&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// const { limit, page } = query; // optional&lt;/span&gt;

    &lt;span class="c1"&gt;// Query the database&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;todoItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TodoItem&lt;/span&gt;&lt;span class="p"&gt;[]&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;todoItemDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;todoItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;todoItems&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error processing request:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal server error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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;h3&gt;
  
  
  POST
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Next.js v14 App Router&lt;/span&gt;
&lt;span class="c1"&gt;// POST /users/:userId/todos&lt;/span&gt;
&lt;span class="c1"&gt;// app/api/users/[userId]/todos/route.ts&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;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// v14.x.x&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;todoItemDb&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;@/lib/db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TodoItem&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;POST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&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;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&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;title&lt;/span&gt; &lt;span class="p"&gt;}&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// request body&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;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid request body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Handle your business logic here (e.g., update the database)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newTodoItem&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;todoItemDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newTodoItem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error processing request:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal server error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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;h3&gt;
  
  
  PUT
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Next.js v14 App Router&lt;/span&gt;
&lt;span class="c1"&gt;// PUT /users/:userId/todos/:itemId&lt;/span&gt;
&lt;span class="c1"&gt;// app/api/users/[userId]/todos/[itemId]/route.ts&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;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// v14.x.x&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;todoItemDb&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;@/lib/db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TodoItem&lt;/span&gt; &lt;span class="o"&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;completed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PUT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&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;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;itemId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&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;todoItem&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;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// request body&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;todoItem&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;todoItem&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid request body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Handle your business logic here (e.g., update the database)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;todoItemDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;updateOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todoItem&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error processing request:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal server error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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;h3&gt;
  
  
  DELETE
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Next.js v14 App Router&lt;/span&gt;
&lt;span class="c1"&gt;// DELETE /users/:userId/todos/:itemId&lt;/span&gt;
&lt;span class="c1"&gt;// app/api/users/[userId]/todos/[itemId]/route.ts&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;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&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;next/server&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// v14.x.x&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;todoItemDb&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;@/lib/db&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DELETE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&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;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="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="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;itemId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&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;todoItemDb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;deleteOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;itemId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Todo item deleted successfully&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Error processing request:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Internal server error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&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;h2&gt;
  
  
  Data Fetching &amp;amp; Rendering
&lt;/h2&gt;

&lt;p&gt;Now with the endpoints built, we're able to make requests to our backend. App Router will use server-side rendering by default.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-side Rendering
&lt;/h3&gt;

&lt;p&gt;If you have dynamic content or additional API requests from the frontend, add &lt;code&gt;"use client";&lt;/code&gt; to pages (or components) and use &lt;code&gt;fetch&lt;/code&gt; or &lt;code&gt;axios&lt;/code&gt; .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Client-side rendering (CSR) - Next.js v14 App Router&lt;/span&gt;
&lt;span class="c1"&gt;// src/app/page.tsx&lt;/span&gt;

&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;use client&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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;apiUrl&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;NEXT_PUBLIC_API_URL&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;http://localhost:3000/api&amp;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="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/v1/user/mockUser123/todos`&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;allTodoItems&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;allTodoItems&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;item&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h3&gt;
  
  
  Static Server-side Rendering
&lt;/h3&gt;

&lt;p&gt;For static pages, use the default Next.js server-side rendering. This will be generated at build-time.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server-side rendering (SSR) - Next.js v14 App Router&lt;/span&gt;
&lt;span class="c1"&gt;// src/app/page.tsx&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;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todoItems&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;../../lib/db&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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;allTodoItems&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todoItems&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;allTodoItems&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;item&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h3&gt;
  
  
  Dynamic Server-side Rendering
&lt;/h3&gt;

&lt;p&gt;To dynamically render pages (or components) at request time, set and export the &lt;code&gt;dynamic&lt;/code&gt; configuration option.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server-side rendering (SSR) - Next.js v14 App Router&lt;/span&gt;
&lt;span class="c1"&gt;// src/app/page.tsx&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;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;todoItems&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;../../lib/db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Render page request time&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;force-dynamic&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;HomePage&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;allTodoItems&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;todoItems&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;allTodoItems&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;item&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;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;h2&gt;
  
  
  Authentication
&lt;/h2&gt;

&lt;p&gt;To secure page routes and endpoints in your application, consider using NextAuth.js (&lt;a href="https://github.com/nextauthjs/next-auth" rel="noopener noreferrer"&gt;repository&lt;/a&gt;). You can either create your own custom login system and database or integrate with popular auth providers like Auth0 or Google.&lt;/p&gt;

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

&lt;p&gt;In this guide we covered the basic endpoints for CRUD (Create, Read, Update, Delete) API routes, along with data fetching for the frontend. This will be the bread and butter for most applications. To started today, find the example code over on GitHub: &lt;a href="https://github.com/spencerlepine/nextjs-app-router-todo" rel="noopener noreferrer"&gt;https://github.com/spencerlepine/nextjs-app-router-todo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading. If you found the article useful don’t forget to &lt;strong&gt;clap&lt;/strong&gt;. If you have any questions, feel free to reach out to me. Connect with me and follow my journey on 👉 👉 &lt;strong&gt;&lt;a href="https://www.linkedin.com/in/spencerlepine" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>api</category>
      <category>todoapp</category>
      <category>boilerplate</category>
    </item>
    <item>
      <title>Building Llama as a Service (LaaS)</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:22 +0000</pubDate>
      <link>https://dev.to/spencerlepine/building-llama-as-a-service-laas-4eo5</link>
      <guid>https://dev.to/spencerlepine/building-llama-as-a-service-laas-4eo5</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fbuilding-llama-as-a-service%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fbuilding-llama-as-a-service%2Fthumbnail.jpg" alt="Blog Post Thumbnail" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a walkthrough of the development process and system design engineering for the Llama as a Service. LaaS is a website and public API that can serve random Llama images. It will respond with a single image URL, or even a list.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visit the &lt;a href="https://llama-as-a-service.netlify.app/" rel="noopener noreferrer"&gt;LaaS website&lt;/a&gt; for a demo&lt;/li&gt;
&lt;li&gt;View the source code on &lt;a href="https://github.com/orgs/llama-as-a-service/repositories" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;View the walkthrough &lt;a href="https://www.youtube.com/watch?v=uDQUA_JTMJk" rel="noopener noreferrer"&gt;YouTube video&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What I Learned
&lt;/h3&gt;

&lt;p&gt;For this project, there is a frontend built with &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; hosted on &lt;a href="https://www.netlify.com/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, connected to the backend.&lt;/p&gt;

&lt;p&gt;I built each API with Node.js, Express, and Docker. Services connected to a NoSQL &lt;a href="https://www.mongodb.com/" rel="noopener noreferrer"&gt;MongoDB&lt;/a&gt; database.&lt;/p&gt;

&lt;p&gt;Each service is in an independent repository to maintain separation of concerns. It would have been possible to build this in a monorepo, but it was good practice.&lt;/p&gt;

&lt;p&gt;Each repository uses &lt;a href="https://docs.github.com/en/actions" rel="noopener noreferrer"&gt;GitHub Actions&lt;/a&gt; to build, and test the code on every push. Express API was deployed to &lt;a href="https://www.heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt; when the main branch was pushed.&lt;/p&gt;

&lt;p&gt;With each app containerized with &lt;a href="https://www.docker.com/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, this allows it to be run on any other developer's machine also running Docker. Although I had automated deployments to Heroku without this, I decided to upload each service to a container registry.&lt;/p&gt;

&lt;p&gt;Each repository also used a GitHub Actions workflow to automatically tag and version updates and releases. It would then build and publish the most up to date Docker image, and release it to the &lt;a href="https://ghcr.io/" rel="noopener noreferrer"&gt;GitHub Container Registry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For future use, this makes it crazy easy to deploy a Kubernetes cluster to the cloud, with a simple &lt;code&gt;docker pull ghcr.io/OWNER/IMAGE_NAME&lt;/code&gt; command. However, that was beyond the scope of this project because of zero budget.&lt;/p&gt;

&lt;p&gt;To manage the environment variables, I was able to share Secrets to the GitHub Action workflows, which are encrypted, and can be shared across an entire organization (meaning multiple repos could access the variables). This allowed me to deploy my code securely to Heroku, without ever hard-coding the API keys.&lt;/p&gt;

&lt;p&gt;Another tool I used was &lt;a href="https://www.artillery.io/" rel="noopener noreferrer"&gt;Artillery&lt;/a&gt; for load testing on my local machine.&lt;/p&gt;

&lt;p&gt;Instead of npm, I tried using &lt;code&gt;yarn&lt;/code&gt; for the package manager, and it was WAY faster in GitHub Actions even without caching enabled.&lt;/p&gt;

&lt;p&gt;Although they did not make it into production, I experimented with the &lt;a href="https://www.rabbitmq.com/" rel="noopener noreferrer"&gt;RabbitMQ&lt;/a&gt; message broker, Python (Django, Flask), Kubernetes + minikube, &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;JWT&lt;/a&gt;, and &lt;a href="https://www.nginx.com/" rel="noopener noreferrer"&gt;NGINX&lt;/a&gt;. This was a hobby project, but I intended to learn about microservices along the way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Demonstration
&lt;/h3&gt;

&lt;p&gt;Here is a screen shot of the &lt;a href="https://llama-as-a-service.netlify.app/" rel="noopener noreferrer"&gt;LaaS&lt;/a&gt; website.&lt;/p&gt;

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

&lt;p&gt;If you would like to try out the API, simply made a GET request to the following endpoint:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://llama-as-a-service-images.herokuapp.com/random&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating an API
&lt;/h3&gt;

&lt;p&gt;First, I started with a simple API built with Node.js and Express, containerized with Docker. I set up GitHub Actions for CI to build and test it on each push. This will later connect to the database, to respond with image URLs.&lt;/p&gt;

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

&lt;p&gt;Although this API doesn’t NEED to scale to millions of users, it was a valuable exercise for building and scaling a system. I aimed for a minimum latency of 300ms with 200 RPS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Image Database
&lt;/h3&gt;

&lt;p&gt;With an API ready to connect to the database, it was time to choose between a NoSQL or SQL database.&lt;/p&gt;

&lt;p&gt;The answer is obvious for this use case. Let’s walk through the data we have, and the use cases.&lt;/p&gt;

&lt;p&gt;We are going to store one single table with image URLs. This could easily be done in either database, but there is one key factor. We need a way to randomly pull a list of images from the database.&lt;/p&gt;

&lt;p&gt;A SQL database makes it simple to query a random row, however, this is not horizontally scalable, and with a large data set, we are replicating the ENTIRE database to each new node.&lt;/p&gt;

&lt;p&gt;On the other hand, NoSQL databases are horizontally scalable; which leads me to Cassandra, but unfortunately it is very difficult to pull random selections from this type of NoSQL database.&lt;/p&gt;

&lt;p&gt;Finally, I settled with MongoDB, which has a built-in &lt;code&gt;$sample&lt;/code&gt; method to pull from the records.&lt;/p&gt;

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

&lt;p&gt;Once I got the MongoDB database running locally with Docker, I created a quick script to seed the database.&lt;/p&gt;

&lt;p&gt;Now it’s time to connect the API to the database.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting API to the Database
&lt;/h3&gt;

&lt;p&gt;Next, I used the &lt;code&gt;mongoose&lt;/code&gt; Node.js API to connect to the local MongoDB.&lt;/p&gt;

&lt;p&gt;I created two endpoints; one to upload an image URL, and another to retrieve a random list of images.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Endpoint Load Testing
&lt;/h3&gt;

&lt;p&gt;To experiment with scaling the API, I wanted to do load testing. Keep in mind that this API does not have much logic, meaning caching, or optimizing the code's performance, will have a huge impact.&lt;/p&gt;

&lt;p&gt;I found a tool for load testing called &lt;a href="https://www.artillery.io/" rel="noopener noreferrer"&gt;Artillery&lt;/a&gt;. Following &lt;a href="https://blog.appsignal.com/2021/11/10/a-guide-to-load-testing-nodejs-apis-with-artillery.html" rel="noopener noreferrer"&gt;this guide&lt;/a&gt; I installed Artillery and began research for the test configuration.&lt;/p&gt;

&lt;p&gt;The API currently has the &lt;code&gt;/random&lt;/code&gt; endpoint to return an image URL (a string), with very little computation. Let’s stress test this to see the current traffic limit.&lt;/p&gt;

&lt;p&gt;The random list endpoint is what we need to optimize. For the starting algorithm though, I seeded 100 image records into the database, and then pulled the ENTIRE list from the database each request. The API would then choose 25 random elements to return. Let’s benchmark how this performs with load testing.&lt;/p&gt;

&lt;p&gt;With the first run, API, the limit on the &lt;code&gt;/random?count=25&lt;/code&gt; endpoint was 225 RPS over 15 seconds, with 99% of the response times were under 300ms. We can improve this.&lt;/p&gt;

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

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

&lt;h3&gt;
  
  
  Optimizing the Endpoint
&lt;/h3&gt;

&lt;p&gt;We have many records of image URLs in the database. Somehow, we need to efficiently transform these into a list, pulling random selections from the database.&lt;/p&gt;

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

&lt;p&gt;Let’s optimize the query for pulling documents from the database. Using a special mongodb query, we can drastically reduce the computational load for a single request. Running locally in postman, &lt;code&gt;random?count=25&lt;/code&gt; endpoint went from ~150ms for a single request, to &amp;lt;50ms.&lt;/p&gt;

&lt;p&gt;This is the only code we need for this endpoint, compared to the previous 20 lines and O(n^2) space.&lt;/p&gt;

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

&lt;p&gt;With the new query, the endpoint maintains 99% sub-300ms response time with a max of 440 RPS over 15 seconds.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Horizontally Scaling the API
&lt;/h3&gt;

&lt;p&gt;With the containerized Node.js/Express API, I could run multiple containers, scaling to handle more traffic. Using a tool called &lt;a href="https://github.com/kubernetes/minikube" rel="noopener noreferrer"&gt;minikube&lt;/a&gt;, we can easily spin up a local &lt;a href="https://github.com/kubernetes/kubernetes" rel="noopener noreferrer"&gt;Kubernetes&lt;/a&gt; cluster to horizontally scale Docker containers. It was possible to keep one shared instance of the database, and many APIs were routed with an internal Kubernetes load balancer.&lt;/p&gt;

&lt;p&gt;Horizontally scaling the API to two instances, the random endpoint maintains 99% sub-300ms response time with a max of 650 RPS over 15 seconds. Three API Instances =&amp;gt; 99% sub-300ms response time with a max of 1000 RPS over 15 seconds. Five API Instances =&amp;gt; 99% sub-300ms response time with a max of 1200 RPS over 15 seconds.&lt;/p&gt;

&lt;p&gt;In practice, five instances were the limit of scaling the API horizontally. Even with more instances, the traffic was never sub 300ms response time. Note, this is dependent on the hardware of my local machine, and not accounting for cross-network latency in the real world.&lt;/p&gt;

&lt;p&gt;With scaling, we can achieve higher throughput, allowing more traffic to flow, and resiliency, where a failed node can simply be replaced.&lt;/p&gt;

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

&lt;p&gt;Since the image responses are intended to be random, we cannot cache the responses. It would be possible to scale the database with a slave/master system, but without a large data set, it is not worth the time to test. The bottleneck is most likely the API and connections to the database, versus MongoDB not handling read requests. It may be possible to improve the read times with a REDIS database, using in-memory caching, but that is overkill for this project.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setting up Authentication
&lt;/h3&gt;

&lt;p&gt;After playing around with load testing, I wanted to explore &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;JSON Web Tokens&lt;/a&gt; and build an API to handle authentication.&lt;/p&gt;

&lt;p&gt;This auth API will generate tokens, which will be sent back to the client as headers. The tokens headers are stored client-side (e.g. cookies, local storage), and sent to the backend each request.&lt;/p&gt;

&lt;p&gt;If we expand the backend, we could include the authentication logic in each microservice.&lt;/p&gt;

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

&lt;p&gt;Not practical. Instead, we can decouple the logic into its own service as shown below:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Creating a Gateway API
&lt;/h3&gt;

&lt;p&gt;Instead of exposing the users directly to each microservice, we should route ALL traffic from the clients to the Gateway API. For this, I chose the same tech stack of Node.js/Express. Using a library, I was able to set up a proxy to the other services. In the future, this could be very useful to standardize requests to the backend, track usage, forward data to a logging microservice, talk to a message broker, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Environment Variables and Configuration
&lt;/h3&gt;

&lt;p&gt;Most of the system built, I needed to simplify the process for configuring the Docker containers locally, and how environment variables would be shared to each. Keep in mind, each service needed to access these in GitHub Actions as well, during deployment.&lt;/p&gt;

&lt;p&gt;I used the &lt;code&gt;docker-compose&lt;/code&gt; files to easily spin up the containers locally. I used default values for the environment variables for local development, and kept the config files separated so it was easy to follow.&lt;/p&gt;

&lt;p&gt;This step was just a process of carefully writing the Docker and docker-compose files, and setting up GitHub Actions Secrets. The code could not run without having all env variables, could be hard to debug locally or lead to ambiguity for other developers.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Simple Frontend
&lt;/h3&gt;

&lt;p&gt;I would talk about building the frontend, but it is just a single page React app I built quickly. It does use a CSS library called &lt;a href="https://bulma.io/" rel="noopener noreferrer"&gt;Bulma&lt;/a&gt;, which is similar to tailwind and worth checking out. I did spend a day implementing a login/signup page, but this was just for the learning experience, and not what I wanted in the final product.&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions Testing and Deployment
&lt;/h3&gt;

&lt;p&gt;With most of the code written, it was time to deploy the app. This was actually a bumpy road because I was not sure how to approach this. I was keeping each component in its own repository on my personal GitHub Account, which was getting hard to keep track of.&lt;/p&gt;

&lt;p&gt;My solution was to create the &lt;a href="https://github.com/llama-as-a-service" rel="noopener noreferrer"&gt;Llama as a Service&lt;/a&gt; GitHub Organization, which also allowed me to store organization-wide secrets that any repository could access.&lt;/p&gt;

&lt;p&gt;Using GitHub Actions, I created workflows to build and test code on every push, and deploy to main branch Heroku (and Netlify for the frontend).&lt;/p&gt;

&lt;p&gt;I also created a workflow to tag and version every update, and release the Docker image to the &lt;a href="https://ghcr.io/" rel="noopener noreferrer"&gt;GitHub Container Registry&lt;/a&gt;. These packages could be private to the organization, or public. I did not end up using these published containers, but it was really dope to see everything automated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying to Production
&lt;/h3&gt;

&lt;p&gt;So after deploying the gateway API, frontend, and backend, I hoped all the services would be connected in production. For some reason the &lt;a href="https://www.npmjs.com/package/http-proxy-middleware" rel="noopener noreferrer"&gt;http-proxy-middleware&lt;/a&gt; was causing problems, and it was not worth redesigning the whole system. I was not ready to work with deploying a Kubernetes Cluster, so I did not use the GHCR Docker packages for deploying.&lt;/p&gt;

&lt;p&gt;Instead, I just stripped away the extra services that I had been working on, and stuck with a simple system to deploy. For the final product, there is the frontend deployed on Netlify, which connects to the API on Heroku, with talks to the MongoDB Atlas database (in the cloud).&lt;/p&gt;

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

&lt;h3&gt;
  
  
  View the Source Code
&lt;/h3&gt;

&lt;p&gt;If you wish to view all of the source code for this project, you can look through each repository here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Organization: &lt;a href="https://github.com/llama-as-a-service" rel="noopener noreferrer"&gt;github.com/llama-as-a-service&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;All the GHCR Packages: &lt;a href="https://github.com/orgs/llama-as-a-service/packages" rel="noopener noreferrer"&gt;github.com/orgs/llama-as-a-service/packages&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Frontend - &lt;a href="https://github.com/llama-as-a-service/frontend" rel="noopener noreferrer"&gt;github.com/llama-as-a-service/frontend&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Images API - &lt;a href="https://github.com/llama-as-a-service/images-service" rel="noopener noreferrer"&gt;github.com/llama-as-a-service/images-service&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Authentication API - &lt;a href="https://github.com/llama-as-a-service/auth-service" rel="noopener noreferrer"&gt;github.com/llama-as-a-service/auth-service&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Gateway API - &lt;a href="https://github.com/llama-as-a-service/gateway-service" rel="noopener noreferrer"&gt;github.com/llama-as-a-service/gateway-service&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to have a repository with Node.js, Express, and Docker set up with GitHub Actions, check out the &lt;a href="https://github.com/llama-as-a-service/express-docker-boilerplate" rel="noopener noreferrer"&gt;boilerplate repository here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are interested in more projects by me, you can check out the &lt;a href="https://spencerlepine.github.io/blog/manyshiba-the-worlds-greatest-twitter-bot" rel="noopener noreferrer"&gt;ManyShiba Twitter bot&lt;/a&gt;, or more on my website.&lt;/p&gt;




&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>node</category>
      <category>mongodb</category>
      <category>heroku</category>
    </item>
    <item>
      <title>Creating Custom Git Commands</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:19 +0000</pubDate>
      <link>https://dev.to/spencerlepine/creating-custom-git-commands-299e</link>
      <guid>https://dev.to/spencerlepine/creating-custom-git-commands-299e</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fcreating-custom-git-commands%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fcreating-custom-git-commands%2Fthumbnail.jpg" alt="Blog Post Thumbnail" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every time I clone a repository from GitHub, I always run the same set of commands. This is prone to typos and simply inconvenient. There is a simple solution of combining each step into a single command that automatically runs everything for us.&lt;/p&gt;

&lt;p&gt;In this example, I need to clone a GitHub repository, move into the new directory, and then open the project in VSCode.&lt;/p&gt;

&lt;p&gt;Instead of multiple commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  git clone https://github.com/spencerlepine/readme-crawler
  &lt;span class="nb"&gt;cd &lt;/span&gt;readme-crawler
  code &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It would great to run one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;    clone https://github.com/spencerlepine/readme-crawler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To achieve this, we can create a script in the&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 directory. Make sure this path matches up with your configuration for the terminal (e.g.

 ```PATH=$PATH:$HOME/bin```

).

Let’s create a custom script to combine the git commands.



```sh
  #!/bin/bash

  ((!$#)) &amp;amp;&amp;amp; echo missing git URL argument! &amp;amp;&amp;amp; exit 1

  git clone $1
  basename=$(basename $1)
  reponame=${basename%.*}
  cd $reponame
  npm install
  code .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use this script or create your own, and follow these steps to set up the custom command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Navigate to usr/local/bin -&amp;gt;&lt;br&gt;
&lt;br&gt;
&lt;code&gt;cd ~/../../usr/local/bin&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run&lt;br&gt;
&lt;br&gt;
&lt;code&gt;vim clone&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Paste the script&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Save the file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;*press ‘ESC’&lt;/li&gt;
&lt;li&gt;*press ‘SHIFT’ + ‘:’&lt;/li&gt;
&lt;li&gt;*type ‘wq’ + ENTER&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;h2&gt;
  
  
  Create an executable
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;
&lt;code&gt;chmod +x clone&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;h2&gt;
  
  
  Run the command!
&lt;/h2&gt;

&lt;p&gt;&lt;br&gt;
&lt;code&gt;clone https://github.com/spencerlepine/manyshiba-bot.git&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Viola! This script will accept one command line argument of the destination repo URL. It will automatically open the new project in VSCode in one command.&lt;/p&gt;

&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>terminal</category>
      <category>commands</category>
    </item>
    <item>
      <title>Git Project Configuration With Husky and ESLint</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:16 +0000</pubDate>
      <link>https://dev.to/spencerlepine/git-project-configuration-with-husky-and-eslint-ib1</link>
      <guid>https://dev.to/spencerlepine/git-project-configuration-with-husky-and-eslint-ib1</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fgit-project-configuration-with-husky-and-eslint%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fgit-project-configuration-with-husky-and-eslint%2Fthumbnail.jpg" alt="Blog Post Thumbnail" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Working on a project with Git and GitHub is relatively simple. When a project starts to grow however, it is crucial to write clean code that other developers can read. Follow this article to learn how to set up linting and pre-commit hooks for your repository.&lt;/p&gt;

&lt;p&gt;Let’s walk through the steps for a one-time setup to configure &lt;a href="https://github.com/typicode/husky" rel="noopener noreferrer"&gt;husky&lt;/a&gt; pre-commit and pre-push hooks, &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; with code styles conventions, &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;prettier&lt;/a&gt; code formatter, and &lt;a href="https://github.com/okonet/lint-staged" rel="noopener noreferrer"&gt;lint-staged&lt;/a&gt;. Husky automatically runs a script on each commit or push. This is useful for linting files to enforce code styles that keeps the entire code base following conventions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Walkthrough
&lt;/h2&gt;

&lt;p&gt;Install the dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install husky@4.3.8 lint-staged@10.5.4 prettier@2.8.8 --save-dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add husky@4.3.8 lint-staged@10.5.4 prettier@2.8.8 --dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Package.json Updates
&lt;/h3&gt;

&lt;p&gt;Add the following to your &lt;code&gt;package.json&lt;/code&gt; to configure all three packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@spencer/example-package"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"prettier --write ."&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;"prettier"&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;"printWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tabWidth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"singleQuote"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"semi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"trailingComma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"es5"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bracketSpacing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"arrowParens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"avoid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"proseWrap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"always"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"requirePragma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"insertPragma"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"endOfLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"jsxBracketSameLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"husky"&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;"hooks"&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;"pre-commit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lint-staged"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"lint-staged"&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;"**/*.(js|jsx|ts|tsx|json|css|md)"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"prettier --write"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure ESLint (optional)
&lt;/h3&gt;

&lt;p&gt;First, install this package&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm install eslint-config-prettier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, run &lt;code&gt;npm init @eslint/config&lt;/code&gt; to create a config file and choose preferred code styles.&lt;/p&gt;

&lt;p&gt;Alternatively, use this example file. In the root directory, create &lt;code&gt;.eslintrc&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"extends"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"eslint:recommended"&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;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"prettier"&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;"parserOptions"&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;"ecmaVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2017&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;"env"&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;"es6"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;"rules"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"no-console"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"no-unused-vars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"react/prop-types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"quotes"&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"double"&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;"avoidEscape"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Everything in action
&lt;/h2&gt;

&lt;p&gt;After making changes, commit the files, and see &lt;code&gt;lint-staged&lt;/code&gt; automatically run, triggered by the pre-commit hook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;my-project&lt;span class="nv"&gt;$ &lt;/span&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s1"&gt;'example commit message'&lt;/span&gt;
✔ Preparing lint-staged...
✔ Running tasks &lt;span class="k"&gt;for &lt;/span&gt;staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
&lt;span class="o"&gt;[&lt;/span&gt;example-branch 4bc4030] add new husky setup
 4 files changed, 59 insertions&lt;span class="o"&gt;(&lt;/span&gt;+&lt;span class="o"&gt;)&lt;/span&gt;, 44 deletions&lt;span class="o"&gt;(&lt;/span&gt;-&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All files have been linted and automatically fixed with &lt;code&gt;prettier&lt;/code&gt;, or denied if too many errors were thrown. Now we can push the "clean" code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;my-project&lt;span class="nv"&gt;$ &lt;/span&gt;git push origin example-branch
&lt;span class="c"&gt;# npx lint-staged&lt;/span&gt;
&lt;span class="c"&gt;# ... (no errors found)&lt;/span&gt;
&lt;span class="c"&gt;# npm test&lt;/span&gt;
&lt;span class="c"&gt;# ... (PASS)&lt;/span&gt;
Enumerating objects: 7, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Counting objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;7/7&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Delta compression using up to 8 threads
Compressing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;4/4&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Writing objects: 100% &lt;span class="o"&gt;(&lt;/span&gt;4/4&lt;span class="o"&gt;)&lt;/span&gt;, 375 bytes | 375.00 KiB/s, &lt;span class="k"&gt;done&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Total 4 &lt;span class="o"&gt;(&lt;/span&gt;delta 3&lt;span class="o"&gt;)&lt;/span&gt;, reused 0 &lt;span class="o"&gt;(&lt;/span&gt;delta 0&lt;span class="o"&gt;)&lt;/span&gt;, pack-reused 0
remote: Resolving deltas: 100% &lt;span class="o"&gt;(&lt;/span&gt;3/3&lt;span class="o"&gt;)&lt;/span&gt;, completed with 3 &lt;span class="nb"&gt;local &lt;/span&gt;objects.
To https://github.com/spencerlepine/my-project.git
   4bc4030..b558038  example-branch -&amp;gt; example-branch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Boilerplate
&lt;/h2&gt;

&lt;p&gt;See a working example here: &lt;a href="https://github.com/spencerlepine/husky-boilerplate" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;p&gt;A useful trick is the &lt;code&gt;-–no-verify&lt;/code&gt; flag to SKIP the pre-commit or pre-push hook.&lt;br&gt;
Use this option to skip the husky script in case you force a commit/push.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;my-project&lt;span class="nv"&gt;$ &lt;/span&gt;git push origin my-branch &lt;span class="nt"&gt;--no-verify&lt;/span&gt;
&lt;span class="c"&gt;# husky will not run "npm test"&lt;/span&gt;
... pushing to GitHub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;husky&lt;/code&gt; released v7, it had some major changes to the configuration. There are many articles and Stack Overflow answers about husky, but some of them are misleading if they were using v4.&lt;/p&gt;

&lt;p&gt;Hope this article helped! Interested in more, check out more articles &lt;a href="https://spencerlepine.github.io/blog" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>linter</category>
      <category>husky</category>
      <category>workflow</category>
    </item>
    <item>
      <title>ManyShiba - The World's Greatest Twitter Bot</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:14 +0000</pubDate>
      <link>https://dev.to/spencerlepine/manyshiba-the-worlds-greatest-twitter-bot-5a70</link>
      <guid>https://dev.to/spencerlepine/manyshiba-the-worlds-greatest-twitter-bot-5a70</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fmanyshiba-the-worlds-greatest-twitter-bot%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fmanyshiba-the-worlds-greatest-twitter-bot%2Fthumbnail.jpg" alt="Blog Post Thumbnail" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ManyShiba is the greatest Twitter bot ever created. Bless your soul with a daily photo of the almighty Shiba. Be uplifted by the spirit of this holy and sacred creature to free your soul.&lt;/p&gt;

&lt;p&gt;For so long, I felt that something was missing in my life. After being blessed by the presence of a divine Shiba Inu dog, I had my answer. I could cleanse my soul each day by reminding myself of this divine being. But that couldn’t be it, there had to be some way I could bless EVERYONE.&lt;/p&gt;

&lt;p&gt;Behold - your new favorite Twitter bot - &lt;a href="https://twitter.com/manyshiba" rel="noopener noreferrer"&gt;@ManyShiba&lt;/a&gt;. (source code: &lt;a href="https://github.com/spencerlepine/manyshiba-bot" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/p&gt;

&lt;h3&gt;
  
  
  So what exactly is the ManyShiba Bot?
&lt;/h3&gt;

&lt;p&gt;This is a simple Node.js app connected to the Twitter API. The app is deployed on &lt;a href="https://dashboard.heroku.com/" rel="noopener noreferrer"&gt;Heroku&lt;/a&gt;  and connected to the Twitter developer account.&lt;/p&gt;

&lt;p&gt;Each time the script runs, a new dog image will be fetched from the &lt;a href="https://shibe.online/" rel="noopener noreferrer"&gt;Shibe.online&lt;/a&gt; API. That image will then be uploaded and posted on the Twitter feed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technologies:
&lt;/h3&gt;

&lt;p&gt;To build this app, there were three technologies I worked with.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shibe.online/" rel="noopener noreferrer"&gt;Shibe.online API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/twit" rel="noopener noreferrer"&gt;twit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Node is a popular Javascript runtime environment that can easily run on Heroku. Heroku is a PaaS and great tool to deploy a small app for free.&lt;/p&gt;

&lt;p&gt;The Shibe.online API is a third party service to retrieve a link for dog pictures. Since there are many random photos to use in that database, it is the perfect resource for finding many new photos.&lt;/p&gt;

&lt;p&gt;Finally, the twit library is a Twitter API Client for Node that simplifies the REST requests. Since this app will only be posting tweets, there are on advanced requests being made to the Twitter API.&lt;/p&gt;

&lt;p&gt;With each of these tools, we can have a functioning Twitter bot. Here are the steps for code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Save the Twitter API configuration&lt;/li&gt;
&lt;li&gt;Initialize the Twit Client with the configuration&lt;/li&gt;
&lt;li&gt;Fetch a random image from Shibe.online&lt;/li&gt;
&lt;li&gt;Convert the image from a URL to base64&lt;/li&gt;
&lt;li&gt;Tweet the image&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After registering a &lt;a href="https://developer.twitter.com/" rel="noopener noreferrer"&gt;Twitter App&lt;/a&gt;, make sure to enable Read/Write permissions in the App settings. Create an &lt;code&gt;.env&lt;/code&gt; file in the root of the project based on &lt;code&gt;.env.example&lt;/code&gt;. We can use this data in our file with an object like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;consumer_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;consumer_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_API_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_API_ACCESS_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;access_token_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TWITTER_API_ACCESS_TOKEN_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;app.js&lt;/code&gt; we can import &lt;code&gt;twit&lt;/code&gt; and pass along the config obj:&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;twit&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;twit&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;twitterClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;twit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before we tweet anything, we first need to generate the content to post. This is where we will retrieve an image from the Shibe.online API. Note, the Shibe endpoint will return a list of image URLs stored on a third party server. We must download this image, because posting an image URL does not actually display it on the feed.&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;API_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://shibe.online/api/shibes?count=1&amp;amp;urls=true&amp;amp;httpsUrls=false&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;fetchRandomImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweetFunction&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;resultList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;API_ENDPOINT&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;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="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resultList&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;newImage&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After retrieving a URL from Shibe.online, we must fetch the image as well. We can convert the data from the image URL and convert it to a base64 string in memory. Since we are saving the image data, a Tweet will always load the image since it does not depend on the third party image database anymore.&lt;/p&gt;

&lt;p&gt;Note, you can use any library for HTTP requests like &lt;a href="https://axios-http.com/" rel="noopener noreferrer"&gt;axios&lt;/a&gt;. This example uses the &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;node-fetch&lt;/code&gt; libraries available on &lt;a href="https://www.npmjs.com/" rel="noopener noreferrer"&gt;npm&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node-fetch&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;http&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http&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;urlToBase64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imgUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tweetFunction&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;await&lt;/span&gt; &lt;span class="nx"&gt;http&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;imgUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;httpRes&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;httpRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setEncoding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// Exclude -&amp;gt; "data:" + httpRes.headers["content-type"] + ";base64,";&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;httpRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data&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;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;body&lt;/span&gt; &lt;span class="o"&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;httpRes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;end&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;tweetFunction&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="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;`Got error: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetchRandomImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweetFunction&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="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;urlToBase64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newImage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tweetFunction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a base64 string, we need to upload it as media context to Twitter. After uploading it, we have access to a &lt;code&gt;media_id&lt;/code&gt;. This media_id can be attached to the actual Tweet, which will cause the image to render on the feed. For this project, there is no text context attached to this Tweet.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tweetImage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweetContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweetContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;twitterClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;media/upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;media_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tweetContent&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="nx"&gt;err&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// now we can assign alt text to the media, for use by screen readers and&lt;/span&gt;
      &lt;span class="c1"&gt;// other text-based presentations and interpreters&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;mediaIdStr&lt;/span&gt; &lt;span class="o"&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;media_id_string&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;altText&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Shiba Inu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;meta_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;media_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mediaIdStr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;alt_text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;altText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="nx"&gt;twitterClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;media/metadata/create&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;meta_params&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="nx"&gt;err&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;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// now we can reference the media and post a tweet (media will attach to the tweet)&lt;/span&gt;
          &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&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="na"&gt;media_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;mediaIdStr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

          &lt;span class="nx"&gt;twitterClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;statuses/update&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;function &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&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="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="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;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="nf"&gt;fetchRandomImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tweetImage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With each step tied together, we can retrieve Shiba images and generate Tweets with media content. To see the source code, head over to the &lt;a href="https://github.com/spencerlepine/manyshiba-bot" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With a working Twitter bot, I could run the script with Node on my local machine. However, it wouldn’t be automated if I had to run it manually. To solve this, I decided to deploy everything onto Heroku. This service allows node servers to run not just simple static files.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://devcenter.heroku.com/articles/scheduler" rel="noopener noreferrer"&gt;Heroku Scheduler&lt;/a&gt;, you can configure script executions. Make sure to add this script to your &lt;code&gt;package.json&lt;/code&gt; 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="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;“&lt;/span&gt;&lt;span class="nx"&gt;node&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;&lt;span class="err"&gt;”&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Adding a setting to execute the script on a timer makes the bot automated. I decided to let the bot create a daily Tweet with this tool. Our ManyShiba bot is now fully functional!&lt;/p&gt;




&lt;p&gt;View the source code on &lt;a href="https://github.com/spencerlepine/manyshiba-bot" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>twitter</category>
      <category>node</category>
      <category>bot</category>
    </item>
    <item>
      <title>Portfolio Site Continuous Integration GitHub Action</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:11 +0000</pubDate>
      <link>https://dev.to/spencerlepine/portfolio-site-continuous-integration-github-action-3cl6</link>
      <guid>https://dev.to/spencerlepine/portfolio-site-continuous-integration-github-action-3cl6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fportfolio-site-continuous-integration-github-action%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fportfolio-site-continuous-integration-github-action%2Fthumbnail.jpg" alt="Blog Post Thumbnail" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After learning about GatsbyJS and building a static Portfolio site and blog, I searched for systems to deploy this website. At this point, I had purchased the domain name through AWS Route53, but I still needed somewhere to host the static files.&lt;/p&gt;

&lt;p&gt;I chose to deploy the site through Digital Ocean Droplet. This was a remote Ubuntu virtual machine with an IP address I could route to. Once I installed apache web server software and connected domain name, the website was live.&lt;/p&gt;

&lt;p&gt;There was still one problem with this deployment process. Updating the website took several steps. After local development, I needed to build/generate the static files with Gatbsy locally, and push them to the GitHub repo. Then, I would ssh into the Ubuntu Droplet, clone the updated repo again, and replace the static files for apache to serve.&lt;/p&gt;

&lt;p&gt;Steps to deploy were repetitive. Having to remember terminal commands and finding passwords was inconvenient. I was unable to build the static files on the remote Ubuntu machine with limited hardware specs.&lt;/p&gt;

&lt;p&gt;One improvement I made was writing a script to delete and copy new files when deploying on Digital Ocean. This addition did not solve everything, as I hard-coded my github username and repository name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# RUN the script:&lt;/span&gt;
&lt;span class="c"&gt;# sudo ./syncBuild.sh portfolio-site&lt;/span&gt;

&lt;span class="nv"&gt;GITHUB_LINK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/spencerlepine/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;.git"&lt;/span&gt;

git clone &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_LINK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GREEN&lt;/span&gt;&lt;span class="s2"&gt; successfully cloned repo&lt;/span&gt;&lt;span class="nv"&gt;$NC&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Removing the current public folder"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; public
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Moving into the github repo folder"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Moving public folder contents OUT of repo folder"&lt;/span&gt;
&lt;span class="nb"&gt;mv &lt;/span&gt;public ..
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GREEN&lt;/span&gt;&lt;span class="s2"&gt; Successfully copy news files &lt;/span&gt;&lt;span class="nv"&gt;$NC&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Moving back into parent directory"&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ..

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Deleting leftover github repo files"&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;  &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Restarting apache server"&lt;/span&gt;
systemctl restart apache2

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GREEN&lt;/span&gt;&lt;span class="s2"&gt; Public folder sync complete! &lt;/span&gt;&lt;span class="nv"&gt;$NC&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Visit: spencerlepine.com"&lt;/span&gt;

&lt;span class="c"&gt;# "syncBuild.sh" 52L, 956C&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although this process took less than 10 minutes, switching between the IDE, GitHub, the terminal, and the browser was annoying. It would be better to automate this process. To do that, we can use a GitHub action that will trigger on every repository update. A handy feature of GitHub repositories is the ability to store secrets/environment variables. We can use this to store passwords directly connected to the repository, so all credentials needed for the project are stored in one place.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Let’s create the GitHub workflow file:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

on:
  push:
    branches: [ master ]

jobs:

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    steps:
    - name: Deploy Static Files
      uses: appleboy/ssh-action@master
      with:
        host: ${{ secrets.HOST }}
        username: ${{ secrets.USERNAME }}
        key: ${{ secrets.KEY }}
        passphrase: ${{ secrets.PASSPHRASE }}
        port: ${{ secrets.PORT }}
        script_stop: true
        script: |
          # change directory to where website files are stored
          # clone the repository
          # remove the current public folder w/ static files
          # enter the repo folder
          # extract the public folder from the repo folder
          # remove the leftover GitHub repo files
          # restart the web server
          echo “Visit deployed site: spencerlepine.com”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This GitHub action will use another &lt;a href="https://github.com/appleboy/ssh-action" rel="noopener noreferrer"&gt;ssh-action&lt;/a&gt; action to handle the remote ssh connection. After storing the connection credentials in the GitHub repository secrets, this can securely/dynamically connect to the remote Ubuntu machine. The last key for this action job is the script, or verbatim Ubuntu commands that will be run. For the sake of brevity, I have only written pseudo-code for the deployment steps.&lt;/p&gt;

&lt;p&gt;With this file saved in the &lt;code&gt;.github&lt;/code&gt; folder in the project,  GitHub can execute the action and connect to our remote server autonomously. After adding a blog post or updating the website, the only step required is pushing the code to GitHub (which I would do anyways). Now, the GitHub workflow will handle all of the steps to connect to the host server, delete old static files, and download the fresh static files.&lt;/p&gt;

&lt;p&gt;The continuous integration for this website is completely automated now. This saves me time and effort. No need to worry about forgetting how to deploy later on.&lt;/p&gt;




&lt;p&gt;View the source code on &lt;a href="https://github.com/spencerlepine/spencerlepine.com" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>github</category>
      <category>automation</category>
      <category>repository</category>
    </item>
    <item>
      <title>Quickly Open GitHub Repo in Browser From Terminal</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:09 +0000</pubDate>
      <link>https://dev.to/spencerlepine/quickly-open-github-repo-in-browser-from-terminal-oal</link>
      <guid>https://dev.to/spencerlepine/quickly-open-github-repo-in-browser-from-terminal-oal</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fquickly-open-github-repo-in-browser-from-terminal%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fquickly-open-github-repo-in-browser-from-terminal%2Fthumbnail.jpg" alt="Blog Post Thumbnail" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I work a lot with the Git CLI and GitHub repository cloned on my local machine. I need a fast way to open the repository web page in the browser. Here is how I solved this, specifically on macOS.&lt;/p&gt;

&lt;p&gt;To start, the the quickest way to get the remote url is the following bash command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote &lt;span class="nt"&gt;-v&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/origin.*push/ {print $2}'&lt;/span&gt; | xargs open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That command alone is not very helpful, since it will be difficult to memorize and type out repeatedly.&lt;/p&gt;

&lt;p&gt;Instead, we can create a user-friendly command to use in the macOS terminal. By creating a custom named script in the &lt;code&gt;bin&lt;/code&gt; directory, the terminal will execute it when the command is used.&lt;/p&gt;

&lt;p&gt;First, navigate to the bin directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/../../usr/local/bin

&lt;span class="c"&gt;# Make sure this path matches up with your &lt;/span&gt;
&lt;span class="c"&gt;# configuration for the terminal (e.g. PATH=$PATH:$HOME/bin)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now create the script file, here I named the command &lt;code&gt;repo-open&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;vim repo-open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now paste the script contents into the file editor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

git remote &lt;span class="nt"&gt;-v&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'/origin.*push/ {print $2}'&lt;/span&gt; | xargs open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;press ‘ESC’&lt;/li&gt;
&lt;li&gt;press ‘SHIFT’ + ‘:’&lt;/li&gt;
&lt;li&gt;type ‘wq’ + ENTER&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create the executable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x repo-open
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That’s it! Now you can run the new script in the terminal. If we are in a directory with a &lt;code&gt;.git&lt;/code&gt; folder, we can run &lt;code&gt;repo-open&lt;/code&gt;, and it will open the remote URL in the default browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;repo-open
&lt;span class="c"&gt;# opens new page in the browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optionally, you can dig a little deeper into writing these scripts. Here are a few examples for Mac and Windows:&lt;/p&gt;

&lt;p&gt;Bash script for Mac:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;function &lt;/span&gt;gbrowse &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;gbrowsevar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git config &lt;span class="nt"&gt;--get&lt;/span&gt; remote.origin.url&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;gbrowsevar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    start &lt;span class="nv"&gt;$gbrowsevar&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Script for Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# GIT: open remote repository using Google Chrome&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;gbrowse &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;NC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\033[0m'&lt;/span&gt; &lt;span class="c"&gt;# No Color&lt;/span&gt;
    &lt;span class="nv"&gt;SB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\033[1;34m'&lt;/span&gt; &lt;span class="c"&gt;# Sky Blue&lt;/span&gt;
    &lt;span class="nv"&gt;PP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\033[1;35m'&lt;/span&gt; &lt;span class="c"&gt;# Purple&lt;/span&gt;
    &lt;span class="nv"&gt;gbrowsevar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git config &lt;span class="nt"&gt;--get&lt;/span&gt; remote.origin.url&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;→ &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;gbrowse:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Chrome&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;gbrowsevar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    start chrome &lt;span class="nv"&gt;$gbrowsevar&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# GIT: open remote repository using Firefox&lt;/span&gt;
&lt;span class="k"&gt;function &lt;/span&gt;fbrowse &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;NC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\033[0m'&lt;/span&gt; &lt;span class="c"&gt;# No Color&lt;/span&gt;
    &lt;span class="nv"&gt;SB&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\033[1;34m'&lt;/span&gt; &lt;span class="c"&gt;# Sky Blue&lt;/span&gt;
    &lt;span class="nv"&gt;PP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'\033[1;35m'&lt;/span&gt; &lt;span class="c"&gt;# Purple&lt;/span&gt;
    &lt;span class="nv"&gt;fbrowsevar&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git config &lt;span class="nt"&gt;--get&lt;/span&gt; remote.origin.url&lt;span class="si"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PP&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;→ &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SB&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;fbrowse:&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NC&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Firefox&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;fbrowsevar&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    start firefox &lt;span class="nv"&gt;$fbrowsevar&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;That’s all for today, I hope this article was helpful. If you have any questions, feel free to connect with me. &lt;/p&gt;

&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>git</category>
      <category>workflow</category>
      <category>terminal</category>
    </item>
    <item>
      <title>TypeScript Development Set Up for VSCode</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:07 +0000</pubDate>
      <link>https://dev.to/spencerlepine/typescript-development-set-up-for-vscode-36ec</link>
      <guid>https://dev.to/spencerlepine/typescript-development-set-up-for-vscode-36ec</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Ftypescript-development-set-up-for-vscode%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Ftypescript-development-set-up-for-vscode%2Fthumbnail.jpg" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Looking to set up VSCode for a TypeScript project? This article will walk through the initial configuration steps for just that.&lt;/p&gt;

&lt;p&gt;This walk-through assumes you have the already have the following installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" rel="noopener noreferrer"&gt;npm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To begin, let’s install the &lt;code&gt;typescript&lt;/code&gt; module globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; typescript
&lt;span class="nv"&gt;$ &lt;/span&gt;tsc &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Great! Now we can code our &lt;code&gt;.ts&lt;/code&gt; files. A small issue remains. In order to compile those files to &lt;code&gt;.js&lt;/code&gt;, we must run &lt;code&gt;tsc&lt;/code&gt; or &lt;code&gt;npm run build&lt;/code&gt; manually. To avoid this, we can auto-compile &lt;strong&gt;on save&lt;/strong&gt; in our IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Auto-Compile on Save
&lt;/h2&gt;

&lt;p&gt;Using the build tasks in VSCode, you can trigger auto-compile on save. This can be done through the settings search bar, or the application's menu bar in macOS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Enter &lt;code&gt;CMD+SHIFT+P&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select: &lt;code&gt;Tasks: Configure Default Build Task&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Choose: &lt;code&gt;tsc: watch - tsconfig.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 2:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Find tabs: in OS VSCode menu bar&lt;/li&gt;
&lt;li&gt;Click: &lt;code&gt;Terminal&lt;/code&gt; (open dropdown)&lt;/li&gt;
&lt;li&gt;Select: &lt;code&gt;Configure Default Build Task&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Choose: &lt;code&gt;tsc watch&lt;/code&gt; option&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now during development, anytime you save the &lt;code&gt;.ts&lt;/code&gt; files, it will generate &lt;code&gt;.js&lt;/code&gt; output automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hiding Compiler Output In Editor File Tree
&lt;/h2&gt;

&lt;p&gt;After running &lt;code&gt;tsc build&lt;/code&gt;, it will populate the directory with compiled files. You may find &lt;code&gt;.js&lt;/code&gt;,  &lt;code&gt;.js.map&lt;/code&gt;,  or &lt;code&gt;.d.ts.&lt;/code&gt; files, which could be useful to explore when learning. However, these can clutter up the file tree in the IDE.&lt;/p&gt;

&lt;p&gt;There is an easy was to hide these files in the workspace, without deleting them from the file system.&lt;/p&gt;

&lt;p&gt;To configure the following VSCode settings, it can be done in the global &lt;code&gt;settings.json&lt;/code&gt; file, or for the projects workspace&lt;/p&gt;

&lt;p&gt;To set up a workspace, Do &lt;code&gt;"CMD+SHIFT+P" =&amp;gt; "Preferences: Open Workspace Settings"&lt;/code&gt;. This creates the &lt;code&gt;&amp;lt;rootDir&amp;gt;/.vscode/settings.json&lt;/code&gt; file in the project directory.  Note, you include the &lt;code&gt;.vscode&lt;/code&gt; folder in the git history, unless a team is not sharing these settings.&lt;/p&gt;

&lt;p&gt;To find the  settings file do &lt;code&gt;"CMD+SHIFT+P" =&amp;gt; "Preferences: Open User Settings (JSON)"&lt;/code&gt; or &lt;code&gt;"CMD+SHIFT+P" =&amp;gt; "Preferences: Open Workspace Settings"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Add the following options for file ignoring to the VSCode &lt;code&gt;settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"files.exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"**/.git"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"**/.DS_Store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"**/*.js.map"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"**/*.js"&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="nl"&gt;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$(basename).ts"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"files.exclude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"**/.git"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"**/.DS_Store"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"**/*.d.ts"&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;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$(basename).ts"&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;"**/*.js"&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;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$(basename).ts"&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;"**/*.js.map"&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;"when"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$(basename)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using .gitignore
&lt;/h2&gt;

&lt;p&gt;When using git for your TypeScript project, you are not committing the compiled  JavaScript code. You most likely will ignore the  files generated, so you can configure that in the &lt;code&gt;.gitignore&lt;/code&gt;.  Be aware of needed files however, for example a &lt;code&gt;jest.config.js&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Keeping the source &lt;code&gt;.ts&lt;/code&gt; files, a &lt;code&gt;.gitignore&lt;/code&gt; file may look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;*.js
&lt;span class="gs"&gt;!jest.config.js
&lt;/span&gt;*.d.ts
&lt;span class="p"&gt;node_modules
&lt;/span&gt;&lt;span class="gs"&gt;!lambda/*.js
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure Format On Save
&lt;/h2&gt;

&lt;p&gt;Optionally, you can avoid wasting time on formatting with &lt;code&gt;formatOnSave&lt;/code&gt;. Add the following key to your &lt;code&gt;settings.json&lt;/code&gt; file to enable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"[typescript]"&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;"editor.defaultFormatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"esbenp.prettier-vscode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"editor.formatOnSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;That's it! You can now begin development for your new TypeScript project, with auto-compile and format-on-save enabled.  The main purpose of this article was to document the setup project for my own use, and strengthen my understanding by teaching.&lt;/p&gt;




&lt;p&gt;That’s all for today, I hope this article was helpful. If you have any questions, feel free to connect with me. &lt;/p&gt;

&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>typescript</category>
      <category>vscode</category>
      <category>workflow</category>
    </item>
    <item>
      <title>What I Learned During 100DaysOfCode</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:05 +0000</pubDate>
      <link>https://dev.to/spencerlepine/what-i-learned-during-100daysofcode-dfo</link>
      <guid>https://dev.to/spencerlepine/what-i-learned-during-100daysofcode-dfo</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fwhat-i-learned-during-100-days-of-code%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fwhat-i-learned-during-100-days-of-code%2Fthumbnail.jpg" alt="Blog Post Thumbnail" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently completed a popular challenge on Twitter named #100DaysOfCode. There is no barrier to entry, and you just need to code for at least 1 hour a day. This is a great challenge to motivate yourself and see your progress alongside others.&lt;/p&gt;

&lt;p&gt;In my case, it was not my first time coding when I started this. However, the challenge helped me stay dedicated and build upon each skill I learned.&lt;/p&gt;

&lt;p&gt;To see the original README journal, see &lt;a href="https://github.com/spencerlepine/100-days-of-code" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 1–10: The Challenge Begins!
&lt;/h2&gt;

&lt;p&gt;It all started with a ton of useful lessons on ES6 skills. There was a lot of new syntax and tricks that I never knew about. Since I started learning Javascript before ES6 was released, there were quite a few features I needed to practice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://scrimba.com/learn/introtoes6" rel="noopener noreferrer"&gt; ES6 Course - Scrimba&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Having just completed a small course on the React framework, I worked on implementing ES6 skills I picked up to make small projects. Here is a simple React app connected to GitHubs REST API. This helped me learn how to make fetch calls and save the data to state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spencerlepine/github-api-react" rel="noopener noreferrer"&gt; spencerlepine/github-api-react - GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each day I worked on different React concepts. Connecting/modifying a mock database locally. Working with state and props and pass data around. My most effective learning styles was learning-by-doing. After reading the docs and seeing a new feature, I would realize how I could use it to improve my project.&lt;/p&gt;

&lt;p&gt;To wrap up this part of the challenge, there were some coding challenges from Cyber Dojo to help practice problem solving. I was able to solve “Align Columns”, “LCD Digits”, “Wonderland Number”. Check out my repo with my solutions here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spencerlepine/cyber-dojo-exercises" rel="noopener noreferrer"&gt;spencerlepine/cyber-dojo-exercises - GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 11–20: Just Getting Started
&lt;/h2&gt;

&lt;p&gt;At this point, there was some great momentum and I was always feeling more motivated to learn. The Scrimba React course had a lot of great lessons on all the basics of React.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://scrimba.com/learn/react" rel="noopener noreferrer"&gt;The React Bootcamp - Scrimba&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built a controlled-form in React that would update state based on events and targets. Next up was the big concept of hooks and functional components. Little did I know this was only the START of what is possible in React.&lt;/p&gt;

&lt;p&gt;The React course was complete, which led me to start a new project called Spotify Top Songs. This site would connect to the Spotify Web API with client-side authentication. When a user connected their account, they could select various artists from a menu. The script would then generate a playlist by accessing the top 5 songs of each artist.&lt;/p&gt;

&lt;p&gt;This time, code was much more organized with the components and logic separated. With so many fetch calls and bits of logic to intertwine, it was important to build everything slowly and cleanly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spencerlepine/spotify-top-songs" rel="noopener noreferrer"&gt;spencerlepine/spotify-top-songs - GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I learned about prop-types and default props, which can be pretty handy. Worked React higher order components and children components. Started learning about AWS S3 buckets. To practice, I deployed my Spotify Top Songs to the S3 bucket.&lt;/p&gt;

&lt;p&gt;After discovering the behemoth of AWS products, I wasted no time working to set up an AWS EC2 instance to host a static site, which I would connect to my Route53 domain.&lt;/p&gt;

&lt;p&gt;Just before reaching day 20, a worked on an in-browser &lt;a href="https://github.com/spencerlepine/react-chess" rel="noopener noreferrer"&gt;Chess React&lt;/a&gt; app. This was just another opportunity to practice Javascript and apply knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 21–30: Gaining Momentum!
&lt;/h2&gt;

&lt;p&gt;With a solid understanding of React, it was time to double down on more computer science fundamentals. It was exciting to build toy projects, but that wouldn’t be enough.&lt;/p&gt;

&lt;p&gt;After finding the CS50 course online, I watched the lectures on C and Python. This was very useful to learn about memory pointers and how the interpreter reads code. Dave is a GREAT teacher and I would highly recommend going through the course.&lt;/p&gt;

&lt;p&gt;Lots to learn about memory allocation, what libraries are doing under the hood, string manipulation, and regex.&lt;/p&gt;

&lt;p&gt;Alongside CS50 was more material about React memo and Context API — write a component to access a ‘global’ state in a separate file. Import that file and render through that in any component of choice. Custom hooks that handle business logic. React router basics. Conditional rendering. All of these skills would allow me to start creating real multi-page sites!&lt;/p&gt;

&lt;p&gt;Day 29 was the day I started working on Galvanize basic prep lessons.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 31–40: Diving Deeper
&lt;/h2&gt;

&lt;p&gt;CS50 included some lessons about data structures and SQL. I worked on some really good challenges for the CS50 Fiftyville assignment.&lt;/p&gt;

&lt;p&gt;It was time for me to learn how to connect a database. I began working with MongoDB and Node.js. I used Postman to practice making requests.&lt;/p&gt;

&lt;p&gt;With a lot of data to store in state, I needed a way to organize it all. Thats where React Redux came up — createStore, redux philosophies, subscribe, dispatch, combineReducer. Abstracting reducers to handle each state in isolation. combineReducers to combine everything and handle state more cleanly in a rootReducer.&lt;/p&gt;

&lt;p&gt;With MongoDB connected to React, I was able to work on a MERN app that could read/write from the database. Connecting back-end/front-end routes and using controllers for the fetch logic/requests.&lt;/p&gt;

&lt;p&gt;Day 36 was when I began the QuickCar react app. Connected it to a backend MongoDB Atlas server. Add reducer + actions for post/fetch from frontend. Save the data in redux state. I created forms and routes with components connected to the Redux state using useSelectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 41–50: Working on QuickCart
&lt;/h2&gt;

&lt;p&gt;As I worked through some lessons for Galvanize basic prep, I also started lessons on &lt;a href="https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/" rel="noopener noreferrer"&gt;freeCodeCamp&lt;/a&gt;. Got a better understanding of Regex from that.&lt;/p&gt;

&lt;p&gt;Next came Javascript algorithm practice + Object Orientated Programming review. Inheriting methods from parent objects. Using Object.prototypes to reuse methods.&lt;/p&gt;

&lt;p&gt;I revisited my AWS account. Started basic portfolio site using React — Set up LAMP stack server to host website. Direct AWS Route53 domain to DigitalOcean droplet. Set up SSL, MySQL, and WordPress. Working to set up a blog too.&lt;/p&gt;

&lt;p&gt;With a working site, it would be really nice to have a blog hosted on the domain. I attempted to set up a blog. Researched Gatsby, GraphCMS, ButterCMS, GraphQL. Queries were not working. Unable to route to posts. This was a difficult concept to navigate, but along the way I learned a lot about apache and setting up ssl.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 51–60: A Working Blog!
&lt;/h2&gt;

&lt;p&gt;Gatsby.js was the answer. Instead of using a headless CMS, I settled with local md files to use for an article. During this challenge, I spent many hours working with the ‘mdx’ plugin. I tried to connect a CMS through queries and GraphQL, but it was very difficult.&lt;/p&gt;

&lt;p&gt;Around this time I continued working through Learn.co lessons for Flatiron prep. There were two main sections on Javascript and Ruby. The Ruby material was interesting and they even did a review on Object Oriented Programming.&lt;/p&gt;

&lt;p&gt;Flatiron wasn’t the only school that I was preparing for though. I completed my technical assessment by creating a basic HTML page with images and a clickable button.&lt;/p&gt;

&lt;p&gt;The last major assignment on hand was working through more of the freeCodeCamp Javascript fundamentals material.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 51–60: Coding Bootcamp!
&lt;/h2&gt;

&lt;p&gt;No, I didn’t start a coding bootcamp, but I was getting ready! I got accepted into Flatiron (they didn’t do a TAA anymore), accepted into General Assembly, and I was studying to pass my Hack Reactor technical assessment. My target was in August, which was over 100 days from this point.&lt;/p&gt;

&lt;p&gt;Making sure I could get into these schools was my main priority, but I continued learning and working on projects on the side.&lt;/p&gt;

&lt;p&gt;I worked on building the QuickCart app and added tons of features. Import/Export a grocery database. Improve the UI and styles. Upload images for a product in the form. Convert images to base64 strings. Work with file blobs and cropping images. Generate suggested products with a recommendation algorithm. Work on authentications between the front/back-end for the MERN app. Add a “cart” to store items until purchase.&lt;/p&gt;

&lt;p&gt;At this point, the grocery app was still connected to MongoDB. I was able to use localStorage and save some user data, but I knew I needed EVERYTHING to be in the cloud.&lt;/p&gt;

&lt;p&gt;With all that work done, I began Hack Reactor premium prep. The technical admissions assessment was going to be difficult, so this would help me prepared my vocalization and problem solving.&lt;/p&gt;

&lt;p&gt;This is where I began working testing. Test driven development is integral to Software Engineering. I was able to appreciate my code much more after practicing it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 71–80: Hack Reactor and Firebase
&lt;/h2&gt;

&lt;p&gt;At this point, I was making really good progress in my Hack Reactor prep work. I was able to learn the basic methodology of testing my code. Whenever I worked on Javascript challenges, it was important to practice writing tests and get used to the upfront setup.&lt;/p&gt;

&lt;p&gt;This week was my first attempt at the Hack Reactor TAA, and I passed! This was incredible, because I got to choose between GA, Flatiron, and Hack Reactor.&lt;/p&gt;

&lt;p&gt;I continued working on QuickCart on the side. There were some bug fixes and UI improvements that needed to be done. I migrated from MongoDB to Firebase for the backend. I had to read through the docs and get familiar with Firestore.&lt;/p&gt;

&lt;p&gt;One of the most important themes of this week was practicing problem solving. Hack Reactor was really pushing best practices and effective communication. Each day I worked on a toy problem/code challenge. With a timer running, I would work on my pseudo-code BEFORE starting and really explain/verbalize my thought process as much as I could.&lt;/p&gt;

&lt;p&gt;Now that I was able to write small tests for a challenge, I wanted to work on writing tests for a real project. I started learning how to use Enzyme and Jest for React test-driven-development. The process is tedious at the start, but it will ensure the code is more robust. This was difficult for me at first, because I wasn’t sure if my React test was written incorrectly, or I needed to pass the test now instead. There were lots of features to read Docs about, like redirecting, testing contexts/store, and tons more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 81–90: Test Driven Development and APIs
&lt;/h2&gt;

&lt;p&gt;The TDD practice with React was the birth of Woofer. I wanted to work on a project and build it from the ground up writing tests. I did my best to write a test FIRST before writing code. This project did not go very far during my challenge, since there were other priorities. It was going to take a long time to complete the idea, and it was just good practice to implement testing.&lt;/p&gt;

&lt;p&gt;I documented/planned out the Woofer app before starting. I made wireframes and planned out the logic/routing ahead of time. It was taking WAY too much time to work with mock stores and complex routing in testing. I even fell into certain anti-patterns with testing after researching about tests online.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spencerlepine/woofer" rel="noopener noreferrer"&gt;spencerlepine/woofer - GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I stepped away from the deep rabbit hole of React TDD. I went back to QuickCart and migrated EVERYTHING to client-side. Now it was one complete React app with firebase authentication built in. I was able to host this site on Heroku too, so anyone could use it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/spencerlepine/quickcart" rel="noopener noreferrer"&gt;spencerlepine/quickcart  - GitHub&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One feature I had been eager to add to the grocery app was “searching products”. With an empty form, users could manually input EVERY detail about a product, but nobody would want to use that. By connecting to the OpenFoodFacts API, I could search a dump of thousands of grocery products. This was an open source dump that anybody can contribute to. They also feature nutrition score data, so it can help users see healthier options.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 91–100: The Final Stretch
&lt;/h2&gt;

&lt;p&gt;After learning so much with Javascript and best-practices for writing code, it was time to USE these skills. I continued working through Hack Reactor prep material. I learned about higher order functions, scopes, and hoisting. There were lessons about terminal commands and important dev tools like homebrew. It was time to upgrade my developer workflow, and get familiar with industry standard software.&lt;/p&gt;

&lt;p&gt;Here I read about Node.js, npm, semvar, and modules. This was everything I need to know about how projects are set up and how developers are able to work together. There needs to be structure and conventions throughout the code base so everyone is on the same page.&lt;/p&gt;

&lt;p&gt;I also added a some features to QuickCart on the side again. I connected a Google Custom Search Engine to allow image searches for a product. Instead of having the user upload or snap a photo, they could simply link an existing photo. This allowed me to store the image sources with links, instead a long base64 string with image data. That would improve scalability and prevent product images from being lost easily.&lt;/p&gt;

&lt;p&gt;The next Hack Reactor prep section was about Git and GitHub. This was a REALLY useful sections to go through because it will be the backbone of any project. Knowing how to properly document and collaborate on a project makes everything so much more compatible. Your code needs to be readable and maintainable. People can review your code and merge branches to improve the project. You could even checkout different commits and revert your code. Before I learned about these practices, I would always CTRL+Z my file and start over again, wasting so much time.&lt;/p&gt;

&lt;p&gt;More work on Mocha and Chai testing was done. Getting familiar with different libraries and how they have similar functions.&lt;/p&gt;

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

&lt;p&gt;Challenge complete! After 100 days of coding, I was able to explore new features and concepts relating to Javascript and software development. The dedication allowed me to keep my momentum and build upon what I learned the previous day.&lt;/p&gt;

&lt;p&gt;Here is an overview of everything I learned:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  ES6 Javascript&lt;/li&gt;
&lt;li&gt;  Functional vs. OPP programming&lt;/li&gt;
&lt;li&gt;  Javascript algorithms and data structures&lt;/li&gt;
&lt;li&gt;  Node.js&lt;/li&gt;
&lt;li&gt;  Redux&lt;/li&gt;
&lt;li&gt;  React&lt;/li&gt;
&lt;li&gt;  SQL&lt;/li&gt;
&lt;li&gt;  Python&lt;/li&gt;
&lt;li&gt;  Comp Sci fundamentals&lt;/li&gt;
&lt;li&gt;  MongoDB&lt;/li&gt;
&lt;li&gt;  Firebase&lt;/li&gt;
&lt;li&gt;  React Context, state, props, controlled forms&lt;/li&gt;
&lt;li&gt;  CMS&lt;/li&gt;
&lt;li&gt;  Hosting a static site&lt;/li&gt;
&lt;li&gt;  Procedure for problem solving (pseudocode, breaking it down)&lt;/li&gt;
&lt;li&gt;  Mocha/Chai testing&lt;/li&gt;
&lt;li&gt;  React Jest/Enzyme testing&lt;/li&gt;
&lt;li&gt;  Connecting to APIs (Spotify Web API, GitHub REST API, OpenFoodFacts API)&lt;/li&gt;
&lt;li&gt;  Fetch calls + axios&lt;/li&gt;
&lt;li&gt;  Separating the front-end / back-end&lt;/li&gt;
&lt;li&gt;  Deploying to Heroku&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Projects
&lt;/h3&gt;

&lt;p&gt;Explore more &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;projects&lt;/a&gt; — or check out the ones mentioned in the article:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;QuickCart&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make a shopping list with personal grocery data to help budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/quickcart" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Portfolio Site&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Portfolio and blog website (&lt;a href="https://www.spencerlepine.com" rel="noopener noreferrer"&gt;Visit Here&lt;/a&gt;) created by Spencer Lepine. Built using static pages created with GatsbyJS…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/portfolio-site-v2" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Cyber Dojo Exercises&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Personal solutions to various Cyber Dojo exercises. Code is written in Python and tests use 'asserts' with pytest. All…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/cyber-dojo-exercises" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Woofer&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tinder for Pets Web App. Swipe and connect with other furry friends in the area.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/woofer" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;React Chess&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Play chess in the browser by with drag and drop moves. This was created using the Javascript React framework. component…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/react-chess" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Study Garden&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Improve focus and discipline with this timer app. Study until the timer runs out and add plants to your personal…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/study-garden" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;Spotify Top Songs&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate a Spotify playlist based on the top rated songs of your favorite artists. Connect user Spotify accounts to…&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/spotify-top-songs" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;GitHub User Overview&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This React App allows the user to type a username get an overview of their GitHub repos using the GitHub REST API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/github-api-react" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;CS50 Problem Sets&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;My solutions to the online CS50 course generously provided by Harvard University.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source Code:&lt;/strong&gt; &lt;a href="https://github.com/spencerlepine/cs50-problem-sets" rel="noopener noreferrer"&gt;GitHub Repo&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Interested in working together?
&lt;/h2&gt;

&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>developer</category>
      <category>challenge</category>
      <category>learning</category>
    </item>
    <item>
      <title>Software Engineering Workflow</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:02 +0000</pubDate>
      <link>https://dev.to/spencerlepine/software-engineering-workflow-4doo</link>
      <guid>https://dev.to/spencerlepine/software-engineering-workflow-4doo</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fsoftware-engineering-workflow%2Fthumbnail.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fspencerlepine%2Fblog%2Fmain%2Fcontent%2Fsoftware-engineering-workflow%2Fthumbnail.jpg" alt="Blog Post Thumbnail" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is a collection of resources and my general workflow for Software Engineering. Note: workstation is running MacOS.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Dependencies/Libraries:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://brew.sh/" rel="noopener noreferrer"&gt;Homebrew&lt;/a&gt; - package manager for linux-based OSs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://git-scm.com/downloads" rel="noopener noreferrer"&gt;Git&lt;/a&gt; - version control, manage files during project development&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nodejs.org/en/download/" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; + &lt;a href="https://github.com/nvm-sh/nvm" rel="noopener noreferrer"&gt;Nvm&lt;/a&gt; - runtime for javascript without a browser&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" rel="noopener noreferrer"&gt;Npm&lt;/a&gt; - large organization of libraries/packages available to use in projects.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.python.org/downloads/" rel="noopener noreferrer"&gt;Python 3&lt;/a&gt; - python language interpreter for python ^3.0.0.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mysql.com/products/workbench/" rel="noopener noreferrer"&gt;MySQL&lt;/a&gt; - SQL database software for development&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://redis.io/" rel="noopener noreferrer"&gt;Redis&lt;/a&gt; - real time data storage with different  data structures in a cache&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://devcenter.heroku.com/articles/heroku-cli" rel="noopener noreferrer"&gt;Heroku CLI&lt;/a&gt; - manager for Heroku apps from the command line&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/cli/" rel="noopener noreferrer"&gt;Amazon CLI&lt;/a&gt; - manager for AWS services from the command line&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Communication:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://slack.com/" rel="noopener noreferrer"&gt;Slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zoom.us/" rel="noopener noreferrer"&gt;Zoom&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Recording:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://obsproject.com/" rel="noopener noreferrer"&gt;OBS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://zoom.us/" rel="noopener noreferrer"&gt;Zoom Meeting Recording&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Other Software:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.google.com/chrome/" rel="noopener noreferrer"&gt;Chrome&lt;/a&gt; - main browser with debugging tools&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.postman.com/" rel="noopener noreferrer"&gt;Postman&lt;/a&gt; - API platform for easy endpoint testing&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://justgetflux.com/" rel="noopener noreferrer"&gt;Flux&lt;/a&gt; - screen eye strain assistance&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.gimp.org/" rel="noopener noreferrer"&gt;GIMP&lt;/a&gt; - photo editing software&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Toy problems:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://excalidraw.com/" rel="noopener noreferrer"&gt;ExcaliDraw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leetcode.com/" rel="noopener noreferrer"&gt;LeetCode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cyber-dojo.org/creator/home" rel="noopener noreferrer"&gt;CyberDojo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.topcoder.com/" rel="noopener noreferrer"&gt;TopCoder&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://coderbyte.com/" rel="noopener noreferrer"&gt;Coderbyte&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hackerrank.com/" rel="noopener noreferrer"&gt;HackerRank&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Note taking:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;IDE:&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://code.visualstudio.com/download" rel="noopener noreferrer"&gt;VSCode&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MacOS Quick Action: Open Folder from finder -&amp;gt; &lt;a href="https://stackoverflow.com/questions/64040393/open-a-folder-in-vscode-through-finder-in-macos" rel="noopener noreferrer"&gt;Configure Quick Action&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; Extension - Integrates ESLint JavaScript into VS Code.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=CoenraadS.bracket-pair-colorizer" rel="noopener noreferrer"&gt;Bracket Pair Colorizer&lt;/a&gt; Extension - A customizable extension for colorizing matching brackets&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://marketplace.visualstudio.com/items?itemName=peakchen90.open-html-in-browser" rel="noopener noreferrer"&gt;Open In Default Browser&lt;/a&gt; Extension -  A VSCode extension to fast open html file in browser&lt;/li&gt;
&lt;li&gt; &lt;a href="https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint" rel="noopener noreferrer"&gt;Stylelint&lt;/a&gt; Extension - Modern CSS/SCSS/Less linter&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;settings.json:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"editor.lightbulb.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.parameterHints.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.renderWhitespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"all"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.snippetSuggestions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.tabSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.wordWrap"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"emmet.showExpandedAbbreviation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"never"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"files.trimTrailingWhitespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"javascript.suggest.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"javascript.updateImportsOnFileMove.enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"never"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"javascript.validate.enable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"eslint.alwaysShowStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"explorer.confirmDelete"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"python.pythonPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"workbench.editorAssociations"&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;"*.ipynb"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"jupyter.notebook.ipynb"&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;"[javascript]"&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;"editor.defaultFormatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vscode.typescript-language-features"&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;"css.validate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"window.zoomLevel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.hover.sticky"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.formatOnPaste"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.formatOnSave"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.defaultFormatter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vscode.json-language-features"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"workbench.iconTheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"material-icon-theme"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"security.workspace.trust.untrustedFiles"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"liveshare.allowGuestTaskControl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"liveshare.allowGuestDebugControl"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"liveshare.anonymousGuestApproval"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"accept"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"python.defaultInterpreterPath"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/usr/bin/python3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="nl"&gt;"editor.largeFileOptimizations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Interested in working together?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Follow my journey or connect with me here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/spencerlepine/" rel="noopener noreferrer"&gt;/in/spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Email: &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;spencer.sayhello@gmail.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio: &lt;a href="https://spencerlepine.com" rel="noopener noreferrer"&gt;spencerlepine.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/spencerlepine" rel="noopener noreferrer"&gt;@spencerlepine&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>workflow</category>
      <category>tools</category>
      <category>engineer</category>
    </item>
    <item>
      <title>Preparing for My Amazon Front End Engineer Interview</title>
      <dc:creator>Spencer Lepine</dc:creator>
      <pubDate>Mon, 08 Apr 2024 20:56:00 +0000</pubDate>
      <link>https://dev.to/spencerlepine/preparing-for-my-amazon-front-end-engineer-interview-5d0m</link>
      <guid>https://dev.to/spencerlepine/preparing-for-my-amazon-front-end-engineer-interview-5d0m</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zqd2hojsc46i1z7e4so.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zqd2hojsc46i1z7e4so.png" alt="Blog Post Thumbnail" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article covers all the study material and advice that helped me ace my &lt;strong&gt;Amazon Front-End Engineer I (FEE)&lt;/strong&gt; interview (November 2022, remote onsite). This is relevant to both the 60min phone interview and final round on-site. I’ll break it down into two sections, technical and behavioral.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note, if you’re brand new to the FEE role, I’d recommend skimming through &lt;a href="https://interviewkickstart.com/blogs/companies/amazon-front-end-engineer-interview-prep?utm_source=spencerlephinemedium&amp;amp;utm_medium=spencerlephinemedium" rel="noopener noreferrer"&gt;this article&lt;/a&gt; first.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Interview Stages
&lt;/h2&gt;

&lt;p&gt;The initial &lt;strong&gt;60-minute phone screen&lt;/strong&gt; is typically more laid back, but still focused. It’s an opportunity to talk about your background and motivation to work at Amazon. You’ll want to be up to speed with LeetCode-style problems (mostly medium, rarely hard) and familiarized with the LPs (Leadership Principles).&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;full on-site&lt;/strong&gt;, I’d recommend reviewing this entire article.&lt;/p&gt;

&lt;p&gt;Here’s brief visual for the interview pipeline at Amazon:&lt;/p&gt;

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

&lt;p&gt;&lt;em&gt;Amazon Interview Stages: Application → Online Assessment (OA) → Phone Screen (60min) → Onsite (4 rounds) → Team Matching&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Skills &amp;amp; Coding Challenges
&lt;/h2&gt;

&lt;p&gt;You likely won’t use an IDE during the interview, allowing you to focus on the concept/design, instead of wasting time on bugs. In my job-hunt experience, this style of using plain JS/CSS/HTML was unique to Amazon and I took some time to brush up on vanilla JS.&lt;/p&gt;

&lt;p&gt;Here’s a breakdown of what I studied for the technical portion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LeetCode problems (mostly medium, a few hard)&lt;/li&gt;
&lt;li&gt;Verbalizing your thought process&lt;/li&gt;
&lt;li&gt;Vanilla JS and DOM API (practice building simple widgets)&lt;/li&gt;
&lt;li&gt;Frontend system design basics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the online assessment (OA) and &lt;strong&gt;coding challenges&lt;/strong&gt;, it’s best to practice LeetCode style problems (medium difficulty, rarely high). When solving these problems, I briefly think about edge-cases/constraints, write all the psuedo-code, then attempt the problem. Don’t hesitate to check in with the interviewer at checkpoints, asking if you’re on track or they have any thoughts. Even if you don’t solve 100% of the problem, an interviewer still values the progress you make.&lt;/p&gt;

&lt;p&gt;It’s critical to verbalize your thought process while solving problem. Whether you are explaining your implementation, or thinking through the problem, the interviewer is assessing how you work through it. Avoid going silent if possible. I pretty awkward at this, so to practice I did talked through LeetCode problems in recorded mock Zoom meetings (like this video)&lt;/p&gt;

&lt;p&gt;For the &lt;strong&gt;on-site final round&lt;/strong&gt;, you’ll work on a frontend system-design question, which will be solved using vanilla JS, CSS, and HTML (including DOM API). Keep in mind that the difficulty level may vary based on your experience, potentially covering a broader scope than outlined in this article.. Note, this could vary based on your level of experience, so difficulty level could be a larger scope than this article. You may need to brush up on the DOM methods and syntax. I recommend building several small widgets to practice. Make sure you understand selectors, event listeners, and modifying styles with JS. If you are unsure what kinds of widgets I am talking about, look through &lt;a href="https://www.frontendinterviewhandbook.com/companies/amazon-front-end-interview-questions" rel="noopener noreferrer"&gt;this&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;Make sure you understand &lt;strong&gt;frontend system design basic&lt;/strong&gt;: component design, state management, performance optimizations, and APIs. To delve into this check out this &lt;a href="https://www.youtube.com/playlist?list=PLI9W87-Dqn7j_x6QtR6sUjycJR7nQLBqT" rel="noopener noreferrer"&gt;playlist&lt;/a&gt; with TONS of detailed videos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Questions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Find the missing number in the array&lt;/li&gt;
&lt;li&gt;Determine if the sum of two integers is equal to the given value&lt;/li&gt;
&lt;li&gt;Merge two sorted linked lists&lt;/li&gt;
&lt;li&gt;Copy linked list with arbitrary pointer&lt;/li&gt;
&lt;li&gt;Copy linked list with arbitrary pointer&lt;/li&gt;
&lt;li&gt;(system design) Design a restaurant listing application where user can make orders and customize their orders by adding additional stuffs like toppings, salads etc. (&lt;a href="https://leetcode.com/discuss/interview-question/1984996/Amazon-Virtual-Onsite-April-2022-FrontEnd-Engineer-II-(L5)Vancouver-Offer/1528383" rel="noopener noreferrer"&gt;example&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;(system design) Design an accordion component&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;LeetCode Questions: &lt;a href="https://www.glassdoor.com/Interview/Amazon-Interview-Questions-E6036.htm" rel="noopener noreferrer"&gt;Amazon Interview Questions (2024) | Glassdoor&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LeetCode Questions: &lt;a href="https://wuyaheng.github.io/Amazon-Tagged-Leetcode-Questions/" rel="noopener noreferrer"&gt;Amazon-Tagged Leetcode Questions (wuyaheng.github.io)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Design Questions: &lt;a href="https://www.frontendinterviewhandbook.com/companies/amazon-front-end-interview-questions" rel="noopener noreferrer"&gt;Amazon Front End Interview Questions | The Official Front End Interview Handbook 2024&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Article: &lt;a href="https://www.educative.io/blog/crack-amazon-coding-interview-questions" rel="noopener noreferrer"&gt;https://www.educative.io/blog/crack-amazon-coding-interview-questions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Video Playlist (Frontend System Design): &lt;a href="https://www.youtube.com/playlist?list=PLI9W87-Dqn7j_x6QtR6sUjycJR7nQLBqT" rel="noopener noreferrer"&gt;https://www.youtube.com/playlist?list=PLI9W87-Dqn7j_x6QtR6sUjycJR7nQLBqT&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Soft Skills &amp;amp; Behavioral Questions
&lt;/h2&gt;

&lt;p&gt;Each interview will include a behavioral question relating to one of the 16 LPs. You don’t have to be an expert, but take some time to learn &amp;amp; familiarize yourself with them.&lt;/p&gt;

&lt;p&gt;My advice would be to prepare 1-2 stories per LP before the interview, as you will need to share concise answers on the spot. I’d recommend jotting down keywords for each story to reference. These can be related to work experience, internships, school projects, personal projects, volunteering, extracurriculars, or any situation where you demonstrated leadership, problem-solving, or collaboration. It doesn't have to be strictly work-related.&lt;br&gt;
To tell a concise/structured story, you can use the &lt;a href="https://interviewsteps.com/blogs/news/amazon-star-method" rel="noopener noreferrer"&gt;STAR framework&lt;/a&gt;, or slightly adapt it. STAR helps to give clearer context and outcomes.&lt;/p&gt;

&lt;p&gt;An important theme is that technical skills can easily be taught, soft skills cannot. A valuable candidate has great personality and team collaboration skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Questions
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Why Amazon? (watch an example answer from an ex-Amazon interviewer)&lt;/li&gt;
&lt;li&gt;Tell me about a time you failed at work. What did you learn from it?&lt;/li&gt;
&lt;li&gt;Tell me about a challenge you faced. What was your role &amp;amp; the outcome?&lt;/li&gt;
&lt;li&gt;Tell me about a time you disagreed with a coworker/manager/decision&lt;/li&gt;
&lt;li&gt;Tell me about a time you had to work or make a decision quickly / under a tight deadline&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Article: &lt;a href="https://www.aboutamazon.com/about-us/leadership-principles" rel="noopener noreferrer"&gt;Amazon Leadership Principles&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Article: &lt;a href="https://igotanoffer.com/blogs/tech/amazon-behavioral-interview" rel="noopener noreferrer"&gt;Amazon Behavioral Interview Questions (+ answers, method) - IGotAnOffer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Article: &lt;a href="https://www.tryexponent.com/blog/how-to-nail-amazons-behavioral-interview-questions" rel="noopener noreferrer"&gt;Top Amazon Behavioral Interview Questions and Answers - Exponent&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Video: &lt;a href="https://www.youtube.com/watch?v=6p1m2nCE7jE&amp;amp;ab_channel=Exponent" rel="noopener noreferrer"&gt;Amazon Behavioral Interview Questions | Leadership Principles Explained&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In no particular order, here’s a list of more material I found useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Article: &lt;a href="https://interviewkickstart.com/blogs/companies/amazon-front-end-engineer-interview-prep?utm_source=spencerlephinemedium&amp;amp;utm_medium=spencerlephinemedium" rel="noopener noreferrer"&gt;Amazon Front-End Engineer Interview Prep&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Article: &lt;a href="https://xjamundx.medium.com/understanding-amazons-front-end-engineering-interview-5e9f38b58058" rel="noopener noreferrer"&gt;Understanding Amazon’s Front-End Engineering Interview&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Article: &lt;a href="https://xjamundx.medium.com/how-i-got-a-front-end-engineering-job-at-amazon-807e26c33915" rel="noopener noreferrer"&gt;How I Landed a Front-End Engineering Job at Amazon&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Video: &lt;a href="https://www.youtube.com/watch?v=gTIS4waIpG4&amp;amp;ab_channel=CodePhony" rel="noopener noreferrer"&gt;Rejected by Amazon - Frontend and Software Engineer - Full Loop Interview Exp with no degree at all&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Video: &lt;a href="https://www.youtube.com/watch?v=rMWDtxJQIbQ" rel="noopener noreferrer"&gt;Amazon Front End Interview Prep | Software Engineer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Video: &lt;a href="https://www.youtube.com/watch?v=jI4WfkudBb8" rel="noopener noreferrer"&gt;Amazon Frontend Interview Experience 2022 | Frontend Engineer 2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Video: &lt;a href="https://www.youtube.com/watch?v=baT3OzbOg5s&amp;amp;ab_channel=KeepOnCoding" rel="noopener noreferrer"&gt;Amazon Interview Experience | Software Engineer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LeetCode Post: &lt;a href="https://leetcode.com/discuss/interview-question/694045/amazon-virtual-onsite-frontend-engineer-ii-offer" rel="noopener noreferrer"&gt;Amazon Virtual Onsite - FrontEnd Engineer II Offer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google search: &lt;a href="https://www.google.com/search?q=reddit+amazon+front+end+engineer" rel="noopener noreferrer"&gt;“reddit amazon front end engineer”&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🚀 &lt;strong&gt;Nail Your Tech Interviews with FAANG+ Experts.&lt;/strong&gt; Get 20% off on Mock Interviews.&lt;br&gt;&lt;br&gt;
Mock interviews and thorough feedback to help you land your dream tech role faster.&lt;br&gt;&lt;br&gt;
Trusted by over 25,000 experienced tech professionals.&lt;br&gt;&lt;br&gt;
&lt;a href="https://learn.interviewkickstart.com/ace-your-mock-interview?utm_source=spencerlephinemedium&amp;amp;utm_medium=spencerlephinemedium&amp;amp;utm_campaign=spencerlephinemedium" rel="noopener noreferrer"&gt;Start Practicing Now&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;&lt;em&gt;TL;DR - practice LeetCode medium, brush up on vanilla JavaScript and CSS fundamentals, verbalize your thought process, prepare stories for each LP, and be confident.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This covers just about everything I used to prepare for my FEE interview. Keep in mind, my interviews were fully remote, and this is biased to my experience for an entry level position.&lt;/p&gt;

&lt;p&gt;I hope you found this article useful! If you have any questions and would like to connect, you can find me over on &lt;a href="https://linkedin.com/in/spencerlepine" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; or shoot me an &lt;a href="//mailto:spencer.sayhello@gmail.com"&gt;email&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>interview</category>
      <category>amazon</category>
    </item>
  </channel>
</rss>
