<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ Heroku - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ Heroku - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Tue, 16 Jun 2026 05:48:07 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/heroku/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ Top Heroku Alternatives for Deployment in 2026 ]]>
                </title>
                <description>
                    <![CDATA[ For more than a decade, Heroku defined what “developer-friendly deployment” meant. Push code, forget servers, and focus on shipping features. That promise shaped an entire generation of platform-as-a-service products. In 2026, that landscape is chang... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/top-heroku-alternatives-for-deployment/</link>
                <guid isPermaLink="false">698cfce6afbd03d112127750</guid>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Manish Shivanandhan ]]>
                </dc:creator>
                <pubDate>Wed, 11 Feb 2026 22:04:22 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1770847448732/3783e969-2001-48bd-8ad9-4fbe46d3b8fd.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>For more than a decade, <a target="_blank" href="https://www.heroku.com/">Heroku</a> defined what “developer-friendly deployment” meant. Push code, forget servers, and focus on shipping features.</p>
<p>That promise shaped an entire generation of platform-as-a-service products. In 2026, that landscape is changing.</p>
<p>Heroku has <a target="_blank" href="https://www.heroku.com/blog/an-update-on-heroku/">clearly stated</a> that it’s moving into a sustaining engineering model. The platform remains stable, secure, and supported, but active innovation is no longer the focus.</p>
<p>For many teams, this is acceptable. For others, especially startups and product teams planning three to five years ahead, it raises an important question: where should new applications live?</p>
<p>In this article, we’ll look at five strong Heroku alternatives that are well-positioned for 2026. Each platform approaches deployment differently, but all aim to preserve what developers loved about Heroku while improving on cost, flexibility, or modern workflows.</p>
<h2 id="heading-what-well-cover">What We’ll Cover</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-why-teams-are-looking-beyond-heroku">Why Teams Are Looking Beyond Heroku</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-sevalla-the-closest-successor-to-classic-heroku">Sevalla: The Closest Successor to Classic Heroku</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-render-a-broad-platform-for-growing-teams">Render: A Broad Platform for Growing Teams</a></p>
</li>
<li><p><a target="_blank" href="http://Fly.io">Fly.io</a><a class="post-section-overview" href="#heading-flyio-global-first-deployment-for-latency-sensitive-apps">: Global-First Deployment for Latency-Sensitive Apps</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-upsun-enterprise-grade-control-without-losing-structure">Upsun: Enterprise-Grade Control Without Losing Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-vercel-the-frontend-native-deployment-platform">Vercel: The Frontend-Native Deployment Platform</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-choosing-the-right-heroku-alternative-in-2026">Choosing the Right Heroku Alternative in 2026</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-why-teams-are-looking-beyond-heroku">Why Teams Are Looking Beyond Heroku</h2>
<p>Heroku’s shift toward maintenance over expansion signals maturity, not failure. But modern teams expect faster iteration, deeper infrastructure control, and tighter integration with cloud-native tooling.</p>
<p>AI workloads, edge computing, and global latency expectations are also reshaping deployment needs.</p>
<p>As a result, teams may want platforms that feel simple on day one but don’t become limiting as scale and complexity grow.</p>
<p>The alternatives discussed here are not identical replacements. Each represents a different philosophy about how applications should be built and operated in 2026.</p>
<h2 id="heading-sevalla-the-closest-successor-to-classic-heroku">Sevalla: The Closest Successor to Classic Heroku</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770718544630/4e5953ac-1bd7-4c38-9274-bd9228e79d1c.jpeg" alt="Sevalla" class="image--center mx-auto" width="1280" height="720" loading="lazy"></p>
<p><a target="_blank" href="https://sevalla.com/heroku-alternative/">Sevalla</a> has quietly positioned itself as one of the most Heroku-like platforms available today. The core idea is familiar. You deploy applications without managing servers, environments are predictable, and the platform stays out of your way.</p>
<p>What makes Sevalla compelling in 2026 is its balance between simplicity and control. It keeps the developer experience tight while avoiding the opaque pricing and rigid abstractions that frustrated many Heroku users over time. Deployments are fast, logs are easy to access, and scaling feels intuitive rather than magical.</p>
<p>Sevalla is particularly attractive for mid-sized teams as well as enterprises that want a clean path from prototype to production. It supports modern application stacks without forcing you into complex infrastructure decisions too early. For teams migrating directly from Heroku, Sevalla often feels like the least disruptive transition.</p>
<p>The platform’s biggest strength is restraint. It doesn’t try to be everything at once. Instead, it focuses on being a reliable home for long-running services, APIs, and background workers. In 2026, that clarity is refreshing.</p>
<p>Built for the enterprise, Sevalla meets the highest standards of security. They are fully compliant with SOC2, ISO 27017, and ISO 27001:2022, ensuring your data stays protected and your requirements are met.</p>
<h2 id="heading-render-a-broad-platform-for-growing-teams">Render: A Broad Platform for Growing Teams</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770718568758/6682062c-de84-4d42-bbcb-3e4312deb76f.png" alt="Render" class="image--center mx-auto" width="1600" height="900" loading="lazy"></p>
<p><a target="_blank" href="https://render.com/docs/render-vs-heroku-comparison">Render</a> takes a more expansive approach. While it’s often compared to Heroku, Render aims to cover a wider range of use cases, from simple web services to complex microservice architectures.</p>
<p>Render stands out because it blends platform simplicity with infrastructure transparency. You still get managed databases, background jobs, and zero-downtime deploys, but you also gain more visibility into how resources are allocated. This makes it easier to reason about cost and performance as systems grow.</p>
<p>For teams that expect to scale steadily, Render offers a comfortable middle ground. It removes much of the operational burden while allowing deeper configuration when needed. Many engineering teams appreciate that Render feels less restrictive than Heroku without pushing them into full DevOps territory.</p>
<p>In 2026, Render is especially popular with SaaS companies that have outgrown entry-level platforms but are not ready to manage Kubernetes clusters themselves. It supports modern CI/CD workflows and integrates well with common developer tools.</p>
<h2 id="heading-flyio-global-first-deployment-for-latency-sensitive-apps">Fly.io: Global-First Deployment for Latency-Sensitive Apps</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770718605158/a6480909-d1a8-49f4-836f-da212f37700d.jpeg" alt="Fly.io" class="image--center mx-auto" width="1000" height="500" loading="lazy"></p>
<p><a target="_blank" href="https://getdeploying.com/flyio-vs-heroku">Fly.io</a> represents a different philosophy entirely. Instead of abstracting infrastructure away, Fly.io embraces it, but makes it programmable and developer-friendly.</p>
<p>Fly.io allows applications to run close to users by deploying workloads across multiple regions by default. This makes it ideal for applications where latency matters, such as real-time collaboration tools, gaming backends, or global APIs.</p>
<p>Unlike Heroku, Fly.io expects developers to understand a bit more about how their application runs. You interact with virtual machines rather than dynos, and configuration is more explicit. However, this added complexity comes with real power.</p>
<p>In 2026, Fly.io appeals strongly to experienced teams that want performance and control without adopting heavy orchestration systems. It’s not always the easiest option, but it is one of the most flexible. For teams willing to invest in understanding the platform, Fly.io can outperform traditional PaaS solutions in both speed and cost efficiency.</p>
<h2 id="heading-upsun-enterprise-grade-control-without-losing-structure">Upsun: Enterprise-Grade Control Without Losing Structure</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770718618266/aa584c8c-3ac2-4a39-a570-755c21783b2a.png" alt="Upsun" class="image--center mx-auto" width="1200" height="630" loading="lazy"></p>
<p><a target="_blank" href="https://upsun.com/blog/a-heroku-alternative/">Upsun</a>, previously known as Platform.sh, brings a more opinionated, enterprise-oriented model to application deployment. It’s designed for teams that care deeply about environment parity, reproducibility, and long-term maintainability.</p>
<p>Upsun treats infrastructure as part of the application. Environments are versioned alongside code, and deployments are deterministic. This approach reduces surprises and makes complex systems easier to reason about over time.</p>
<p>For organizations with compliance requirements or multi-environment workflows, Upsun offers a level of rigor that Heroku never aimed to provide. At the same time, it abstracts away much of the operational burden that typically comes with such control.</p>
<p>In 2026, Upsun is particularly well-suited for regulated industries, large content platforms, and teams with multiple long-lived environments. It’s less about rapid experimentation and more about predictable, repeatable delivery at scale.</p>
<h2 id="heading-vercel-the-frontend-native-deployment-platform">Vercel: The Frontend-Native Deployment Platform</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770718632237/e6a0725d-151a-4a73-9f61-cf5d1550b043.webp" alt="Vercel" class="image--center mx-auto" width="672" height="428" loading="lazy"></p>
<p><a target="_blank" href="https://vercel.com/">Vercel</a> is often discussed in a different category, but it deserves inclusion in any modern deployment conversation. Vercel is optimized for frontend applications, serverless functions, and edge workloads.</p>
<p>If Heroku excelled at hosting monolithic web apps, Vercel excels at composable, frontend-driven architectures. It integrates deeply with modern frameworks and makes global deployment nearly effortless.</p>
<p>In 2026, many applications are frontend-heavy, with APIs split into smaller services or serverless functions. For these use cases, Vercel offers a developer experience that feels faster and more modern than traditional PaaS platforms.</p>
<p>However, Vercel is not a full replacement for Heroku in every scenario. Long-running background jobs and stateful services often live elsewhere. Still, for teams building modern web products, Vercel frequently becomes the centerpiece of their deployment strategy.</p>
<h2 id="heading-choosing-the-right-heroku-alternative-in-2026">Choosing the Right Heroku Alternative in 2026</h2>
<p>There is no single “best” Heroku replacement. The right choice depends on how your application behaves, how your team works, and how much control you want over infrastructure.</p>
<p>Sevalla is ideal for teams that want familiarity and minimal friction. Render suits growing teams that need flexibility without chaos. Fly.io is powerful for global, performance-sensitive systems. Upsun excels in structured, enterprise environments. Vercel dominates frontend-centric architectures.</p>
<p>The common thread is that deployment in 2026 is no longer one-size-fits-all. Heroku set the standard, but the ecosystem has evolved. Today’s platforms offer sharper trade-offs, clearer philosophies, and better alignment with modern development patterns.</p>
<p>For teams starting new projects, the opportunity is clear. You can choose a platform that matches your future, not just your present.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Heroku is not disappearing, and for many existing workloads, it will continue to run reliably for years. However, its shift toward a sustaining engineering model makes one thing clear: teams building new products in 2026 should think carefully about where they place their long-term bets.</p>
<p>Deployment platforms are no longer just hosting choices. They shape how fast teams move, how systems scale, and how painful future migrations become.</p>
<p>In 2026, the strongest deployment strategy is intentional, not inherited. Heroku showed the industry what was possible. Its successors are now defining what comes next.</p>
<p><em>Hope you enjoyed this article. Learn more about me by</em> <a target="_blank" href="https://manishshivanandhan.com/"><strong><em>visiting my website</em></strong></a><em>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Simple Deployment Pipeline with Reusable Github Actions and Heroku ]]>
                </title>
                <description>
                    <![CDATA[ By Liz Johnson If you've been using GitHub for a while, you've probably heard of or used GitHub actions.  If you haven't heard of Github Actions or used them before, you can use them for automating your build, test, or deployment pipelines. You can c... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-simple-deployment-pipeline-with-reuable-github-actions-and-heroku/</link>
                <guid isPermaLink="false">66d4601247a8245f78752a7f</guid>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub Actions ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 31 May 2023 17:25:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/AdobeStock_131006414-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Liz Johnson</p>
<p>If you've been using GitHub for a while, you've probably heard of or used GitHub actions. </p>
<p>If you haven't heard of Github Actions or used them before, you can use them for automating your build, test, or deployment pipelines. You can create workflows that will be triggered upon certain actions such as opening a pull request or pushing to a branch.  </p>
<p>These actions are useful to create build pipelines that automate deployments. They also help maintain the integrity of branches by running tests on all pushes/pull-requests.</p>
<p>Here is a simple workflow that would run tests whenever you push branches or open pull-requests in Github:</p>
<pre><code>---
name: Run tests
<span class="hljs-attr">on</span>: [push, pull_request]

<span class="hljs-attr">jobs</span>:
  test:
    runs-on: ubuntu-latest

    <span class="hljs-attr">steps</span>:
      - name: Checkout
        <span class="hljs-attr">uses</span>: actions/checkout@v2

      - name: Setup and Run Tests
        <span class="hljs-attr">run</span>:  |
          docker-compose build
          docker-compose run web rake db:setup
          docker-compose run web rspec
</code></pre><p>The trigger is listed after the "on" tag. This workflow is triggered on pushes to the remote. Below you will see examples where there are multiple triggers for the workflow. If you have multiple triggers you can list the triggers in brackets like an array.  </p>
<p>Next, you have your jobs tag. Under this tag you can list the various jobs you want to run. You may have a few different test jobs for unit tests, integration tests, and then perhaps a build job to build an image that gets pushed to a remote repository.  </p>
<p>Within the job you have various steps. The first step is usually the checkout step. GitHub actions will spin up a virtual machine runner to run your jobs in, so you will want to include all the steps you need to set up this virtual machine for your application. </p>
<p>This means the first thing you'll want to do is get the code onto the virtual machine. This happens with the checkout step above. </p>
<p>Then your job needs give GitHub instructions on how to run things like the tests. The workflow above is running everything through Docker. The GitHub runner can see the docker-compose file when it checks out the project. Then it can run the three Docker steps listed above to spin up a container and then run the unit tests inside that container.  </p>
<p>With this workflow if you were to open a pull-request in GitHub and the branch had failing tests, you'd get an alert telling you that your introducing breaking changes with an output like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-189.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sample GitHub Actions alert</em></p>
<p>These workflows can sometimes become complex and very involved. In this tutorial, I will explain some simple ways to cleanup your workflows to avoid copy-pasting yaml configurations.  </p>
<p>I will then go on to explain how you can create a simple deployment pipeline that enforces that one job passes before the other one runs.</p>
<h2 id="heading-where-you-might-find-duplicate-jobs">Where You Might Find Duplicate Jobs</h2>
<p>Say you have a job that runs your tests, and that job needs to be run in multiple different workflows. You want to run tests on all pull-requests. You also want to run them on merges to main in a way that blocks the production build/deploy step if the tests don’t pass.</p>
<p>Perhaps your first idea here is to build two workflows, one named test and the other named deploy. The test workflow would have one job: to set up and run all unit tests. In the other workflow you can copy the yaml from the test job in the test workflow and paste it as the first job in your deploy workflow. </p>
<p>Your test workflow would look something like this:</p>
<pre><code>---
name: Run tests
<span class="hljs-attr">on</span>: pull-request

<span class="hljs-attr">jobs</span>:
  test:
    runs-on: ubuntu-latest

    <span class="hljs-attr">steps</span>:
      - name: Checkout
        <span class="hljs-attr">uses</span>: actions/checkout@v2

      - name: Setup and Run Tests
        <span class="hljs-attr">run</span>:  |
          docker-compose build
          docker-compose run web rake db:setup
          docker-compose run web rspec
</code></pre><p>And your deployment workflow would look something like this:</p>
<pre><code>---
name: Deploy
<span class="hljs-attr">on</span>:
  push:
    branches: [main]

<span class="hljs-attr">jobs</span>:
  tests:
      runs-on: ubuntu-latest

    <span class="hljs-attr">steps</span>:
      - name: Checkout
        <span class="hljs-attr">uses</span>: actions/checkout@v2

      - name: Setup and Run Tests
        <span class="hljs-attr">run</span>:  |
          docker-compose build
          docker-compose run web rake db:setup
          docker-compose run web rspec
  <span class="hljs-attr">deploy</span>:
    name: deploy
    runs-on: ubuntu-latest
    <span class="hljs-attr">steps</span>:
      - run: echo <span class="hljs-string">'The triggering workflow succeeded deploying now'</span>
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3<span class="hljs-number">.12</span><span class="hljs-number">.13</span> # This is the action
        <span class="hljs-attr">with</span>:
          usedocker: <span class="hljs-literal">true</span>
</code></pre><p>The deploy workflow allows both jobs (test and deploy) to run on merges to main, but it actually won’t block the deploy job from running if the tests fail. </p>
<p>But we can make this better in two ways: the first is by using reusable GitHub actions so that we can eliminate the copy pasting of the job yaml. Second, is using the GitHub job “needs” keyword so that we can make our deploy job depend on our test job succeeding.</p>
<p>I built this out <a target="_blank" href="https://github.com/lizzypy/base-app-liz">here</a> and will be going through that example. </p>
<h2 id="heading-how-to-create-reusable-github-actions">How to Create Reusable Github Actions</h2>
<p>Github has <a target="_blank" href="https://github.blog/2022-02-10-using-reusable-workflows-github-actions/">a blog post about reusable actions</a> that I recommend. It goes a bit further than I will go here. But the important information for us is the explanation of what a reusable action is.  A reusable action is one where you create a job in one place and then call it a separate workflow.</p>
<p>If I wanted my test workflow to be re-useable, I'd need to add a trigger labeled "workflow_call".  I also want my workflow triggered on push and pull-requests. So my triggers would look something like this:</p>
<pre><code>---
name: Run CI Process <span class="hljs-keyword">for</span> the app
<span class="hljs-attr">on</span>: [workflow_call, push, pull_request, workflow_dispatch]
</code></pre><p>And the full workflow would look like this:</p>
<pre><code>---
name: Run tests
<span class="hljs-attr">on</span>: [workflow_call, push, pull_request, workflow_dispatch]


<span class="hljs-attr">jobs</span>:
  test:
    runs-on: ubuntu-latest

    <span class="hljs-attr">steps</span>:
      - name: Checkout
        <span class="hljs-attr">uses</span>: actions/checkout@v2

      - name: Setup and Run Tests
        <span class="hljs-attr">run</span>:  |
          docker-compose build
          docker-compose run web rake db:setup
          docker-compose run web rspec
</code></pre><p>To reuse the test workflow in a deploy workflow (where I want to run tests and deploy my application) I could do something like the following:</p>
<pre><code>jobs:
    test:
        uses:./.github/workflows/test.yml
</code></pre><p>This would allow the test job to be run in a separate workflow without having to copy the yaml associated with the test job from one workflow to another.</p>
<h2 id="heading-dependent-jobs">Dependent Jobs</h2>
<p>That’s cool, but we don’t just want our test job to be reused in our deploy workflow. If the test job happens to fail in the deploy workflow, we actually want that to <em>block</em> deployment.</p>
<p>Now, we can look at the “needs” keyword which is documented <a target="_blank" href="https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions">here</a>. Using “needs” we can require that the deploy job won’t even run unless the test job is successful.</p>
<p>Before I jump into using the "needs" tag, let me briefly explain what the deployment job is doing. </p>
<p>I have deployed the example app using Heroku. Instead of using the Heroku Git remote, I open pull-requests against a main branch. On merges to main I use this <a target="_blank" href="https://github.com/AkhileshNS/heroku-deploy">open source Github action</a> to deploy the main branch to Heroku.  </p>
<p>My deploy job will look something like this:</p>
<pre><code>deploy:
    name: deploy
    runs-on: ubuntu-latest
    <span class="hljs-attr">steps</span>:
      - run: echo <span class="hljs-string">'The triggering workflow succeeded deploying now'</span>
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3<span class="hljs-number">.12</span><span class="hljs-number">.13</span> # This is the action
        <span class="hljs-attr">with</span>: 
            usedocker: <span class="hljs-literal">true</span>
</code></pre><p>The "usedocker" tag above specifies that I want to deploy this application to Heroku by building a pushing a docker image to the Heroku Container Registry. If you look through the source code for the Heroku deploy action that is referenced above, you’ll see that when I set "usedocker" to "true" it will run this command:</p>
<p><code>heroku container:push</code></p>
<p>That can be seen <a target="_blank" href="https://github.com/AkhileshNS/heroku-deploy/blob/master/index.js#L76">here</a>.</p>
<p>If we want this job to require the a successful test run before we run the deploy job, we can add a test job to our workflow that references our reusable test job that we created:</p>
<pre><code>---
name: Deploy
<span class="hljs-attr">on</span>:
  push:
    branches: [main]

<span class="hljs-attr">jobs</span>:
  tests:
    uses: ./.github/workflows/test.yml
</code></pre><p>Now we can add the <code>needs</code> tag to our deployment action and our full workflow yaml will look something like this:</p>
<pre><code>---
name: Deploy
<span class="hljs-attr">on</span>:
  push:
    branches: [main]

<span class="hljs-attr">jobs</span>:
  tests:
    uses: ./.github/workflows/test.yml
  <span class="hljs-attr">deploy</span>:
    name: deploy
    <span class="hljs-attr">needs</span>: [ tests ]
    runs-on: ubuntu-latest
    <span class="hljs-attr">steps</span>:
      - run: echo <span class="hljs-string">'The triggering workflow succeeded deploying now'</span>
      - uses: actions/checkout@v2
      - uses: akhileshns/heroku-deploy@v3<span class="hljs-number">.12</span><span class="hljs-number">.13</span> # This is the action
        <span class="hljs-attr">with</span>:
          usedocker: <span class="hljs-literal">true</span>
</code></pre><p>And that's it! Now when we merge branches to main, GitHub gives us a visual of our dependent jobs that looks like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/05/image-159.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If we had a more complex workflow we could see exactly which job it is failing on. </p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>With these steps, we can clean up our workflow yamls to reference existing jobs/workflows when possible. We can also build a simple pipeline of dependent actions where one steps depends on the success of a previous step.  </p>
<p>These are the beginnings of CI/CD pipeline that can allow for frequent deploys. In turn it will allow us to get new features and fixes to users faster.  </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build and Deploy a Backend App with Express, Postgres, Github, and Heroku ]]>
                </title>
                <description>
                    <![CDATA[ By Njoku Samson Ebere In this tutorial, we will be learning how to build and deploy an image management application backend.  It will be able to store a record of an image in the database, get the image's record back from the database, update the rec... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-backend-application/</link>
                <guid isPermaLink="false">66d84fa05a0b456f4d321471</guid>
                
                    <category>
                        <![CDATA[ Back end development  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 03 Mar 2022 20:46:30 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/03/pexels-pixabay-417273.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Njoku Samson Ebere</p>
<p>In this tutorial, we will be learning how to build and deploy an image management application backend. </p>
<p>It will be able to store a record of an image in the database, get the image's record back from the database, update the record, and even delete the record completely as the case may be.</p>
<p>To achieve all of this, we will be using Express (a Node.js framework), Postgres (a database), Cloudinary (a cloud based image storage), GitHub (for version control/storage) and Heroku (a hosting platform).</p>
<p>These tools are all free. So you don’t have to bother about how to go about paying for them. Thanks to these great innovators.</p>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>If you are new to most of these technologies, I would advise you go through my other tutorial on how to <a target="_blank" href="https://www.freecodecamp.org/news/build-a-secure-server-with-node-and-express/">create a server and upload images to Cloudinary</a>.</p>
<p>If you are totally to Postgres, then check out this <a target="_blank" href="https://dev.to/ogwurujohnson/-persisting-a-node-api-with-postgresql-without-the-help-of-orms-like-sequelize-5dc5">tutorial</a>.</p>
<p>Whenever you are ready, let’s get to work!</p>
<h2 id="heading-how-to-store-and-retrieve-an-image-record">How to Store and Retrieve an Image Record</h2>
<h3 id="heading-create-database-and-table">Create Database and Table</h3>
<p>So you'll want to start by cloning this <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/cloudinary-upload">project</a> if you don't already have it.</p>
<p>In your <strong>pgAdmin</strong>:</p>
<ul>
<li>Create a database and name it <code>tutorial</code></li>
<li>Create a table and name it <code>tutorial</code></li>
<li>Create a Login/Group Role and name it <code>tutorial</code>. <strong>(Do not forget to give it all privileges.)</strong></li>
</ul>
<p>Back in your project directory, install the <a target="_blank" href="https://node-postgres.com/">node-postgres</a> (<code>npm i pg</code>) and <a target="_blank" href="https://www.npmjs.com/package/make-runnable">make-runnnable</a> (<code>npm i make-runnable</code>) packages.</p>
<p>In your <code>package.json</code> file, replace the contents of the <code>"scripts"</code> with <code>"create": "node ./services/dbConnect createTables"</code>. We will use this to execute the <code>dbConnect</code> file we are about to create.</p>
<p>Create a <code>services/dbConnect</code> file to contain the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> pg = <span class="hljs-built_in">require</span>(<span class="hljs-string">"pg"</span>);

<span class="hljs-keyword">const</span> config = {
  <span class="hljs-attr">user</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">database</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">port</span>: <span class="hljs-number">5432</span>,
  <span class="hljs-attr">max</span>: <span class="hljs-number">10</span>, <span class="hljs-comment">// max number of clients in the pool</span>
  <span class="hljs-attr">idleTimeoutMillis</span>: <span class="hljs-number">30000</span>,
};

<span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> pg.Pool(config);

pool.on(<span class="hljs-string">"connect"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"connected to the Database"</span>);
});

<span class="hljs-keyword">const</span> createTables = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> imageTable = <span class="hljs-string">`CREATE TABLE IF NOT EXISTS
    images(
      id SERIAL PRIMARY KEY,
      title VARCHAR(128) NOT NULL,
      cloudinary_id VARCHAR(128) NOT NULL,
      image_url VARCHAR(128) NOT NULL
    )`</span>;
  pool
    .query(imageTable)
    .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(res);
      pool.end();
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(err);
      pool.end();
    });
};

pool.on(<span class="hljs-string">"remove"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"client removed"</span>);
  process.exit(<span class="hljs-number">0</span>);
});

<span class="hljs-comment">//export pool and createTables to be accessible from anywhere within the application</span>
<span class="hljs-built_in">module</span>.exports = {
  createTables,
  pool,
};

<span class="hljs-built_in">require</span>(<span class="hljs-string">"make-runnable"</span>);
</code></pre>
<p>Now we are all set to create the table in our database. If you are ready, let's rock and roll!</p>
<p>Execute the following code in your terminal:</p>
<pre><code class="lang-javascript">
  npm run create
</code></pre>
<p>If the image below is your result, then you are good to go:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/fjk5ty113j5p7kaxveqc.JPG" alt="Alt Text" width="697" height="726" loading="lazy"></p>
<p>Check your <strong>pgAdmin</strong>, and you should have your table seated properly in your database like in the image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/sj7gy6d86vm7ay3d8a74.JPG" alt="Alt Text" width="1366" height="694" loading="lazy"></p>
<p>Alright, it's been a long road. It's time to unite Node, Postgres, and Cloudinary.</p>
<h2 id="heading-how-to-create-endpoints-to-store-and-retrieve-image-records">How to Create Endpoints to Store and Retrieve Image Records</h2>
<h3 id="heading-endpoint-1-persist-image">Endpoint 1: Persist Image</h3>
<p>First, require the <code>dbConnect.js</code> file on the top of the <code>app.js</code> file like so:</p>
<pre><code class="lang-javascript">  <span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">'services/dbConnect.js'</span>);
</code></pre>
<p>Then in the <code>app.js</code> file, make a new endpoint <em>(persist-image)</em> with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// persist image</span>
app.post(<span class="hljs-string">"/persist-image"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">title</span>: request.body.title,
    <span class="hljs-attr">image</span>: request.body.image,
  }

  <span class="hljs-comment">// upload image here</span>
  cloudinary.uploader.upload(data.image)
  .then().catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
    response.status(<span class="hljs-number">500</span>).send({
      <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
      error,
    });
  });
})
</code></pre>
<p>Replace the <code>then</code> block with the following code:</p>
<pre><code class="lang-javascript">
.then(<span class="hljs-function">(<span class="hljs-params">image</span>) =&gt;</span> {
    db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
      <span class="hljs-comment">// inset query to run if the upload to cloudinary is successful</span>
      <span class="hljs-keyword">const</span> insertQuery = <span class="hljs-string">'INSERT INTO images (title, cloudinary_id, image_url) 
         VALUES($1,$2,$3) RETURNING *'</span>;
      <span class="hljs-keyword">const</span> values = [data.title, image.public_id, image.secure_url];
    })
  })
</code></pre>
<p>The <code>image.public_id</code> and <code>image.secure_url</code> are gotten as part of the details returned for an image after the image has been successfully uploaded to Cloudinary. </p>
<p>We are now keeping a record of the <code>image.public_id</code> and <code>image.secure_url</code> (as you can see in the code above) in order to use it to retrieve, update, or delete the image record when we see fit. </p>
<p>Alright, let's move forward!</p>
<p>Still in the <code>then</code> block, add the following code under the query we created:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// execute query</span>
client.query(insertQuery, values)
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        result = result.rows[<span class="hljs-number">0</span>];

        <span class="hljs-comment">// send success response</span>
        response.status(<span class="hljs-number">201</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Uploaded Successfully"</span>,
            <span class="hljs-attr">title</span>: result.title,
            <span class="hljs-attr">cloudinary_id</span>: result.cloudinary_id,
            <span class="hljs-attr">image_url</span>: result.image_url,
          },
        })
      }).catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
          e,
        });
      })
</code></pre>
<p>So our <code>persist-image</code> endpoint now looks like this:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// persist image</span>
app.post(<span class="hljs-string">"/persist-image"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">title</span>: request.body.title,
    <span class="hljs-attr">image</span>: request.body.image
  }

  <span class="hljs-comment">// upload image here</span>
  cloudinary.uploader.upload(data.image)
  .then(<span class="hljs-function">(<span class="hljs-params">image</span>) =&gt;</span> {
    db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
      <span class="hljs-comment">// inset query to run if the upload to cloudinary is successful</span>
      <span class="hljs-keyword">const</span> insertQuery = <span class="hljs-string">'INSERT INTO images (title, cloudinary_id, image_url) 
         VALUES($1,$2,$3) RETURNING *'</span>;
      <span class="hljs-keyword">const</span> values = [data.title, image.public_id, image.secure_url];

      <span class="hljs-comment">// execute query</span>
      client.query(insertQuery, values)
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        result = result.rows[<span class="hljs-number">0</span>];

        <span class="hljs-comment">// send success response</span>
        response.status(<span class="hljs-number">201</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Uploaded Successfully"</span>,
            <span class="hljs-attr">title</span>: result.title,
            <span class="hljs-attr">cloudinary_id</span>: result.cloudinary_id,
            <span class="hljs-attr">image_url</span>: result.image_url,
          },
        })
      }).catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
          e,
        });
      })
    })  
  }).catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
    response.status(<span class="hljs-number">500</span>).send({
      <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
      error,
    });
  });
});
</code></pre>
<p><strong>Now let's test out all our hard work:</strong></p>
<p>Open your <em>postman</em> and test out your endpoint like the image below. Mine was successful. Hope yours had no errors too?</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/euxsoyb821v85uv5rhml.JPG" alt="Alt Text" width="1351" height="720" loading="lazy"></p>
<p>Open your Cloudinary console/dashboard and check your <code>media Library</code>. Your new image should be sitting there comfortably like mine below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/dxy5fl8fodqpn89oltzh.JPG" alt="Alt Text" width="1366" height="735" loading="lazy"></p>
<p>And now to the main reason why we are here: check the <code>images</code> table in your <strong>pgAdmin</strong>. Mine is what you see below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ypv6vcrocsgp13owq5dv.JPG" alt="Alt Text" width="1366" height="693" loading="lazy"></p>
<p>Oohlala! We made it this far. Please take a break if you need one. I will be here waiting when you return. :)</p>
<p>If you are ready, then let's retrieve the image we persisted a moment ago.</p>
<h3 id="heading-endpoint-2-retrieve-image">Endpoint 2: Retrieve Image</h3>
<p>Start with this code:</p>
<pre><code class="lang-javascript">
app.get(<span class="hljs-string">"/retrieve-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {

});
</code></pre>
<p>Next, we will need to collect a unique ID from the user to retrieve a particular image. So add <code>const { id } = request.params;</code> to the code above like so:</p>
<pre><code class="lang-javascript">
app.get(<span class="hljs-string">"/retrieve-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// data from user</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

});
</code></pre>
<p>Add the following code just below the code above:</p>
<pre><code class="lang-javascript">
db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
      <span class="hljs-comment">// query to find image</span>
    <span class="hljs-keyword">const</span> query = <span class="hljs-string">"SELECT * FROM images WHERE cloudinary_id = $1"</span>;
    <span class="hljs-keyword">const</span> value = [cloudinary_id];
    });
</code></pre>
<p>Under the query, execute the query with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// execute query</span>
    client
      .query(query, value)
      .then(<span class="hljs-function">(<span class="hljs-params">output</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">id</span>: output.rows[<span class="hljs-number">0</span>].cloudinary_id,
            <span class="hljs-attr">title</span>: output.rows[<span class="hljs-number">0</span>].title,
            <span class="hljs-attr">url</span>: output.rows[<span class="hljs-number">0</span>].image_url,
          },
        });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">401</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"failure"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">message</span>: <span class="hljs-string">"could not retrieve record!"</span>,
            error,
          },
        });
      });
</code></pre>
<p>Now our <code>retrieve-image</code> API looks like this:</p>
<pre><code class="lang-javascript">
app.get(<span class="hljs-string">"/retrieve-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// data from user</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

  db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
    <span class="hljs-comment">// query to find image</span>
    <span class="hljs-keyword">const</span> query = <span class="hljs-string">"SELECT * FROM images WHERE cloudinary_id = $1"</span>;
    <span class="hljs-keyword">const</span> value = [cloudinary_id];

    <span class="hljs-comment">// execute query</span>
    client
      .query(query, value)
      .then(<span class="hljs-function">(<span class="hljs-params">output</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">id</span>: output.rows[<span class="hljs-number">0</span>].cloudinary_id,
            <span class="hljs-attr">title</span>: output.rows[<span class="hljs-number">0</span>].title,
            <span class="hljs-attr">url</span>: output.rows[<span class="hljs-number">0</span>].image_url,
          },
        });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">401</span>).send({
          <span class="hljs-attr">status</span>: <span class="hljs-string">"failure"</span>,
          <span class="hljs-attr">data</span>: {
            <span class="hljs-attr">message</span>: <span class="hljs-string">"could not retrieve record!"</span>,
            error,
          },
        });
      });
  });
});
</code></pre>
<p><strong>Let's see how well we did:</strong></p>
<p>In your postman, copy the <code>cloudinary_id</code> and add it to the URL like in the image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/9bmutxbmitgznqnnwmny.JPG" alt="Alt Text" width="1348" height="719" loading="lazy"></p>
<p>YEEESSS! We can also retrieve our image.</p>
<p>If you are here, then you deserve a round of applause and a standing ovation for your industriousness.</p>
<p>Congratulations! You just reached a great milestone.</p>
<p>The code for storing and retrieving image records is <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/create-APIs">here</a>.</p>
<h2 id="heading-how-to-update-and-delete-an-image-record">How to Update and Delete an Image Record</h2>
<p>We will now see how to delete and update an image record as the case maybe. Let's begin with the delete endpoint.</p>
<h3 id="heading-delete-endpoint">Delete Endpoint</h3>
<p>In the app.js file, start with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// delete image</span>
app.delete(<span class="hljs-string">"delete-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {

});
</code></pre>
<p>Next, we want to get the unique ID of the image we want to delete from the URL, that is <code>cloudinary_id</code>. So inside the code above add:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> { cloudinary_id } = request.params;
</code></pre>
<p>We now start the deleting process.</p>
<p>First, we delete from Cloudinary. Add the following code to delete the image from Cloudinary:</p>
<pre><code class="lang-javascript">
cloudinary.uploader
    .destroy(cloudinary_id)
    .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
      response.status(<span class="hljs-number">200</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span>,
        result,
      });
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      response.status(<span class="hljs-number">500</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Failure"</span>,
        error,
      });
    });
</code></pre>
<p>At this point, our API can delete the image from Cloudinary only (you can check it out in postman). But we also want to get rid of the record we have in our Postgres database.</p>
<p>Second, we delete from our Postgres database. To do so, replace the code in the <code>then</code> block with the following <code>query</code>:</p>
<pre><code class="lang-javascript">
db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {

      <span class="hljs-comment">// delete query</span>
      <span class="hljs-keyword">const</span> deleteQuery = <span class="hljs-string">"DELETE FROM images WHERE cloudinary_id = $1"</span>;
      <span class="hljs-keyword">const</span> deleteValue = [cloudinary_id];

})
</code></pre>
<p>Execute the query with the following code underneath it:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// execute delete query</span>
      client.query(deleteQuery, deleteValue)
      .then(<span class="hljs-function">(<span class="hljs-params">deleteResult</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Deleted Successfully!"</span>,
          deleteResult
        });
      }).catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Couldn't be Deleted!"</span>,
          e
        });
      });
</code></pre>
<p>So our Endpoint should look like this:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// delete image</span>
app.delete(<span class="hljs-string">"/delete-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// unique ID</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

  <span class="hljs-comment">// delete image from cloudinary first</span>
  cloudinary.uploader
    .destroy(cloudinary_id)

    <span class="hljs-comment">// delete image record from postgres also</span>
    .then(<span class="hljs-function">() =&gt;</span> {
      db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {

      <span class="hljs-comment">// delete query</span>
      <span class="hljs-keyword">const</span> deleteQuery = <span class="hljs-string">"DELETE FROM images WHERE cloudinary_id = $1"</span>;
      <span class="hljs-keyword">const</span> deleteValue = [cloudinary_id];

      <span class="hljs-comment">// execute delete query</span>
      client
        .query(deleteQuery, deleteValue)
        .then(<span class="hljs-function">(<span class="hljs-params">deleteResult</span>) =&gt;</span> {
          response.status(<span class="hljs-number">200</span>).send({
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Deleted Successfully!"</span>,
            deleteResult,
          });
        })
        .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
          response.status(<span class="hljs-number">500</span>).send({
            <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Couldn't be Deleted!"</span>,
            e,
          });
        });
      })
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      response.status(<span class="hljs-number">500</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"Failure"</span>,
        error,
      });
    });
});
</code></pre>
<p>The time has arrived for us to put our Endpoint to the test.</p>
<p>The following is my Cloudinary <code>media library</code> with two images I uploaded already. Take note of their unique ID (<code>public_id</code>). </p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/sjir185on5pqrlzrc1hl.JPG" alt="Alt Text" width="1365" height="766" loading="lazy"></p>
<p>If you don't already have that, please use the persist-image endpoint to upload some images.</p>
<p>Now let's proceed to postman:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/beu5lleymnffa5vyzj97.JPG" alt="Alt Text" width="1365" height="730" loading="lazy"></p>
<p>Notice, the unique ID as it matches one of the image in my Cloudinary media library.</p>
<p>From the output, we executed the DELETE command and that deleted one ROW from our image TABLE in our database.</p>
<p>Now this is my media library with one of the images remaining:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/8d7rs33580c4ewpcipbr.JPG" alt="Alt Text" width="1366" height="768" loading="lazy"></p>
<p>Walahhhh... We are now able to get rid of an image.</p>
<p>Do take a break if you need one. ✌🏾</p>
<p>If you are ready, I am ready to update images.</p>
<h3 id="heading-update-image-api">Update Image API</h3>
<p>Below the <code>delete-image</code> API, let's start creating the <code>update-image</code> API with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// update image</span>
app.put(<span class="hljs-string">"/update-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {

});

All codes will live <span class="hljs-keyword">in</span> there.
</code></pre>
<p>Collect the unique Cloudinary ID and new image details from the user with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// unique ID</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

<span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">title</span>: request.body.title,
    <span class="hljs-attr">image</span>: request.body.image,
  };
</code></pre>
<p>Delete the image from Cloudinary with the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// delete image from cloudinary first</span>
  cloudinary.uploader
    .destroy(cloudinary_id)
      <span class="hljs-comment">// upload image here</span>
    .then()
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      response.status(<span class="hljs-number">500</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"failed"</span>,
        error,
      });
    });
</code></pre>
<p>Next, upload another image to Cloudinary. To do that, enter the following code into the <code>then</code> block:</p>
<pre><code class="lang-javascript">
() =&gt; {
      cloudinary.uploader
        .upload(data.image)
        .then()
        .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
          response.status(<span class="hljs-number">500</span>).send({
            <span class="hljs-attr">message</span>: <span class="hljs-string">"failed"</span>,
            err,
          });
        });
    }
</code></pre>
<p>Now let's replace our initial record with the new image details. Replace the content of the <code>then</code> block with the following:</p>
<pre><code class="lang-javascript">
(result) =&gt; {
          db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {

            <span class="hljs-comment">// update query</span>
            <span class="hljs-keyword">const</span> updateQuery =
              <span class="hljs-string">"UPDATE images SET title = $1, cloudinary_id = $2, image_url = $3 WHERE cloudinary_id = $4"</span>;
            <span class="hljs-keyword">const</span> value = [
              data.title,
              result.public_id,
              result.secure_url,
              cloudinary_id,
            ];
          });
        }
</code></pre>
<p>We execute the query using the following code just beneath the query declaration:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// execute query</span>
            client
              .query(updateQuery, value)
              .then(<span class="hljs-function">() =&gt;</span> {

                <span class="hljs-comment">// send success response</span>
                response.status(<span class="hljs-number">201</span>).send({
                  <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
                  <span class="hljs-attr">data</span>: {
                    <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Updated Successfully"</span>
                  },
                });
              })
              .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                response.status(<span class="hljs-number">500</span>).send({
                  <span class="hljs-attr">message</span>: <span class="hljs-string">"Update Failed"</span>,
                  e,
                });
              });
</code></pre>
<p>At this point, this is what I have:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// update image</span>
app.put(<span class="hljs-string">"/update-image/:cloudinary_id"</span>, <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
  <span class="hljs-comment">// unique ID</span>
  <span class="hljs-keyword">const</span> { cloudinary_id } = request.params;

  <span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">title</span>: request.body.title,
    <span class="hljs-attr">image</span>: request.body.image,
  };

    <span class="hljs-comment">// delete image from cloudinary first</span>
    cloudinary.uploader
      .destroy(cloudinary_id)

      <span class="hljs-comment">// upload image here</span>
      .then(<span class="hljs-function">() =&gt;</span> {
        cloudinary.uploader
          .upload(data.image)

          <span class="hljs-comment">// update the database here</span>
          .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
            db.pool.connect(<span class="hljs-function">(<span class="hljs-params">err, client</span>) =&gt;</span> {
            <span class="hljs-comment">// update query</span>
            <span class="hljs-keyword">const</span> updateQuery =
              <span class="hljs-string">"UPDATE images SET title = $1, cloudinary_id = $2, image_url = $3 WHERE cloudinary_id = $4"</span>;
            <span class="hljs-keyword">const</span> value = [
              data.title,
              result.public_id,
              result.secure_url,
              cloudinary_id,
            ];

            <span class="hljs-comment">// execute query</span>
            client
              .query(updateQuery, value)
              .then(<span class="hljs-function">() =&gt;</span> {

                <span class="hljs-comment">// send success response</span>
                response.status(<span class="hljs-number">201</span>).send({
                  <span class="hljs-attr">status</span>: <span class="hljs-string">"success"</span>,
                  <span class="hljs-attr">data</span>: {
                    <span class="hljs-attr">message</span>: <span class="hljs-string">"Image Updated Successfully"</span>
                  },
                });
              })
              .catch(<span class="hljs-function">(<span class="hljs-params">e</span>) =&gt;</span> {
                response.status(<span class="hljs-number">500</span>).send({
                  <span class="hljs-attr">message</span>: <span class="hljs-string">"Update Failed"</span>,
                  e,
                });
              });
            });
          })
          .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
            response.status(<span class="hljs-number">500</span>).send({
              <span class="hljs-attr">message</span>: <span class="hljs-string">"failed"</span>,
              err,
            });
          });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failed"</span>,
          error,
        });
      });

});
</code></pre>
<p>It's testing time!</p>
<p>This is my postman in the image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/jowr0guiazmqkhx1mmls.JPG" alt="Alt Text" width="1366" height="768" loading="lazy"></p>
<p>Take note of the unique cloudinary ID which matches the image left in my Cloudinary media library.</p>
<p>Now take a look at my Cloudinary media library in the image that follows:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/td9rpqovhh2kl6ytc2u4.JPG" alt="Alt Text" width="1366" height="768" loading="lazy"></p>
<p>Take note of the new image replacing the initial one in my media library above.</p>
<p>Also, see that the unique Cloudinary ID matches that in my database with the new title. See image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/wu3mydrn71x3azyd75ee.JPG" alt="Alt Text" width="1366" height="729" loading="lazy"></p>
<p>Yayeh! You did awesomely great! 💪</p>
<p>We just completed an image management application with Node.js, Cloudinary and Postgres.</p>
<h2 id="heading-code-optimisation-with-express-routing">Code Optimisation With Express Routing</h2>
<p>Express Routing enables us to make our Node.js code more optimized or give it a more modular structure by separating the business logic from the controllers. We want to use that to clean up our code so far. </p>
<p>We'll begin by creating a new folder with the name <code>routes</code> in the root directory:</p>
<pre><code class="lang-javascript">
mk dir routes
</code></pre>
<p>In the routes folder, create a file with the name: <code>routes.js</code>.</p>
<p>For Windows:</p>
<pre><code class="lang-javascript">
echo . &gt; routes.js
</code></pre>
<p>For Mac:</p>
<pre><code class="lang-javascript">
touch routes.js
</code></pre>
<p>Empty the <code>routes.js</code> file if anything is there and enter the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);

<span class="hljs-keyword">const</span> router = express.Router();



<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<p>Add the following code above the last line:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> cloudinary = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cloudinary"</span>).v2;
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();
<span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../services/dbConnect.js"</span>);

<span class="hljs-comment">// cloudinary configuration</span>
cloudinary.config({
  <span class="hljs-attr">cloud_name</span>: process.env.CLOUD_NAME,
  <span class="hljs-attr">api_key</span>: process.env.API_KEY,
  <span class="hljs-attr">api_secret</span>: process.env.API_SECRET,
});
</code></pre>
<p>Back in the App.js file, delete the following code:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> cloudinary = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cloudinary"</span>).v2;
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();
<span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./services/dbConnect.js"</span>);

<span class="hljs-comment">// cloudinary configuration</span>
cloudinary.config({
  <span class="hljs-attr">cloud_name</span>: process.env.CLOUD_NAME,
  <span class="hljs-attr">api_key</span>: process.env.API_KEY,
  <span class="hljs-attr">api_secret</span>: process.env.API_SECRET,
});
</code></pre>
<p>Move all the APIs to <code>routes.js</code>.</p>
<p>Change all occurence of <code>app</code> to <code>router</code> carefully.</p>
<p>My <code>routes.js</code> file now looks like <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/blob/routing/routes/routes.js">this</a>.</p>
<p>Back in the <code>app.js</code> file, import the <code>routes.js</code> file like so:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// import the routes file</span>
<span class="hljs-keyword">const</span> routes = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./routes/routes"</span>)
</code></pre>
<p>Now register the routes like so:</p>
<pre><code class="lang-javascript">
<span class="hljs-comment">// register the routes </span>
app.use(<span class="hljs-string">'/'</span>, routes);
</code></pre>
<p>This is my <code>app.js</code> file at the moment:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> app = express();

<span class="hljs-comment">// import the routes file</span>
<span class="hljs-keyword">const</span> routes = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./routes/routes"</span>)

<span class="hljs-comment">// body parser configuration</span>
<span class="hljs-keyword">const</span> bodyParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">"body-parser"</span>);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ <span class="hljs-attr">extended</span>: <span class="hljs-literal">true</span> }));

<span class="hljs-comment">// register the routes </span>
app.use(<span class="hljs-string">'/'</span>, routes);

<span class="hljs-built_in">module</span>.exports = app;
</code></pre>
<p>It's time to test and see if our routes are still working like before.</p>
<p>Make sure yours are working like mine below:</p>
<h4 id="heading-persist-image">persist-image</h4>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/r2uz54xix054li4lgw6i.JPG" alt="persist image" width="1366" height="730" loading="lazy"></p>
<h4 id="heading-retrieve-image">retrieve-image</h4>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/o44a1dnfgvz3r9sv3a1j.JPG" alt="retrieve image" width="1366" height="727" loading="lazy"></p>
<h4 id="heading-update-image">update-image</h4>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/6t9k5m079rwx4p6ouax3.JPG" alt="update image" width="1365" height="728" loading="lazy"></p>
<h4 id="heading-delete-image">delete-image</h4>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/nkkydplioi68la4eynhp.JPG" alt="delete image" width="1366" height="728" loading="lazy"></p>
<p>Wow! We have been able to separate our routes from our <code>app.js</code> file.</p>
<p>The code for this is <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/routing">here</a>.</p>
<p>Even though our <code>routes.js</code> file is still lengthy, we have a good basis to separate our business logic from our controllers. The time has arrived to do just that.</p>
<h2 id="heading-how-to-move-each-endpoint-to-a-different-file">How to Move Each Endpoint to a Different File</h2>
<p>Begin by creating a new folder in the <code>routes</code> folder and name it <code>controllers</code>.</p>
<p>In the controllers folder, create 5 files and name them after the 5 endpoints.</p>
<p>Our folder and files should be structured as follows:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/dmren2r2w2801lf28wjy.JPG" alt="folder and files structure" width="242" height="728" loading="lazy"></p>
<p>Back in the routes.js file, let's work on the <code>image-upload</code> API. Cut the following code:</p>
<pre><code class="lang-javascript">
(request, response) =&gt; {
  <span class="hljs-comment">// collected image from a user</span>
  <span class="hljs-keyword">const</span> data = {
    <span class="hljs-attr">image</span>: request.body.image,
  };

  <span class="hljs-comment">// upload image here</span>
  cloudinary.uploader
    .upload(data.image)
    .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
      response.status(<span class="hljs-number">200</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span>,
        result,
      });
    })
    .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      response.status(<span class="hljs-number">500</span>).send({
        <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
        error,
      });
    });
}
</code></pre>
<p>In the <code>imageUpload</code> file, equate the code you already cut from the <code>image-upload</code> endpoint to <code>exports.imageUpload</code> like so:</p>
<pre><code class="lang-javascript">
<span class="hljs-built_in">exports</span>.imageUpload = <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
    <span class="hljs-comment">// collected image from a user</span>
    <span class="hljs-keyword">const</span> data = {
      <span class="hljs-attr">image</span>: request.body.image,
    };

    <span class="hljs-comment">// upload image here</span>
    cloudinary.uploader
      .upload(data.image)
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span>,
          result,
        });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
          error,
        });
      });
  }
</code></pre>
<p>Now let's import what is necessary for this code to work. So this is my <code>imageUpload</code> file right now:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> cloudinary = <span class="hljs-built_in">require</span>(<span class="hljs-string">"cloudinary"</span>).v2;
<span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();

<span class="hljs-comment">// cloudinary configuration</span>
cloudinary.config({
  <span class="hljs-attr">cloud_name</span>: process.env.CLOUD_NAME,
  <span class="hljs-attr">api_key</span>: process.env.API_KEY,
  <span class="hljs-attr">api_secret</span>: process.env.API_SECRET,
});

<span class="hljs-built_in">exports</span>.imageUpload = <span class="hljs-function">(<span class="hljs-params">request, response</span>) =&gt;</span> {
    <span class="hljs-comment">// collected image from a user</span>
    <span class="hljs-keyword">const</span> data = {
      <span class="hljs-attr">image</span>: request.body.image,
    };

    <span class="hljs-comment">// upload image here</span>
    cloudinary.uploader
      .upload(data.image)
      .then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        response.status(<span class="hljs-number">200</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span>,
          result,
        });
      })
      .catch(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
        response.status(<span class="hljs-number">500</span>).send({
          <span class="hljs-attr">message</span>: <span class="hljs-string">"failure"</span>,
          error,
        });
      });
  }
</code></pre>
<p>Let's import and register the <code>imageUpload</code> API in the <code>routes.js</code> file like so:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> imageUpload = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./controllers/imageUpload"</span>);

<span class="hljs-comment">// image upload API</span>
router.post(<span class="hljs-string">"image-upload"</span>, imageUpload.imageUpload);
</code></pre>
<p>Now we have this line of code pointing to the <code>imageUpload</code> API in the <code>imageUpload.js</code> file from the <code>routes.js</code> file.</p>
<p>How awesome! Our code is more readable.</p>
<p>Make sure to test the API to be sure it's working properly. Mine works perfectly. See image below:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/t456z1fz1537nja0huyr.JPG" alt="image upload test result" width="1358" height="1024" loading="lazy"></p>
<p>Now, it's your turn!</p>
<p>Apply what you have learnt to the other APIs. Let's see what you have got.</p>
<p>I will be waiting on the other side...</p>
<p>If you are here, then I believe you have done yours and they're working perfectly – or at least, you already gave it your best shot. Kudos!</p>
<p>Checkout mine <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/controller-setup/routes">here</a>.</p>
<p>Congratulations. You are awesome :)</p>
<p>The code optimisation code is <a target="_blank" href="https://github.com/EBEREGIT/server-tutorial/tree/controller-setup">here</a>.</p>
<p>Alright, on to the next step.</p>
<h2 id="heading-how-to-deploy-to-github-and-heroku">How to Deploy to GitHub And Heroku</h2>
<p>Now that we've completed our application, let's deploy it on Heroku so that we can access it even without being on our laptop where the code was written.</p>
<p>I will be walking you through uploading our application to <a target="_blank" href="https://github.com/">GitHub</a> and deploying it to <a target="_blank" href="https://heroku.com/">Heroku</a>.</p>
<p>Without further ado, let's get our hands dirty.</p>
<h2 id="heading-how-to-upload-the-code-to-github">How to Upload the Code to GitHub</h2>
<p>Uploading or pushing to GitHub is as easy as eating your favorite meal. Check out <a target="_blank" href="https://docs.github.com/en/get-started/importing-your-projects-to-github/importing-source-code-to-github/adding-an-existing-project-to-github-using-the-command-line">this resource</a> to learn how to push your project from you local machine to GitHub.</p>
<h2 id="heading-how-to-deploy-to-heroku">How to Deploy to Heroku</h2>
<p>Let's begin by creating an account on <a target="_blank" href="https://heroku.com/">Heroku</a>.</p>
<p>If you have created an account, you may have been prompted to create an app (that is a folder where your app will be housed). You can do that, but I will do mine using my terminal since the terminal comes with a few added functionalities that we will need later.</p>
<p>Open your project in a terminal if you have not done so already. I will be using the VS Code integrated terminal.</p>
<p>Install Heroku CLI:</p>
<pre><code class="lang-javascript">npm install heroku
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/716g1v0nnea93rdrhmk5.JPG" alt="Alt Text" width="666" height="273" loading="lazy"></p>
<p>Login to Heroku CLI. This will open a browser window, which you can use to log in.</p>
<pre><code class="lang-javascript">heroku login
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/bky4mywipzua8cj6d0kf.JPG" alt="Alt Text" width="741" height="181" loading="lazy"></p>
<p>Create an app. It can have any name. I am using <code>node-postgres-cloudinary</code>.</p>
<pre><code class="lang-javascript">heroku create node-postgres-cloudinary
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ibofc7xcyqgi165f5dz1.JPG" alt="Alt Text" width="743" height="155" loading="lazy"></p>
<p>Go to your <a target="_blank" href="https://dashboard.heroku.com/apps">Heroku dashboard</a> and you will find the newly created app.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/d84emoge8pi9qt0a2qrb.JPG" alt="Alt Text" width="1366" height="624" loading="lazy"></p>
<p>Waalaah!</p>
<p>That is how mine looks in the image above. I have some apps there already but you can see the one I just created.</p>
<p>Let's now add the PostgreSQL database to the app.</p>
<h3 id="heading-how-to-add-heroku-postgres">How to Add Heroku Postgres</h3>
<p>Click on the app you just created. It will take you to the app's dashboard.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/g48iadulci1evt6j8ftf.JPG" alt="Alt Text" width="1365" height="625" loading="lazy"></p>
<p>Click on the <code>Resources</code> tab/menu.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/e6bvzsv7cn6ebgwi0y65.JPG" alt="Alt Text" width="1366" height="173" loading="lazy"></p>
<p>In the <code>Add-ons</code> Section, search and select <code>Heroku Postgres</code>.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/91nwppclfdkvitp56rrj.JPG" alt="Alt Text" width="1365" height="625" loading="lazy"></p>
<p>Make sure you select the <code>Hobby Dev - Free</code> plan in the pop-up window that follows:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/hd7zj1qa2t4e051zn654.JPG" alt="Alt Text" width="1366" height="625" loading="lazy"></p>
<p>Click on the <code>provision</code> button to add it to the app like so:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/8w2z6tok8plmp154ovuj.JPG" alt="Alt Text" width="1366" height="628" loading="lazy"></p>
<p>Click on the <code>Heroku Postgres</code> to take you to the <code>Heroku Postgres</code> dashboard.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/nvrdf902ypuz2frqiu1q.JPG" alt="Alt Text" width="1365" height="625" loading="lazy"></p>
<p>Click on the <code>settings</code> tab:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/6k81alizfc5yzgtbzlcv.JPG" alt="Alt Text" width="1366" height="630" loading="lazy"></p>
<p>Click on <code>View Credentials</code>:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/33p2ceyfkbmr590s1r6b.JPG" alt="Alt Text" width="1347" height="165" loading="lazy"></p>
<p>In the Credentials, we are interested in the Heroku CLI. We will be using it in a bit.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/xzmtjtqrg8a5vqrfjkg3.JPG" alt="Alt Text" width="1366" height="614" loading="lazy"></p>
<p>Back to the terminal.</p>
<p>Let's confirm if the <code>Heroku Postgres</code> was added successfully. Enter the following in the terminal:</p>
<pre><code class="lang-javascript">heroku addons
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/7hhl70mw99b2mrji9i35.JPG" alt="Alt Text" width="841" height="249" loading="lazy"></p>
<p>Yeeeeaaaah! It was added successfully.</p>
<p>Before we proceed, <strong>make sure that your PostgreSQL <code>path</code> is set correctly if you are on Windows</strong>. Follow this <a target="_blank" href="https://www.computerhope.com/issues/ch000549.htm">link</a> to learn how to set a <code>path</code>. The path should be like this: <code>C:\Program Files\PostgreSQL\&lt;VERSION&gt;\bin</code>. </p>
<p>The version will depend on the one installed on you machine. Mine is: <code>C:\Program Files\PostgreSQL\12\bin</code> since I am using the <code>version 12</code>.</p>
<p>The following image might be helpful:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/dl3wv0z1dtvyjvvusx26.JPG" alt="Alt Text" width="1366" height="728" loading="lazy"></p>
<p>You may have to navigate to the folder where PostgreSQL is installed on your machine to find out your own path.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/bzf04c5827d4tri3ngrg.JPG" alt="Alt Text" width="1366" height="725" loading="lazy"></p>
<p>Login into the <code>Heroku Postgres</code> using the <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli">Heroku CLI</a> from our <code>Heroku Postgres</code> <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-postgresql-credentials">credentials</a>. This is mine – yours will be different:</p>
<pre><code class="lang-javascript">heroku pg:psql postgresql-slippery<span class="hljs-number">-19135</span> --app node-postgres-cloudinary
</code></pre>
<p>If you got an error, it is most likely because your path is not set properly.</p>
<h3 id="heading-how-to-prepare-our-database-connection-to-match-herokus">How to Prepare our Database Connection to Match Heroku's</h3>
<p>At the moment, my database looks like this:</p>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> pg = <span class="hljs-built_in">require</span>(<span class="hljs-string">"pg"</span>);

<span class="hljs-keyword">const</span> config = {
  <span class="hljs-attr">user</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">database</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">password</span>: <span class="hljs-string">"tutorial"</span>,
  <span class="hljs-attr">port</span>: <span class="hljs-number">5432</span>,
  <span class="hljs-attr">max</span>: <span class="hljs-number">10</span>, <span class="hljs-comment">// max number of clients in the pool</span>
  <span class="hljs-attr">idleTimeoutMillis</span>: <span class="hljs-number">30000</span>,
};

<span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> pg.Pool(config);

pool.on(<span class="hljs-string">"connect"</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"connected to the Database"</span>);
});
</code></pre>
<p>If you try connecting Heroku to this, you are going to get an error. This is because Heroku has a <code>connection string</code> setup already. So we have to setup our connection such that Heroku can easily connect. </p>
<p>I am going to refactor my database connection file (<code>dbConnect.js</code>) and <code>.env</code> file to make this happen.</p>
<ul>
<li>dbConnect.js</li>
</ul>
<pre><code class="lang-javascript">
<span class="hljs-keyword">const</span> pg = <span class="hljs-built_in">require</span>(<span class="hljs-string">'pg'</span>);
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config();

<span class="hljs-comment">// set production variable. This will be called when deployed to a live host</span>
<span class="hljs-keyword">const</span> isProduction = process.env.NODE_ENV === <span class="hljs-string">'production'</span>;

<span class="hljs-comment">// configuration details</span>
<span class="hljs-keyword">const</span> connectionString = <span class="hljs-string">`postgresql://<span class="hljs-subst">${process.env.DB_USER}</span>:<span class="hljs-subst">${process.env.DB_PASSWORD}</span>@<span class="hljs-subst">${process.env.DB_HOST}</span>:<span class="hljs-subst">${process.env.DB_PORT}</span>/<span class="hljs-subst">${process.env.DB_DATABASE}</span>`</span>;

<span class="hljs-comment">// if project has been deployed, connect with the host's DATABASE_URL</span>
<span class="hljs-comment">// else connect with the local DATABASE_URL</span>
<span class="hljs-keyword">const</span> pool = <span class="hljs-keyword">new</span> pg.Pool({
  <span class="hljs-attr">connectionString</span>: isProduction ? process.env.DATABASE_URL : connectionString,
  <span class="hljs-attr">ssl</span>: isProduction,
});

<span class="hljs-comment">// display message on success if successful</span>
pool.on(<span class="hljs-string">'connect'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Teamwork Database connected successfully!'</span>);
});
</code></pre>
<ul>
<li>.env file</li>
</ul>
<pre><code class="lang-javascript">
DB_USER=<span class="hljs-string">"tutorial"</span>
DB_PASSWORD=<span class="hljs-string">"tutorial"</span>
DB_HOST=<span class="hljs-string">"localhost"</span>
DB_PORT=<span class="hljs-string">"5432"</span>
DB_DATABASE=<span class="hljs-string">"tutorial"</span>
</code></pre>
<p>With the setup of the <code>dbconnect</code> and <code>.env</code> file, we are ready to export our database and tables from our local machine to <code>heroku postgres</code>.</p>
<h3 id="heading-how-to-export-database-and-tables">How to Export Database and Tables</h3>
<p>Go to your <code>pgAdmin</code> and locate the database for this tutorial. Mine is tutorial.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/m454dij54j7dghsxqa25.JPG" alt="Alt Text" width="1366" height="693" loading="lazy"></p>
<p>Right-Click on it and select <code>Backup</code>. This will bring up a new window.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/u57h6kpw5gtbhublc1la.JPG" alt="Alt Text" width="1366" height="695" loading="lazy"></p>
<p>Enter a name for the SQL file like I did. Select the <code>plain</code> format. Then click Backup. This will save the file to your documents folder.</p>
<p>Locate the file and move it into the project directory. It can be anywhere in the directory but I choose to move mine into the <code>services</code> directory because it holds the database related files.</p>
<p>Back in the terminal, navigate to the folder containing the SQL file and run the following code to add the tables we just exported to the <code>heroku postgres</code> database:</p>
<pre><code class="lang-html">cat <span class="hljs-tag">&lt;<span class="hljs-name">your-SQL-file</span>&gt;</span> | <span class="hljs-tag">&lt;<span class="hljs-name">heroku-CLI-from-the-heroku-posgres-credentials</span>&gt;</span>
</code></pre>
<p>This is what mine looks like:</p>
<pre><code class="lang-javascript">cat tutorial.sql | heroku pg:psql postgresql-slippery<span class="hljs-number">-19135</span> --app node-postgres-cloudinary
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/67x0f9521g5savkrafpc.JPG" alt="Alt Text" width="738" height="677" loading="lazy"></p>
<p>Did you notice that I changed directory to services (<code>cd services</code>)? That is where my <code>sql</code> file is located.</p>
<p>Wow! We have just successfully exported our database and tables to Heroku.</p>
<p>It is almost over...</p>
<h3 id="heading-how-to-tell-github-that-we-made-changes">How to Tell GitHub that We Made Changes</h3>
<p>Add the files we have made changes to:</p>
<pre><code class="lang-javascript">$ git add .
</code></pre>
<p>The period (<code>.</code>) adds all files.</p>
<p>Commit your latest changes:</p>
<pre><code class="lang-javascript">$ git commit -m <span class="hljs-string">"refactored the dbConnect and .env file to fit in to heroku; Added the database SQL file"</span>
</code></pre>
<p>Push the committed files:</p>
<pre><code class="lang-javascript">$ git push origin -u master
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/u8kogsya1gt3uxnujz35.JPG" alt="Alt Text" width="1318" height="577" loading="lazy"></p>
<h3 id="heading-finally-deploying-our-app">Finally deploying our App</h3>
<p>Go to you app's dashboard:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/4j4bi64n0f0bp65j7pb7.JPG" alt="Alt Text" width="1366" height="171" loading="lazy"></p>
<p>Select the GitHub Deployment method:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/5edwt90cpy75b3uyqgvy.JPG" alt="Alt Text" width="1319" height="143" loading="lazy"></p>
<p>Search and select a repo, and click on <code>connect</code>:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/ifg4zh7jzab3edpaxvnf.JPG" alt="Alt Text" width="1279" height="222" loading="lazy"></p>
<p>Select the branch you want to deploy (in my own case, it is the <code>master</code> branch):</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/4ka9c6781vft1gio7p0k.JPG" alt="Alt Text" width="1307" height="316" loading="lazy"></p>
<p>Enable automatic deployment by clicking the <code>Enable automatic deployment</code> button as in the image above.</p>
<p>Click on the <code>Deploy</code> button in the manual deploy</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/7ksrclnfyjzzvn2nr4gc.JPG" alt="Alt Text" width="1251" height="200" loading="lazy"></p>
<p>We will not have to do all this for subsequent deployments.</p>
<p>Now you have a button telling you to "view site" after build is completed. Click it. This will open your app in a new tab.</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/9xi5vx6lx99z49uscre6.JPG" alt="Alt Text" width="1366" height="692" loading="lazy"></p>
<p><strong>Oh no! A bug? Application error??</strong></p>
<p>Don't worry, it just a small issue. Something you should never forget to do while making deployments. Most hosting service will require it.</p>
<h3 id="heading-how-to-fix-the-heroku-application-error">How to Fix the Heroku Application Error</h3>
<p>Get back to the root directory of your project.</p>
<p>Create a file and name it <code>Procfile</code> (it has no extension).</p>
<p>In the file, enter the following code:</p>
<pre><code class="lang-javascript">web: node index.js
</code></pre>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/k3jb0rof1g6bs1zs2eh6.JPG" alt="Alt Text" width="603" height="708" loading="lazy"></p>
<p>This directs Heroku to the server file (<code>index.js</code>) which is the entry point of the application. If your server is in a different file, please modify as required.</p>
<p>Save the file and push the new changes to GitHub.</p>
<p>Wait 2 to 5 minutes for Heroku to automatically detect changes in your GitHub repo and render the changes on the app.</p>
<p>You can now refresh that error page and see your hard work paying off:</p>
<p><img src="https://dev-to-uploads.s3.amazonaws.com/i/n4t9yp4598wc5i7v8p82.JPG" alt="Alt Text" width="1366" height="694" loading="lazy"></p>
<p>You can also test the <code>retrieve image</code> route and see it working.</p>
<p>Congratulations! What a feat you have attained.</p>
<p>Other routes <strong>(persist-image, update-image, and delete-image)</strong> will not be working because we have not provisioned or added <code>cloudinary</code> add-on. It is as simple as the <code>PostgreSQL</code> one we just did. So you can give it a shot.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We started this tutorial with the aim of learning how to build a backend application using Express, Postgres, Cloudinary, Github and Heroku. </p>
<p>We learned how to store, retrieve, delete, and update an image record. We then organised our code with Express Routing, pushed it to GitHub, and deployed it on Heroku. That was a lot.</p>
<p>I hope you will agree that it was worth it because we learnt a lot. You should try adding the Cloudinary add-on yourself to sharpen your knowledge even more.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Your Own Heroku with Dokku ]]>
                </title>
                <description>
                    <![CDATA[ By Nuno Bispo Heroku is a well-known PaaS widely used by developers. And as a fun and useful project, you can easily make your own Heroku-like PaaS with Dokku. What is Heroku? Heroku is a platform as a service (PaaS) company founded in 2007. The pla... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-your-on-heroku-with-dokku/</link>
                <guid isPermaLink="false">66d4608a733861e3a22a734b</guid>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ PaaS ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 04 Feb 2022 19:01:06 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/02/dokku.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Nuno Bispo</p>
<p>Heroku is a well-known PaaS widely used by developers. And as a fun and useful project, you can easily make your own Heroku-like PaaS with Dokku.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/dokku-logo-with-name5-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-what-is-heroku">What is Heroku?</h2>
<p>Heroku is a platform as a service (PaaS) company founded in 2007. The platform runs on AWS, and its ephemeral storage system is called "Dyno".</p>
<p>Heroku is one of the most used PaaS by developers and for a good reason– it is easy to use, well documented, and supports several programming languages.</p>
<p>But what if you could deploy your own Heroku-like platform, including a CI/CD pipeline, database connections, HTTPS connections, and much more with one simple-to-use application?</p>
<p>Well, that is what Dokku provides and more. Let's take a look.</p>
<h2 id="heading-what-is-a-paas">What is a PaaS?</h2>
<p>Platform-as-a-Service (PaaS) is a software architecture style that provides an easy-to-use abstraction layer for deploying your application's code and managing it.</p>
<p>This allows you to focus on writing business logic rather than worrying about the platform itself.</p>
<p>PaaS providers usually provide their own database service as well as other related services, which can greatly simplify common development tasks.</p>
<p>The great advantage of PaaS is that the application developer doesn't need to perform any system administration work. Instead, you can just upload your code and configuration settings to a central server platform.</p>
<p>The service then takes care of deploying the code, scaling it as needed, backing up data, handling hosting and uptime concerns, and so on.</p>
<h2 id="heading-what-is-dokku">What is Dokku?</h2>
<p>Dokku is a hosted Platform as a Service that enables developers to deploy their applications with ease.</p>
<p>From their website:</p>
<blockquote>
<p>The smallest PaaS implementation you've ever seen</p>
</blockquote>
<p>Dokku is based on Docker and uses Heroku's build-packs to compile and package your applications.</p>
<p>One of the best things about Dokku is that it is very lightweight and can be installed on a single server or VM.</p>
<p>It includes scalable hosting using Docker containers, continuous deployment with Git, and other popular DevOps tools.</p>
<p>Dokku also offers a variety of features, such as support for multiple languages, custom domains, automated deployments, and many more.</p>
<p>You can easily connect Postgres databases and even file storage to your applications.</p>
<p>You can check out more information at <a target="_blank" href="https://dokku.com/">https://dokku.com/</a> or the documentation at: <a target="_blank" href="https://dokku.com/docs/getting-started/installation/">https://dokku.com/docs/getting-started/installation/</a>.</p>
<p>You can also show some love for the <a target="_blank" href="https://github.com/dokku/dokku">open-source GitHub project here</a>. </p>
<h2 id="heading-how-to-install-dokku">How to Install Dokku</h2>
<p>In order to install Dokku you will need a Linux VPS and a domain name. </p>
<p>You can install and use Dokku without a domain name but it is much simpler using a domain. I recommend a cloud VPS because it makes it easier to access and configure.</p>
<p>When connecting a domain, either a single domain or a wildcard can be associated with the server's IP.</p>
<p>I will be using a VPS hosted on <a target="_blank" href="https://hetzner.cloud/">Hetzner</a> with Ubuntu 20.04 installed.</p>
<p>We first start by making sure that our system is up to date with these commands:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Update the linux installation</span>
$ sudo apt update
$ sudo apt upgrade -y
</code></pre>
<p>Then we can download and run the installation script for Dokku:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install Dokku with the install script</span>
$ wget https://raw.githubusercontent.com/dokku/dokku/v0.26.8/bootstrap.sh;
$ sudo DOKKU_TAG=v0.26.8 bash bootstrap.sh

--&gt; Ensuring we have the proper dependencies
--&gt; Note: Installing dokku <span class="hljs-keyword">for</span> the first time will result <span class="hljs-keyword">in</span> removal of
    files <span class="hljs-keyword">in</span> the nginx <span class="hljs-string">'sites-enabled'</span> directory. Please manually
    restore any files that may be removed after the installation and
    web setup is complete.

    Installation will <span class="hljs-built_in">continue</span> <span class="hljs-keyword">in</span> 10 seconds.

    [...........]

    --&gt; Running post-install dependency installation

 ! Setup a user<span class="hljs-string">'s ssh key for deployment by passing in the public ssh key as shown:

     echo '</span>CONTENTS_OF_ID_RSA_PUB_FILE<span class="hljs-string">' | dokku ssh-keys:add admin</span>
</code></pre>
<p>The installation script will install Docker and all necessary dependencies and also Dokku itself, as seen in the code above.</p>
<p>After the installation is complete we need to assign the SSH keys to access and also configure our domain name.</p>
<p>In case you have set up access to your VPS with SSH (which you should) then you already have the necessary keys – you just need to add them to Dokku:</p>
<pre><code># Assign SSH key to Dokku
$ cat ~<span class="hljs-regexp">/.ssh/</span>authorized_keys | dokku ssh-keys:add admin

<span class="hljs-attr">SHA256</span>:<span class="hljs-number">6</span>O1TLVOUkWV+zmTWXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
</code></pre><p>In case you don't already have an SSH key in the server, then you need to generate a key pair:</p>
<pre><code># Generate SSH key
$ ssh-keygen

Generating public/private rsa key pair.
Enter file <span class="hljs-keyword">in</span> which to save the key (<span class="hljs-regexp">/root/</span>.ssh/id_rsa):
Enter passphrase (empty <span class="hljs-keyword">for</span> no passphrase):
Enter same passphrase again:
Your identification has been saved <span class="hljs-keyword">in</span> /root/.ssh/id_rsa
Your public key has been saved <span class="hljs-keyword">in</span> /root/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:<span class="hljs-number">7</span>T6BbRCVWjGtcSUXXXXXXXXXXXXXXXXXXXXXXXXXXXXX root@freeDokku
The key<span class="hljs-string">'s randomart image is:
+---[RSA 3072]----+
[.................]
|     . oS*.o . . |
[.................]
+----[SHA256]-----+</span>
</code></pre><p>Then you can add it to Dokku:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Add SSH key to Dokku</span>
$ dokku ssh-keys:add admin /root/.ssh/id_rsa.pub

SHA256:7T6BbRCVWjGtcSUXXXXXXXXXXXXXXXXXXXXXXXX
</code></pre>
<p>Next, and the final step, is to assign the domain for your Dokku installation. We do that with the command:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Set installation global domain</span>
$ dokku domains:set-global domain.com

-----&gt; Set domain.com
</code></pre>
<p>Make sure you replace 'domain.com' with your own domain, and that your domain name DNS points to the server's IP address.</p>
<p>And that is all you need to do to install and set up Dokku. It is really that simple.</p>
<p>You can now start adding your applications. </p>
<p>Let's see an example of that by adding a standard Django application in the next section.</p>
<h2 id="heading-how-to-create-your-application-in-dokku">How to Create Your Application in Dokku</h2>
<p>To create and deploy our first application, there is some preparation work we need to do on Dokku.</p>
<p>To deploy an application on Dokku, follow these steps:</p>
<ul>
<li>Create the application on Dokku, which means giving it a name.</li>
<li>Create the associate database (or other plugins, if needed). This will create and provision a database for use with an automatic DATABASE_URL added to the application for ease of deployment.</li>
<li>Push the necessary code to Dokku's application internal GitHub endpoint. This can include also the necessary release steps (like running Django migrations, for example).</li>
</ul>
<p>After the code is pushed, Dokku will generate any necessary Docker container and will run our application with any associated databases (or other plugins).</p>
<p>Now that we've covered the necessary steps, let's go through them in practice.</p>
<p>Let's start by creating our application. For this tutorial, I will create a very simple Django website that contains all the necessary logic for us to test Dokku. </p>
<p>We create an application on Dokku with this command (in the server where we installed Dokku):</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Creating our application on Dokku</span>
$ dokku apps:create djangotutorial

-----&gt; Creating djangotutorial...
</code></pre>
<p>By default, datastores (or databases) are not created when an application is created. </p>
<p>The datastores are handled by a series of plugins. You can <a target="_blank" href="https://dokku.com/docs/community/plugins/#official-plugins-beta">check here for all available plugins</a>.</p>
<p>For our application, we will create a Postgres datastore. Since by default no plugins are installed, we first need to install the Postgres plugin:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># install the postgres plugin</span>
<span class="hljs-comment"># plugin installation requires root, hence the user change</span>
sudo dokku plugin:install https://github.com/dokku/dokku-postgres.git
</code></pre>
<p>Then we can create our Postgres datastore:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create a Postgres datastore</span>
$ dokku postgres:create djangotutorial_datastore

       Waiting <span class="hljs-keyword">for</span> container to be ready
       Creating container database
       Securing connection to database
=====&gt; Postgres container created: djangotutorial_datastore
=====&gt; djangotutorial_datastore postgres service information
       Config dir:          /var/lib/dokku/services/postgres/djangotutorial_datastore/data
       Config options:
       Data dir:            /var/lib/dokku/services/postgres/djangotutorial_datastore/data
       Dsn:                 postgres://postgres:ea706cc108c805d5124d134d934024c5@dokku-postgres-djangotutorial-datastore:5432/djangotutorial_datastore
       Exposed ports:       -
       Id:                  782a04fe6bbd25958752c17c304358fd5ec1f3c54d6d53175b6481b3b957d94b
       Internal ip:         172.17.0.5
       Links:               -
       Service root:        /var/lib/dokku/services/postgres/djangotutorial_datastore
       Status:              running
       Version:             postgres:14.1
</code></pre>
<p>We can check that our Docker container for the datastore is already up and running with:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Check running containers</span>
$ docker ps

CONTAINER ID   IMAGE                      COMMAND                  CREATED              STATUS              PORTS      NAMES
782a04fe6bbd   postgres:14.1              <span class="hljs-string">"docker-entrypoint.s…"</span>   About a minute ago   Up About a minute   5432/tcp   dokku.postgres.djangotutorial_datastore
</code></pre>
<p>Now that we have the datastore up and running, we need to associate it with our application:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Associate datastore with the application</span>
$ dokku postgres:link djangotutorial_datastore djangotutorial

-----&gt; Setting config vars
       DATABASE_URL:  postgres://postgres:ea706cc108c805d5124d134d934024c5@dokku-postgres-djangotutorial-datastore:5432/djangotutorial_datastore
-----&gt; Restarting app djangotutorial
 !     App image (dokku/djangotutorial:latest) not found
</code></pre>
<p>You can see that a DATABASE_URL is automatically created and associated with the application.</p>
<p>The example above mentions that our application image is not found because we haven't pushed any code to it yet.</p>
<p>We can check our application's environment variables to confirm that our DATABASE_URL is present:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Checking an application environment variables</span>
$ dokku config:show djangotutorial

=====&gt; djangotutorial env vars
DATABASE_URL:  postgres://postgres:ea706cc108c805d5124d134d934024c5@dokku-postgres-djangotutorial-datastore:5432/djangotutorial_datastore
</code></pre>
<p>We now have all the necessary configurations done on the Dokku side to support the deployment of our application.</p>
<p>Next, we will create the code for our application and deploy that to Dokku for an automated CI/CD pipeline.</p>
<h2 id="heading-how-to-create-our-application-code-on-pycharm">How to Create Our Application Code on PyCharm</h2>
<p>Before we can deploy an application, we need to have its source code to push to Dokku.</p>
<p>For this tutorial, we are going to create a very simple Django application that shows also the use of the Postgres database.</p>
<p>We will be using PyCharm as our IDE to create and manage our project.</p>
<p>We create a new project in PyCharm – let's call it 'DjangoTutorial':</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/PyCharm-NewProject.png" alt="Image" width="600" height="400" loading="lazy">
<em>Creating a new project on PyCharm - Screenshot by author</em></p>
<p>I personally prefer to create new projects with a virtual environment already in place, which makes life much easier.</p>
<p>If you created the project with a default main.py file (like I did because I keep forgetting to remove the checkmark), you can safely delete it now. We are not going to use it.</p>
<p>The first step is, of course, to install Django so we can build our application. We do that install using pip:</p>
<pre><code>$ pip install django

Collecting django
  Downloading Django<span class="hljs-number">-4.0</span><span class="hljs-number">.2</span>-py3-none-any.whl (<span class="hljs-number">8.0</span> MB)
     |████████████████████████████████| <span class="hljs-number">8.0</span> MB <span class="hljs-number">6.4</span> MB/s
Collecting sqlparse&gt;=<span class="hljs-number">0.2</span><span class="hljs-number">.2</span>
  Using cached sqlparse<span class="hljs-number">-0.4</span><span class="hljs-number">.2</span>-py3-none-any.whl (<span class="hljs-number">42</span> kB)
Collecting tzdata
  Using cached tzdata<span class="hljs-number">-2021.5</span>-py2.py3-none-any.whl (<span class="hljs-number">339</span> kB)
Collecting asgiref&lt;<span class="hljs-number">4</span>,&gt;=<span class="hljs-number">3.4</span><span class="hljs-number">.1</span>
  Downloading asgiref<span class="hljs-number">-3.5</span><span class="hljs-number">.0</span>-py3-none-any.whl (<span class="hljs-number">22</span> kB)
Installing collected packages: tzdata, sqlparse, asgiref, django
Successfully installed asgiref<span class="hljs-number">-3.5</span><span class="hljs-number">.0</span> django<span class="hljs-number">-4.0</span><span class="hljs-number">.2</span> sqlparse<span class="hljs-number">-0.4</span><span class="hljs-number">.2</span> tzdata<span class="hljs-number">-2021.5</span>
</code></pre><p>Then we create our Django project with:</p>
<pre><code>$ django-admin startproject DjangoTutorial .
</code></pre><p>Notice the '.' at the end of the command. I like to use that so that it creates the project in the current directory instead of creating an extra sub-directory.</p>
<p>You should now have a project structure like this in PyCharm:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/PyCharm-Project.png" alt="Image" width="600" height="400" loading="lazy">
<em>PyCharm folder structure for our Django application - screenshot by the author</em></p>
<p>We can run our project with the standard Django run:</p>
<pre><code>$ python manage.py runserver   

Watching <span class="hljs-keyword">for</span> file changes <span class="hljs-keyword">with</span> StatReloader
Performing system checks...

System check identified no issues (<span class="hljs-number">0</span> silenced).

You have <span class="hljs-number">18</span> unapplied migration(s). Your project may not work properly until you apply the migrations <span class="hljs-keyword">for</span> app(s): admin, auth, contenttypes, sessions.
Run <span class="hljs-string">'python manage.py migrate'</span> to apply them.
February <span class="hljs-number">02</span>, <span class="hljs-number">2022</span> - <span class="hljs-number">16</span>:<span class="hljs-number">49</span>:<span class="hljs-number">27</span>
Django version <span class="hljs-number">4.0</span><span class="hljs-number">.2</span>, using settings <span class="hljs-string">'DjangoTutorial.settings'</span>
Starting development server at http:<span class="hljs-comment">//127.0.0.1:8000/</span>
Quit the server <span class="hljs-keyword">with</span> CTRL-BREAK.
</code></pre><p>We have not yet applied our migrations, so we will do that next after we discuss the database configuration for both local and Dokku access.</p>
<p>Navigating to the link <a target="_blank" href="http://127.0.0.1:8000/">http://127.0.0.1:8000/</a>, we can now access our standard Django welcome page:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/Django.png" alt="Image" width="600" height="400" loading="lazy">
<em>Django welcome page - screenshot by the author</em></p>
<p>We have our Django installation up and running so now we can start building the rest of the project.</p>
<p>Like most projects, we will need to store data in a database (or database using the Dokku naming).</p>
<p>We also want to be able to debug and run our application locally on the development machine (using a local database, in this SQLite) and run it on the cloud with Dokku using the Postgres database. </p>
<p>This means we need to change some configuration in our settings.py to be able to support both use cases without us needing to change any flags or configs every time.</p>
<p>We start by installing the package dj-database-url with:</p>
<pre><code># Install packages <span class="hljs-keyword">for</span> the database url
$ pip install dj-database-url
$ pip install psycopg2


# We also install <span class="hljs-built_in">this</span> package to be able to use environment variables
$ pip install python-decouple
</code></pre><p>This package enables us to have a Django database connection dictionary, populated with all the data by simply specifying a database URL.</p>
<p>With the package install, let's update the configuration on the settings.py:</p>
<pre><code class="lang-python"><span class="hljs-comment"># We need to add this import at the beginning to use environment variables</span>
<span class="hljs-keyword">import</span> dj_database_url
<span class="hljs-keyword">from</span> decouple <span class="hljs-keyword">import</span> config
<span class="hljs-keyword">from</span> django.conf.global_settings <span class="hljs-keyword">import</span> DATABASES

.....

<span class="hljs-comment"># Let's also updated the allowed host so we can use it later on</span>
ALLOWED_HOSTS = config(<span class="hljs-string">'ALLOWED_HOSTS'</span>).split(<span class="hljs-string">','</span>)

.....

<span class="hljs-comment"># We replace the default database configuration from Django with this one</span>
<span class="hljs-comment"># Database</span>
<span class="hljs-comment"># https://docs.djangoproject.com/en/4.0/ref/settings/#databases</span>

<span class="hljs-comment"># DATABASE URL</span>
DATABASES[<span class="hljs-string">'default'</span>] = dj_database_url.parse(config(<span class="hljs-string">'DATABASE_URL'</span>),conn_max_age=<span class="hljs-number">600</span>)
</code></pre>
<p>We will also need to create '.env' file in the root dir of our project:</p>
<pre><code>#HOST SETTINGS
ALLOWED_HOSTS = <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>

#DATABASE SETTINGS
DATABASE_URL=<span class="hljs-string">'sqlite:///db.sqlite3'</span>
</code></pre><p>As you can see, with this change we can use the database URL from the local '.env' file on the local development machine, and then on Dokku it will automatically use the already defined DATABASE_URL that was created when we linked the datastore to the application on Dokku.</p>
<p>We can now create our first (and only) web page of this tutorial), a simple counter that stores and read the value from the database.</p>
<p>Let's create a separate application to contain our logic:</p>
<pre><code class="lang-bash">$ python manage.py startapp counter
</code></pre>
<p>We now should have a new folder called 'counter' in our project. Let's add a new model by opening the 'models.py' file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.db <span class="hljs-keyword">import</span> models


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Counter</span>(<span class="hljs-params">models.Model</span>):</span>
    count = models.IntegerField(default=<span class="hljs-number">0</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-keyword">return</span> self.count
</code></pre>
<p>We can now add a new URL to load our counter page. We do that by adding a new file called 'urls.py' to our 'counter' folder:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path
<span class="hljs-keyword">from</span> . <span class="hljs-keyword">import</span> views

urlpatterns = [
    path(<span class="hljs-string">'counter/'</span>, views.counter, name=<span class="hljs-string">'counter'</span>),
]
</code></pre>
<p>We now have both the model and the URL to load our test page. All we need now is the view and HTML template to render the page.</p>
<p>Let's create the view by editing the 'views.py' file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.shortcuts <span class="hljs-keyword">import</span> render
<span class="hljs-keyword">from</span> .models <span class="hljs-keyword">import</span> Counter


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">counter</span>(<span class="hljs-params">request</span>):</span>
    counter_value = Counter.objects.last()

    <span class="hljs-keyword">if</span> counter_value <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:
        counter_value = Counter(count=<span class="hljs-number">0</span>)
        counter_value.save()

    <span class="hljs-keyword">if</span> request.method == <span class="hljs-string">'POST'</span>:
        counter_value.count += <span class="hljs-number">1</span>
        counter_value.save()

    <span class="hljs-keyword">return</span> render(request, <span class="hljs-string">'counter.html'</span>, {<span class="hljs-string">'counter'</span>: counter_value.count})
</code></pre>
<p>Now we can create our HTML template to show the counter value on the page. We create a new file called 'counter.html' inside a new 'templates' folder:</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Counter<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>&gt;</span>
      {%csrf_token%}
    <span class="hljs-tag">&lt;<span class="hljs-name">h4</span>&gt;</span>Counter value is: {{ counter }}<span class="hljs-tag">&lt;/<span class="hljs-name">h4</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"Increase Counter"</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>The last step is to add our newly created application to the 'settings.py' file in order for Django to recognize it:</p>
<pre><code>.....

INSTALLED_APPS = [
    <span class="hljs-string">'counter'</span>,
    <span class="hljs-string">'django.contrib.admin'</span>,
    <span class="hljs-string">'django.contrib.auth'</span>,
    <span class="hljs-string">'django.contrib.contenttypes'</span>,
    <span class="hljs-string">'django.contrib.sessions'</span>,
    <span class="hljs-string">'django.contrib.messages'</span>,
    <span class="hljs-string">'django.contrib.staticfiles'</span>,
]

.....
</code></pre><p>And the URL to our main URLs file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> django.contrib <span class="hljs-keyword">import</span> admin
<span class="hljs-keyword">from</span> django.urls <span class="hljs-keyword">import</span> path, include

urlpatterns = [
    path(<span class="hljs-string">''</span>, include(<span class="hljs-string">'counter.urls'</span>)),
    path(<span class="hljs-string">'admin/'</span>, admin.site.urls),
]
</code></pre>
<p>With all the necessary code and HTML in place, we can now create and run our migrations to create our new model in the database. We first do that on the local server by running:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create and run migrations</span>
$ python manage.py makemigrations
$ python manage.py migrate

Operations to perform:
  Apply all migrations: admin, auth, contenttypes, counter, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying counter.0001_initial... OK
  Applying sessions.0001_initial... OK
</code></pre>
<p>As you can see, we not only applied the migrations for our new application but we also run the initial migrations for the other Django applications since this was the first time we ran the migrations.</p>
<p>We can again run our server locally and we should be able to access the URL <a target="_blank" href="http://127.0.0.1:8000/counter/">http://127.0.0.1:8000/counter/</a> and increment the counter:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/CounterPage_Local.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Running our counter application - GIF by the author</em></p>
<p>As you can see, reloading the page keeps our counter value, meaning that the value is been stored in the database with our model.</p>
<h2 id="heading-how-to-deploy-our-application-to-dokku">How to Deploy Our Application to Dokku</h2>
<p>We now have a very simple application running with database integration to store our counter value.</p>
<p>We are ready to deploy it to the cloud so we can test it there and make sure our database is also working in the cloud.</p>
<p>Before we do the Git push to deploy the code to Dokku, we need to do some preparation:</p>
<ul>
<li>Install our web server (gunicorn)</li>
<li>Create our requirements file (for our packages)</li>
<li>Create our Procfile (for our deployment commands)</li>
</ul>
<p>Let's start with installing our web server to use in the cloud:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install our web server</span>
$ pip install gunicorn
</code></pre>
<p>With our packages in place we can now create our requirements file with:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Create requirements file</span>
$ pip freeze &gt; requirements.txt
</code></pre>
<p>Now we need to create the 'Procfile'. This file is used by Dokku to determine which commands to run on deployment and after deployment. </p>
<p>So let's create a new file called 'Procfile' in the root directory with the contents:</p>
<pre><code>web: gunicorn DjangoTutorial.wsgi
<span class="hljs-attr">release</span>: python manage.py migrate
</code></pre><p>We have created two commands for Dokku to run:</p>
<ul>
<li>release – this command is executed on the deployment of our application in Dokku. We use it to migrate our database.</li>
<li>web – this command allows Dokku to know which webserver to run to allow access to the application.</li>
</ul>
<p>Finally, to make sure that we can collect any static files when our code is deployed to Dokku, we need to create a new directory called 'static' on the root directory. Inside we create an empty file called '.gitkeep' (this will allow us to add the directory to the Git repository later).</p>
<p>We also need to add this path for the static files to our 'settings.py' file:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Static files (CSS, JavaScript, Images)</span>
<span class="hljs-comment"># https://docs.djangoproject.com/en/4.0/howto/static-files/</span>

STATIC_URL = <span class="hljs-string">'static/'</span>
STATIC_ROOT = BASE_DIR / <span class="hljs-string">"static"</span>
</code></pre>
<p>Now all the files and logic are in place and we can deploy to Dokku with a standard Git push. Let's check our current file structure:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/PyCharm-FolderStrcuture-1.png" alt="Image" width="600" height="400" loading="lazy">
<em>PyCharm folder structure - screenshot by the author</em></p>
<p>To be able to push our code to Dokku, we need to add our project to a Git repository.</p>
<p>Since we don't want to push all the files from our folder structure to the Dokku git repository, we create a '.gitignore' to exclude certain files and directories. I use the contents of this excellent Gist to populate the file:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="3cf91616c9f3bbc3d1339adfc707b08a">
        <script src="https://gist.github.com/MOOOWOOO/3cf91616c9f3bbc3d1339adfc707b08a.js"></script></div><p>We can now initialize and commit our code to a Git repository locally:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Initialize repository</span>
$ git init -b main

<span class="hljs-comment"># Add and commit our files</span>
$ git add . &amp;&amp; git commit -m <span class="hljs-string">"initial commit"</span>

[main (root-commit) e77a16a] initial commit
 20 files changed, 438 insertions(+)       
 create mode 100644 .gitignore
 create mode 100644 DjangoTutorial/__init__.py
 create mode 100644 counter/tests.py
 create mode 100644 counter/urls.py
 create mode 100644 counter/views.py
 create mode 100644 db.sqlite3
 create mode 100644 manage.py
 create mode 100644 requirements.txt
</code></pre>
<p>With our repository committed, we can now push it to a remote repository, that is the Dokku Git repository for our application:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Adding our remote repository (replace domain.com with your domain name)</span>
$ git remote add dokku dokku@domain.com:djangotutorial

<span class="hljs-comment"># Time to push our code to the remote repository</span>
$ git push dokku main

Enumerating objects: 34, <span class="hljs-keyword">done</span>.
Counting objects: 100% (34/34), <span class="hljs-keyword">done</span>.
Delta compression using up to 8 threads
Compressing objects: 100% (31/31), <span class="hljs-keyword">done</span>.
Writing objects: 100% (34/34), 11.41 KiB | 402.00 KiB/s, <span class="hljs-keyword">done</span>.
Total 34 (delta 7), reused 0 (delta 0)
-----&gt; Set main to DOKKU_DEPLOY_BRANCH.
-----&gt; Cleaning up...
-----&gt; Building djangotutorial from herokuish
-----&gt; Adding BUILD_ENV to build environment...
       BUILD_ENV added successfully
-----&gt; Python app detected
-----&gt; No Python version was specified. Using the buildpack default: python-3.9.9
       To use a different version, see: https://devcenter.heroku.com/articles/python-runtimes
-----&gt; No change <span class="hljs-keyword">in</span> requirements detected, installing from cache
-----&gt; Installing python-3.9.9
-----&gt; Installing pip 21.3.1, setuptools 57.5.0 and wheel 0.37.0
-----&gt; Installing SQLite3
-----&gt; Installing requirements with pip
       Collecting asgiref==3.5.0
       Downloading asgiref-3.5.0-py3-none-any.whl (22 kB)
       Collecting dj-database-url==0.5.0
       Downloading dj_database_url-0.5.0-py2.py3-none-any.whl (5.5 kB)
       Collecting Django==4.0.2
       Downloading Django-4.0.2-py3-none-any.whl (8.0 MB)
       Collecting gunicorn==20.1.0
       Downloading gunicorn-20.1.0-py3-none-any.whl (79 kB)
       Collecting psycopg2==2.9.3
       Downloading psycopg2-2.9.3.tar.gz (380 kB)
       Preparing metadata (setup.py): started
       Preparing metadata (setup.py): finished with status <span class="hljs-string">'done'</span>
       Collecting python-decouple==3.5
       Downloading python_decouple-3.5-py3-none-any.whl (9.6 kB)
       Collecting sqlparse==0.4.2
       Downloading sqlparse-0.4.2-py3-none-any.whl (42 kB)
       Collecting tzdata==2021.5
       Downloading tzdata-2021.5-py2.py3-none-any.whl (339 kB)
       Building wheels <span class="hljs-keyword">for</span> collected packages: psycopg2
       Building wheel <span class="hljs-keyword">for</span> psycopg2 (setup.py): started
       Building wheel <span class="hljs-keyword">for</span> psycopg2 (setup.py): finished with status <span class="hljs-string">'done'</span>
       Created wheel <span class="hljs-keyword">for</span> psycopg2: filename=psycopg2-2.9.3-cp39-cp39-linux_x86_64.whl size=579484 sha256=9d6a2810a5d766738526d6f411e5e9ce514cce882b6c80a47a13c02dc7529e02
       Stored <span class="hljs-keyword">in</span> directory: /tmp/pip-ephem-wheel-cache-8k0chg5g/wheels/b3/a1/6e/5a0e26314b15eb96a36263b80529ce0d64382540ac7b9544a9
       Successfully built psycopg2
       Installing collected packages: sqlparse, asgiref, tzdata, python-decouple, psycopg2, gunicorn, Django, dj-database-url
       Successfully installed Django-4.0.2 asgiref-3.5.0 dj-database-url-0.5.0 gunicorn-20.1.0 psycopg2-2.9.3 python-decouple-3.5 sqlparse-0.4.2 tzdata-2021.5
-----&gt; $ python manage.py collectstatic --noinput
       128 static files copied to <span class="hljs-string">'/tmp/build/static'</span>.

-----&gt; Discovering process types
       Procfile declares types -&gt; release, web
-----&gt; Releasing djangotutorial...
-----&gt; Checking <span class="hljs-keyword">for</span> predeploy task
       No predeploy task found, skipping
-----&gt; Checking <span class="hljs-keyword">for</span> release task
-----&gt; Executing release task from Procfile: python manage.py migrate
=====&gt; Start of djangotutorial release task (a602cab30) output
       Operations to perform:
         Apply all migrations: admin, auth, contenttypes, counter, sessions
       Running migrations:
         Applying contenttypes.0001_initial... OK
         Applying auth.0001_initial... OK
         Applying admin.0001_initial... OK
         Applying admin.0002_logentry_remove_auto_add... OK
         Applying admin.0003_logentry_add_action_flag_choices... OK
         Applying contenttypes.0002_remove_content_type_name... OK
         Applying auth.0002_alter_permission_name_max_length... OK
         Applying auth.0003_alter_user_email_max_length... OK
         Applying auth.0004_alter_user_username_opts... OK
         Applying auth.0005_alter_user_last_login_null... OK
         Applying auth.0006_require_contenttypes_0002... OK
         Applying auth.0007_alter_validators_add_error_messages... OK
         Applying auth.0008_alter_user_username_max_length... OK
         Applying auth.0009_alter_user_last_name_max_length... OK
         Applying auth.0010_alter_group_name_max_length... OK
         Applying auth.0011_update_proxy_permissions... OK
         Applying auth.0012_alter_user_first_name_max_length... OK
         Applying counter.0001_initial... OK
         Applying sessions.0001_initial... OK
=====&gt; End of djangotutorial release task (a602cab30) output
-----&gt; App Procfile file found
=====&gt; Processing deployment checks
       No CHECKS file found. Simple container checks will be performed.
       For more efficient zero downtime deployments, create a CHECKS file. See https://dokku.com/docs/deployment/zero-downtime-deploys/ <span class="hljs-keyword">for</span> examples
-----&gt; Deploying djangotutorial via the docker-local scheduler...
-----&gt; Deploying web (count=1)
       Attempting pre-flight checks (web.1)
       Waiting <span class="hljs-keyword">for</span> 10 seconds (web.1)
       Default container check successful (web.1)
-----&gt; Deploying release (count=0)
-----&gt; Running post-deploy
-----&gt; Creating new app virtual host file...
-----&gt; Configuring djangotutorial.domain.com...(using built-in template)
-----&gt; Creating http nginx.conf
       Reloading nginx
-----&gt; Renaming containers
       Renaming container djangotutorial.web.1.upcoming-7101 (f8d229ebd8bc) to djangotutorial.web.1
-----&gt; Checking <span class="hljs-keyword">for</span> postdeploy task
       No postdeploy task found, skipping
-----&gt; Updated schedule file
=====&gt; Application deployed:
       http://djangotutorial.domain.com

To domain.com:djangotutorial
 * [new branch]      main -&gt; main
</code></pre>
<p>We have just deployed our application to Dokku. </p>
<p>What just happened? Well, Dokku did a lot of work for us:</p>
<ul>
<li>Installed Python</li>
<li>Installed the requirements</li>
<li>Collected the static files</li>
<li>Performed the migrations</li>
<li>And finally started a gunicorn server to deploy our application </li>
</ul>
<p>If you had a permission error, then your private key should be registered within your local development environment. If you get a <code>permission denied</code> error when pushing, you can register your private key as follows: <code>ssh-add -k ~/&lt;your private key&gt;</code>.</p>
<p>You may also see an error regarding the ALLOWED_HOSTS when accessing the application. In that case, all you need to do is to run the following command on the Dokku server to set the environment variable to the correct value:</p>
<pre><code># <span class="hljs-built_in">Set</span> ALLOWED_HOSTS environment variable (make sure to use your domain name)
$ dokku config:set djangotutorial ALLOWED_HOSTS=djangotutorial.domain.com
</code></pre><p>We can now access and test our application at the above URL:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/02/PageCounter_Server.gif" alt="Image" width="600" height="400" loading="lazy">
<em>Running our counter application on Dokku - GIF by the author</em></p>
<p>Congratulations, you just deployed your application on Dokku. </p>
<h2 id="heading-how-to-add-ssl-with-lets-encrypt">How to Add SSL with Let's Encrypt</h2>
<p>One final configuration that we can do is to add SSL security to our application by installing a Let's Encrypt SSL certificate.</p>
<p>We can do this very easily on Dokku with the Let's Encrypt plugin:</p>
<pre><code class="lang-bash"><span class="hljs-comment"># Install the Let's Encrypt plugin</span>
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git

<span class="hljs-comment"># Configure the plugin (make sure to replace to your email)</span>
dokku config:<span class="hljs-built_in">set</span> --global DOKKU_LETSENCRYPT_EMAIL=email@domain.com

<span class="hljs-comment"># set a custom domain that you own for your application</span>
dokku domains:<span class="hljs-built_in">set</span> djangotutorial djangotutorial.your.domain.com

<span class="hljs-comment"># Enable Let's Encrypt</span>
dokku letsencrypt:<span class="hljs-built_in">enable</span> djangotutorial

<span class="hljs-comment"># Enable Let's Encrypt auto-renewal</span>
dokku letsencrypt:cron-job --add
</code></pre>
<p>Now we have a more secure application. After all, our counter is very important.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Using a PaaS makes a developer's life easier when building web applications. </p>
<p>You can use hosted PaaS like Heroku and there are many others, so the choice is there.</p>
<p>But there are some main drawbacks:</p>
<ul>
<li>Price – hosted solutions might have limits in terms of database storage or file storage, among others</li>
<li>You don't control the hosting where the PaaS is deployed. Recent examples of AWS shows that not even the biggest hosting is free of problems.</li>
</ul>
<p>You can work around these issues by self-hosting your PaaS.</p>
<p>This allows for more control in terms of pricing. You can use hosting provider like <a target="_blank" href="https://www.digitalocean.com/">Digital Ocean</a>, <a target="_blank" href="https://hetzner.cloud/">Hetzner</a>, and others who have quite cheap VPSs that work perfectly with Dokku.</p>
<p>There are no database limits. The only limits you might have are memory and disk space, but you can always upgrade your VPS for a smaller price than getting a new database at Heroku.</p>
<p>Dokku is easy to install and like we saw. Creating and deploying an application is a 3 step process:</p>
<ul>
<li>Create an application on Dokku</li>
<li>Create a datastore on Dokku (if needed, like Postgres) and link to the application</li>
<li>Deploy your code to Dokku with Git</li>
</ul>
<p>Additionally, you might need to configure some environment variables and SSL certificates, but that is all. </p>
<p>Dokku is really the smallest PaaS implementation.</p>
<p>Full source code for the Django application is available at:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/nunombispo/DjangoTutorial">https://github.com/nunombispo/DjangoTutorial</a></div>
<p>Follow me on Twitter: <a target="_blank" href="https://twitter.com/DevAsService">https://twitter.com/DevAsService</a></p>
<p>Check out my website at: <a target="_blank" href="https://developer-service.io/">https://developer-service.io/</a></p>
<p>Or check out my blog at: <a target="_blank" href="https://blog.developer-service.io/">https://blog.developer-service.io/</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Deploy a Live2D Web App Using Heroku ]]>
                </title>
                <description>
                    <![CDATA[ What is Live2D? Live2D is a technology that allows artists to easily transform traditional 2D illustrations to create fluid expressions and motions. The most popular software for Live2D modeling and animation is Cubism, which also provides well-docum... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-deploy-a-live2d-web-app-using-heroku/</link>
                <guid isPermaLink="false">66d4601d787a2a3b05af43d8</guid>
                
                    <category>
                        <![CDATA[ animation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ #Game Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Game Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Lynn Zheng ]]>
                </dc:creator>
                <pubDate>Mon, 28 Dec 2020 17:48:17 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/12/web-2.gif" medium="image" />
                <content:encoded>
                    <![CDATA[ <h2 id="heading-what-is-live2d">What is Live2D?</h2>
<p>Live2D is a technology that allows artists to easily transform traditional 2D illustrations to create fluid expressions and motions.</p>
<p>The most popular software for Live2D modeling and animation is Cubism, which also provides well-documented SDK for web, native apps, and the Unity game development engine.</p>
<p>In this tutorial, I will walk you through how to build on top of Cubism's official Live2D Web SDK sample and deploy it to Heroku, a popular cloud app-hosting platform.</p>
<h2 id="heading-how-to-set-up-the-environment">How to Set Up the Environment</h2>
<p>To follow along with this tutorial, clone my GitHub repo and checkout the <code>start</code> branch. The finished project is on the <code>develop</code> branch.</p>
<p>I've also recorded <a target="_blank" href="https://youtu.be/uH1IczzE_t4">a video tutorial on YouTube</a>.</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://github.com/RuolinZheng08/heroku-live2d">https://github.com/RuolinZheng08/heroku-live2d</a></div>
<p> </p>
<pre><code class="lang-shell">git clone https://github.com/RuolinZheng08/heroku-live2d.git
git checkout start

# update the submodule, Cubism's Live2d Web Framework
git submodule update --init
</code></pre>
<p>Install Node.js and npm using Homebrew:</p>
<pre><code class="lang-shell"># if you need to install homebrew
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# homebrew will install node and npm at the same time
brew install node
</code></pre>
<p>I'll be using Visual Studio Code as my main IDE, but you may follow along using any editor of your choice.</p>
<h2 id="heading-how-to-run-the-starter-code-locally">How to Run the Starter Code Locally</h2>
<p>The directory structure is as follows. Our web app will be served from <code>Samples/TypeScript/Demo</code>.</p>
<pre><code class="lang-pgsql">.
├─ .vscode          # Visual Studio Code project setting
├─ Core             # Live2D Cubism Core JavaScript <span class="hljs-keyword">and</span> TypeScript source code
├─ Framework        #  Source code <span class="hljs-keyword">for</span> the rendering <span class="hljs-keyword">and</span> animation features
└─ Samples
   ├─ Resources     # Live2D model files <span class="hljs-keyword">and</span> web image assets
   └─ TypeScript    # [IMPORTANT] TypeScript sample project
</code></pre>
<p>Inside the <code>heroku-live2d</code> directory, run the following commands:</p>
<pre><code class="lang-shell">cd Samples/TypeScript/Demo/

npm install

npm run-script build

npm run-script serve
</code></pre>
<p>Navigate to <a target="_blank" href="http://localhost:5000/Samples/TypeScript/Demo/">http://localhost:5000/Samples/TypeScript/Demo/</a> and you should be able to see a Live2D character.</p>
<p>To interact with the model, hold down your mouse cursor and the character's head and eyes will follow your cursor. Tap on the body of the character to see a special animation. Tap on the gear icon in the top right corner to switch between different models.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/web.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Mark from</em> <code>Samples/Resources/Mark</code></p>
<h2 id="heading-how-to-deploy-to-heroku">How to Deploy to Heroku</h2>
<p>The starter code uses npm, TypeScript, and webpack.</p>
<p>To deploy our project to Heroku, we need to create a <code>package.json</code> file that Heroku can use to build our project in our project root directory. We also need to modify <code>Samples/TypeScript/Demo/package.json</code> and the webpack configuration in <code>Samples/TypeScript/Demo/webpack.config.js</code>.</p>
<h3 id="heading-top-level-packagejson">Top-level package.json</h3>
<p>The boilerplate <code>package.json</code> for a Node.js Heroku app looks like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"heroku-live2d"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Live2D Cubism Heroku Demo"</span>,
    <span class="hljs-attr">"scripts"</span>: {
        <span class="hljs-attr">"start"</span>: ...,
        <span class="hljs-attr">"build"</span>: ...
    },
    <span class="hljs-attr">"dependencies"</span>: {
        ...
    }
}
</code></pre>
<p>Inspect the <code>dependencies</code> and <code>devDependencies</code> attributes in <code>Samples/TypeScript/Demo/package.json</code> and add both sets of dependencies as <code>dependencies</code> to <code>heroku-live2d/package.json</code>.</p>
<p>Remember that when building and serving locally, we used <code>npm run-script [build|serve]</code> from inside the <code>Samples/TypeScript/Demo</code> directory.</p>
<p>Therefore, to run these npm commands from the project root, we need to prepend <code>cd Samples/TypeScript/Demo</code> before the npm commands. The build command, for example, will become:</p>
<pre><code class="lang-shell">cd Samples/TypeScript/Demo &amp;&amp; npm run-script build
</code></pre>
<p>With these changes, the top-level <code>package.json</code> should look like this:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"heroku-live2d"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Live2D Cubism Heroku Demo"</span>,
    <span class="hljs-attr">"scripts"</span>: {
        <span class="hljs-attr">"start"</span>: <span class="hljs-string">"cd Samples/TypeScript/Demo &amp;&amp; npm run-script start"</span>,
        <span class="hljs-attr">"build"</span>: <span class="hljs-string">"cd Samples/TypeScript/Demo &amp;&amp; npm run-script build"</span>
    },
    <span class="hljs-attr">"dependencies"</span>: {
        <span class="hljs-attr">"@typescript-eslint/eslint-plugin"</span>: <span class="hljs-string">"^2.18.0"</span>,
        <span class="hljs-attr">"@typescript-eslint/parser"</span>: <span class="hljs-string">"^2.18.0"</span>,
        <span class="hljs-attr">"eslint"</span>: <span class="hljs-string">"^6.8.0"</span>,
        <span class="hljs-attr">"eslint-config-prettier"</span>: <span class="hljs-string">"^6.10.0"</span>,
        <span class="hljs-attr">"eslint-plugin-prettier"</span>: <span class="hljs-string">"^3.1.2"</span>,
        <span class="hljs-attr">"prettier"</span>: <span class="hljs-string">"^1.19.1"</span>,
        <span class="hljs-attr">"rimraf"</span>: <span class="hljs-string">"^3.0.1"</span>,
        <span class="hljs-attr">"serve"</span>: <span class="hljs-string">"^11.3.0"</span>,
        <span class="hljs-attr">"ts-loader"</span>: <span class="hljs-string">"^6.2.1"</span>,
        <span class="hljs-attr">"typescript"</span>: <span class="hljs-string">"^3.7.5"</span>,
        <span class="hljs-attr">"webpack"</span>: <span class="hljs-string">"^4.41.5"</span>,
        <span class="hljs-attr">"webpack-cli"</span>: <span class="hljs-string">"^3.3.10"</span>,
        <span class="hljs-attr">"webpack-dev-server"</span>: <span class="hljs-string">"^3.10.1"</span>,
        <span class="hljs-attr">"whatwg-fetch"</span>: <span class="hljs-string">"^3.0.0"</span>
    }
}
</code></pre>
<h3 id="heading-samplestypescriptdemopackagejson">Samples/TypeScript/Demo/package.json</h3>
<p>On localhost, we were running on port 5000. However, Heroku will dynamically assign our web app a port stored in a variable <code>$PORT</code>. Therefore, we need the <code>npm run-script start</code> command inside <code>Samples/TypeScript/Demo/package.json</code> to start the webpack server on port <code>$PORT</code>.</p>
<p>Append to <code>scripts &gt; start &gt; webpack-dev-server --progress</code> so it looks like this:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"webpack-dev-server --progress --port $PORT"</span>,
    ...
}
</code></pre>
<h3 id="heading-samplestypescriptdemowebpackconfigjs">Samples/TypeScript/Demo/webpack.config.js</h3>
<p>Add <code>disableHostCheck</code> to the configuration of <code>devServer</code> and remove <code>port</code> since we have configured it dynamically above.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
    ...,
    <span class="hljs-attr">devServer</span>: {
        <span class="hljs-attr">contentBase</span>: path.resolve(__dirname, <span class="hljs-string">'../../..'</span>),
        <span class="hljs-attr">watchContentBase</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">inline</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">hot</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">port</span>: <span class="hljs-number">5000</span>, <span class="hljs-comment">// delete this line</span>
        <span class="hljs-attr">host</span>: <span class="hljs-string">'0.0.0.0'</span>,
        <span class="hljs-attr">disableHostCheck</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// add this line</span>
        <span class="hljs-attr">compress</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">useLocalIp</span>: <span class="hljs-literal">true</span>,
        <span class="hljs-attr">writeToDisk</span>: <span class="hljs-literal">true</span>
    },
    ...
}
</code></pre>
<p>Add <code>watchOptions</code> so that our <code>node_modules</code> won't be watched. If we don't do this, we will run into an error about exceeding the maximum number of watchers when we deploy to Heroku.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {
    ...,
    <span class="hljs-attr">watchOptions</span>: {
        <span class="hljs-attr">ignored</span>: <span class="hljs-regexp">/node_modules/</span>
    },
    ...
}
</code></pre>
<h3 id="heading-deploy-to-heroku">Deploy to Heroku</h3>
<p>To download the Heroku command line client, run</p>
<pre><code class="lang-shell">brew tap heroku/brew &amp;&amp; brew install heroku
</code></pre>
<p>Log into Heroku from the command line using <code>heroku login</code>.</p>
<p>Create a Heroku app and append some numbers (for example, 123) to the app name to ensure uniqueness.</p>
<pre><code class="lang-shell">heroku create heroku-live2d-NUMBERS
</code></pre>
<p>Set up Node.js as the buildpack:</p>
<pre><code class="lang-pgsql">heroku buildpacks:<span class="hljs-keyword">set</span> heroku/nodejs
</code></pre>
<p>Add and commit your project using git. Note that we don't necessarily need <code>git push</code>:</p>
<pre><code class="lang-shell">git add .
git commit -m "Ready to deploy to heroku"
</code></pre>
<p>Push the project to Heroku, assuming you are following along on the <code>start</code> branch. You can always check the branch you are on and push from that branch.</p>
<pre><code class="lang-shell"># check which branch we are on
git branch

# the syntax is
# git push heroku GIT_BRANCH_NAME:HEROKU_BRANCH_NAME
git push heroku start:master
</code></pre>
<p>You may need to wait for a few minutes for the build process to complete.</p>
<p>After that, navigate to <code>YOUR-HEROKU-APP-NAME.herokuapp.com/Samples/TypeScript/Demo</code>. In my case, the URL is <a target="_blank" href="https://heroku-live2d.herokuapp.com/Samples/TypeScript/Demo/">https://heroku-live2d.herokuapp.com/Samples/TypeScript/Demo/</a>. The Live2D characters will be there to greet you :)</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/12/Screen-Shot-2020-12-27-at-16.13.19.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Notice that the highlighted URL is already on Heroku</em></p>
<h3 id="heading-how-to-redirect-indexhtml-to-samplestypescriptdemo">How to Redirect index.html to Samples/TypeScript/Demo</h3>
<p>You might have noticed that <code>YOUR-HEROKU-APP-NAME.herokuapp.com</code> shows a list of the directory structure instead of the Live2D models. We can solve this by adding a dummy top-level <code>index.html</code> that redirects to <code>Samples/TypeScript/Demo</code>.</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-comment">&lt;!-- Just a dummy html to redirect to my subdirectory --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"refresh"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"0; url=Samples/TypeScript/Demo"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p>Rerun the deployment command <code>git push heroku start:master</code>. Now when you visit <code>YOUR-HEROKU-APP-NAME.herokuapp.com</code>, you will be automatically redirected to the Live2D model page.</p>
<p>Congratulations on making it to the end of this tutorial! You now have a Live2D Web App deployed to Heroku.</p>
<p>I hope you enjoyed this tutorial. Let's keep in touch! Connect with me on <a target="_blank" href="https://www.linkedin.com/in/ruolin-zheng/">LinkedIn</a>, <a target="_blank" href="https://github.com/RuolinZheng08">GitHub</a>, <a target="_blank" href="https://medium.com/@ruolinzheng">Medium</a>, or check out <a target="_blank" href="https://ruolinzheng08.github.io/">my personal website</a>.</p>
<h3 id="heading-resources-amp-links">Resources &amp; Links</h3>
<p><a target="_blank" href="https://github.com/RuolinZheng08/heroku-live2d/tree/develop">My GitHub repo for this tutorial</a></p>
<p><a target="_blank" href="https://heroku-live2d.herokuapp.com/">My Heroku App</a></p>
<p><a target="_blank" href="https://youtu.be/uH1IczzE_t4">My YouTube Video Tutorial</a></p>
<p><a target="_blank" href="https://docs.live2d.com/cubism-sdk-tutorials/sample-build-web/">Cubism's Official SDK Documentation</a></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Heroku Deploy – How to Push a Web App or Site to Production ]]>
                </title>
                <description>
                    <![CDATA[ By Stan Georgian When it comes to deploying an application, there are usually two options: a VPS or a PaaS (platform as a service). This article will show you a recipe for deploying an application to production on a PaaS like Heroku. Step 1 - Create ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-deploy-an-application-to-heroku/</link>
                <guid isPermaLink="false">66d45edd787a2a3b05af43a4</guid>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 05 Aug 2020 18:41:16 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/08/preview.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Stan Georgian</p>
<p>When it comes to deploying an application, there are usually two options: a <a target="_blank" href="https://en.wikipedia.org/wiki/Virtual_private_server">VPS</a> or a <a target="_blank" href="https://en.wikipedia.org/wiki/Platform_as_a_service">PaaS</a> (platform as a service). This article will show you a recipe for deploying an application to production on a PaaS like <a target="_blank" href="https://www.heroku.com/">Heroku</a>.</p>
<h2 id="heading-step-1-create-the-project">Step 1 - Create the project</h2>
<p>The first step is to create a simple structure for our project with some basic files. For this article, I’ll create a demo server with NodeJS.</p>
<p>In a new folder I’ll open a terminal and run the command <code>npm init -y</code> in order to create a new project.  The dummy server will be written in <a target="_blank" href="https://expressjs.com/">Express</a>, so we need to run the <code>npm install express</code> command to install this module.</p>
<p>Once this library is installed, we can create a new file for our project, named <code>app.js</code>. Inside it we'll write the code for our simple server:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/server.png" alt="Image" width="600" height="400" loading="lazy">
<em>[RAW](https://carbon.now.sh/?bg=rgba(171%2C%20184%2C%20195%2C%201)&amp;t=seti&amp;wt=none&amp;l=javascript&amp;ds=true&amp;dsyoff=20px&amp;dsblur=68px&amp;wc=true&amp;wa=true&amp;pv=56px&amp;ph=56px&amp;ln=false&amp;fl=1&amp;fm=Hack&amp;fs=14px&amp;lh=133%25&amp;si=false&amp;es=2x&amp;wm=false&amp;code=const%2520app%2520%253D%2520require(%2522express%2522)()%253B%250A%250Aconst%2520PORT%2520%253D%2520process.env.PORT%2520%257C%257C%25203000%253B%250A%250Aapp.get(%2522%2522%252C%2520(req%252C%2520res)%2520%253D%253E%2520%257B%250A%2520%2520res.send(%2522Hello%2520world%2522)%253B%250A%257D)%253B%250A%250Aapp.listen(PORT%252C%2520()%2520%253D%253E%2520%257B%250A%2520%2520console.log(%2560App%2520up%2520at%2520port%2520%2524%257BPORT%257D%2560)%253B%250A%257D)%253B)</em></p>
<p>We can start the application by running <code>node app.js</code>. Then we can try it out at the following URL <code>http://localhost:3000</code>. At this point you should see the message <code>Hello World</code> in the browser.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/output.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-2-version-control-system">Step 2 - Version control system</h2>
<p>The next step is to choose a version control system and to place our code in a development platform in a repository.</p>
<p>The most popular version control system is <a target="_blank" href="https://git-scm.com/">Git</a> along with <a target="_blank" href="https://github.com/">Github</a> as a development platform, so that's what we'll use here.</p>
<p>On GitHub, go ahead and create a new repository for your application, like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/1.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To upload your local code into a repository, you need to run the commands that are listed on Github after you click <code>Create repository</code> button:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/git.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>Commands to upload our code to the Github repo</em></p>
<p><strong>!</strong> Before we do this, we must ignore some files. We want to upload to the repository only the code that we write, without the dependencies (the installed modules).</p>
<p>For that, we need to create a new file <code>.gitignore</code> and inside it write the file that we want to ignore.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/ignore.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>Folder structure and .gitignore file</em></p>
<p>Now, we can write the commands listed in the picture above (the one from GitHub).</p>
<p>If you ran the commands correctly, then it'll be on your repository’s page. If you refresh it you should see your files, except the one that you explicitly ignored, namely <code>node_modules</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/git-master.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-3-link-the-repository-with-heroku">Step 3 - Link the repository with Heroku</h2>
<p>At this step, we can link the repository from Github to our Heroku application.</p>
<p>First, create a new application on Heroku and follow the steps listed on the platform.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/heroku-new.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once the application has been created, a window similar to this should appear:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/heroku-shoud-see.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>App dashboard</em></p>
<p>Now, if you look at the navigation at the top, you'll see  <code>Overview</code>, <code>Resources</code>, <code>Deploy</code>, <code>Metrics</code>  and so on. Be sure that <code>Deploy</code> is selected. Then on the second row, click on the GitHub icon.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/search-and-cionnect.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>Click connect</em></p>
<p>Search for the desired application, which is <code>demo-deploy-app-09</code> in our case. Then click <code>Connect</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/deploy.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>Deploy Branch</em></p>
<p>Once the application is successfully connected with your Heroku account, you can click <code>Deploy Branch</code> to deploy your application.</p>
<p>If you want, you can also select the option <code>Enable Automatic Deploys</code> which will automatically pull the code from your Github repository every time you make a push to that repository.</p>
<p>Once the application has been deployed, you can click on View to open your application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/final.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-4-configure-heroku-to-properly-run-the-application">Step 4 - Configure Heroku to properly run the application</h2>
<p>If you open the application at this point, you should see something like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/errr.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>That’s right, an error. That’s because Heroku doesn’t know how to start our application.</p>
<p>If you remember, we ran the command <code>node app.js</code> to start the application locally.<br>Heroku has no way of knowing what commands it needs to run to start the application, and that's why it threw an error.</p>
<p>To solve this problem, we must create a new file named <code>Procfile</code> with the following content: <code>web: node ./app.js</code>.</p>
<p>To update our application, all we need to do is push a new commit to GitHub. If we have enabled the <code>Automatic Deploys</code> option, then the code will be automatically pulled to Heroku. Otherwise we need to click on <code>Deploy Branch</code> again.</p>
<p>Once the application is rebuilt, we should see it working like so:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/deployed.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-step-5-how-to-add-an-add-on">Step 5 - How to add an add-on</h2>
<p>One key benefit that Heroku provides is the fact that you can easily add resources in the form of <code>add-ons</code> to your project. These external resources come in the form of databases, logging &amp; monitoring tools, CI and CD tools, or testing tools.</p>
<p>So now let's see how to add a new resource to your project. First, we'll go to Resources, and from there we'll add a new tool for testing.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/add-addon.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Go ahead and click on <code>Find more add-ons</code> and then search for <code>Loadmill</code>.</p>
<p><a target="_blank" href="https://elements.heroku.com/addons/loadmill">Loadmill</a> is a testing tool which is really great for regression testing and load testing.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/install-loadmill.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Go ahead and click on <code>Install…</code>. Then choose the application you want to link.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/provision-add-on.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>At this step, Heroku will automatically create a new account for you on the provisioned platform.</p>
<p>On the resources tab, you can see the newly added resource:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/ff.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you go ahead and access this add-on, you should see its dashboard with an intro tutorial and a demo test created for you.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/fff2.PNG" alt="Image" width="600" height="400" loading="lazy">
<em>Loadmill dashboard</em></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Heroku allows developers to quickly and almost painlessly deploy an application on a web server.</p>
<p>It also provides a lot of plugins that you can integrate into your application.</p>
<p>A PaaS solution will always allow you to move faster than the solution with a VPS where you have to configure everything from the ground up.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Develop an End-to-End Machine Learning Project and Deploy it to Heroku with Flask ]]>
                </title>
                <description>
                    <![CDATA[ There's one question I always get asked regarding Data Science: What is the best way to master Data Science? What will get me hired? My answer remains constant: There is no alternative to working on portfolio-worthy projects. Even after passing the T... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/end-to-end-machine-learning-project-turorial/</link>
                <guid isPermaLink="false">66d45f3bb6b7f664236cbde1</guid>
                
                    <category>
                        <![CDATA[ Data Science ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Flask Framework ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Harshit Tyagi ]]>
                </dc:creator>
                <pubDate>Mon, 03 Aug 2020 15:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/07/main.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>There's one question I always get asked regarding Data Science:</p>
<p><em>What is the best way to master Data Science? What will get me hired?</em></p>
<p>My answer remains constant: There is no alternative to working on <a target="_blank" href="https://towardsdatascience.com/how-to-build-an-effective-data-science-portfolio-56d19b885aa8?source=---------5------------------">portfolio-worthy projects</a>.</p>
<p>Even after <a target="_blank" href="https://medium.com/@harshit_tyagi/google-certified-tensorflow-developer-learning-plan-tips-faqs-my-journey-9f88016048e3?source=---------7------------------">passing the TensorFlow Developer Certificate</a> Exam, I’d say that you can only really prove your competency with projects that showcase your research, programming skills, mathematical background, and so on.</p>
<p>In my post <a target="_blank" href="https://towardsdatascience.com/how-to-build-an-effective-data-science-portfolio-56d19b885aa8?source=---------5------------------">how to build an effective Data Science Portfolio</a>, I shared many project ideas and other tips to prepare an awesome portfolio. This post is dedicated to one of those ideas: building an end-to-end data science/ML project.</p>
<h1 id="heading-agenda">Agenda</h1>
<p>This tutorial is intended to walk you through all the major steps involved in completing an and-to-end Machine Learning project. For this project, I’ve chosen a supervised learning regression problem.</p>
<p>Here are the major topics covered:</p>
<ul>
<li><p><strong>Pre-requisites and Resources</strong></p>
</li>
<li><p><strong>Data Collection and Problem Statement</strong></p>
</li>
<li><p><strong>Exploratory Data Analysis with Pandas and NumPy</strong></p>
</li>
<li><p><strong>Data Preparation using Sklearn</strong></p>
</li>
<li><p><strong>Selecting and Training a few Machine Learning Models</strong></p>
</li>
<li><p><strong>Cross-Validation and Hyperparameter Tuning using Sklearn</strong></p>
</li>
<li><p><strong>Deploying the Final Trained Model on Heroku via a Flask App</strong></p>
</li>
</ul>
<p>Let’s start building…</p>
<h1 id="heading-pre-requisites-and-resources"><strong>Pre-requisites and Resources</strong></h1>
<p>To go through this project and tutorial, you should be familiar with Machine Learning algorithms, Python environment setup, and common ML terminologies. Here are a few resources to get you started:</p>
<ul>
<li><p>Read the first 2–3 chapters of The hundred page ML book: <a target="_blank" href="http://themlbook.com/wiki/doku.php">http://themlbook.com/wiki/doku.php</a></p>
</li>
<li><p><a target="_blank" href="https://towardsdatascience.com/task-cheatsheet-for-almost-every-machine-learning-project-d0946861c6d0?source=---------2------------------">List of Tasks for almost every Machine Learning Project</a> — Keep referring to this list while working on this (or any other) ML project.</p>
</li>
<li><p>You need a <a target="_blank" href="https://towardsdatascience.com/ideal-python-environment-setup-for-data-science-cdb03a447de8?source=---------18------------------">Python Environment set up</a> — a virtual environment dedicated to this project.</p>
</li>
<li><p>You should be familiar with <a target="_blank" href="https://towardsdatascience.com/the-complete-guide-to-jupyter-notebooks-for-data-science-8ff3591f69a4?source=---------16------------------">Jupyter Notebook</a>.</p>
</li>
</ul>
<p>That’s it, so make sure you have an understanding of these concepts and tools and you’re ready to go!</p>
<h1 id="heading-data-collection-and-problem-statement"><strong>Data Collection and Problem Statement</strong></h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/1ETE.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The first step is to get your hands on the data. But if you have access to data (as most product-based companies do), then the first step is to define the problem that you want to solve. We don’t have the data yet, so we are going to collect the data first.</p>
<p>We are using the Auto MPG dataset from the <a target="_blank" href="http://archive.ics.uci.edu/ml/datasets/Auto+MPG">UCI Machine Learning Repository</a>. Here is the link to the dataset:</p>
<ul>
<li><a target="_blank" href="http://archive.ics.uci.edu/ml/datasets/Auto+MPG">http://archive.ics.uci.edu/ml/datasets/Auto+MPG</a></li>
</ul>
<blockquote>
<p><em>The data concerns city-cycle fuel consumption in miles per gallon, to be predicted in terms of 3 multivalued discrete and 5 continuous attributes.</em></p>
</blockquote>
<p>Once you have downloaded the data, move it to your project directory, activate your virtualenv, and start the Jupyter local server.</p>
<p>You can download the data into your project from the notebook as well using <code>wget</code> :</p>
<pre><code class="lang-javascript">!wget <span class="hljs-string">"http://archive.ics.uci.edu/ml/machine-learning-databases/auto-mpg/auto-mpg.data"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/2ETE.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The next step is to load this <code>.data</code> file into a pandas datagram. For that, make sure you have pandas and other general use case libraries installed. Import all the general use case libraries like so:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt
<span class="hljs-keyword">import</span> seaborn <span class="hljs-keyword">as</span> sns
</code></pre>
<p>Then read and load the file into a dataframe using the <code>read_csv()</code> method:</p>
<pre><code class="lang-python"><span class="hljs-comment"># defining the column names</span>
cols = [<span class="hljs-string">'MPG'</span>,<span class="hljs-string">'Cylinders'</span>,<span class="hljs-string">'Displacement'</span>,<span class="hljs-string">'Horsepower'</span>,<span class="hljs-string">'Weight'</span>,
                <span class="hljs-string">'Acceleration'</span>, <span class="hljs-string">'Model Year'</span>, <span class="hljs-string">'Origin'</span>]
<span class="hljs-comment"># reading the .data file using pandas</span>
df = pd.read_csv(<span class="hljs-string">'./auto-mpg.data'</span>, names=cols, na_values = <span class="hljs-string">"?"</span>,
                comment = <span class="hljs-string">'\t'</span>,
                sep= <span class="hljs-string">" "</span>,
                skipinitialspace=<span class="hljs-literal">True</span>)
<span class="hljs-comment">#making a copy of the dataframe</span>
data = df.copy()
</code></pre>
<p>Next, look at a few rows of the dataframe and read the description of each attribute from the website. This helps you define the problem statement.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/3ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><strong>Problem Statement —</strong> The data contains the MPG (Mile Per Gallon) variable which is continuous data and tells us about the efficiency of fuel consumption of a vehicle in the 70s and 80s.</p>
<blockquote>
<p>_Our aim here is to predict the MPG value for a vehicl_e, <em>given</em> that <em>we have other attributes of that vehicle.</em></p>
</blockquote>
<h1 id="heading-exploratory-data-analysis-with-pandas-and-numpy"><strong>Exploratory Data Analysis with Pandas and NumPy</strong></h1>
<p>For this rather simple dataset, the exploration is broken down into a series of steps:</p>
<h3 id="heading-check-for-data-type-of-columns">Check for data type of columns</h3>
<pre><code class="lang-python"><span class="hljs-comment">##checking the data info</span>
data.info()
</code></pre>
<h3 id="heading-check-for-null-values">Check for null values.</h3>
<pre><code class="lang-python"><span class="hljs-comment">##checking for all the null values</span>
data.isnull().sum()
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/4ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The horsepower column has 6 missing values. We’ll have to study the column a bit more.</p>
<h3 id="heading-check-for-outliers-in-horsepower-column">Check for outliers in horsepower column</h3>
<pre><code class="lang-python"><span class="hljs-comment">##summary statistics of quantitative variables</span>
data.describe()

<span class="hljs-comment">##looking at horsepower box plot</span>
sns.boxplot(x=data[<span class="hljs-string">'Horsepower'</span>])
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/5ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Since there are a few outliers, we can use the median of the column to impute the missing values using the pandas <code>median()</code> method.</p>
<pre><code class="lang-python"><span class="hljs-comment">##imputing the values with median</span>
median = data[<span class="hljs-string">'Horsepower'</span>].median()
data[<span class="hljs-string">'Horsepower'</span>] = data[<span class="hljs-string">'Horsepower'</span>].fillna(median)
data.info()
</code></pre>
<h3 id="heading-look-for-the-category-distribution-in-categorical-columns">Look for the category distribution in categorical columns</h3>
<pre><code class="lang-python"><span class="hljs-comment">##category distribution</span>

data[<span class="hljs-string">"Cylinders"</span>].value_counts() / len(data)
data[<span class="hljs-string">'Origin'</span>].value_counts()
</code></pre>
<p>The 2 categorical columns are Cylinders and Origin, which only have a few categories of values. Looking at the distribution of the values among these categories will tell us how the data is distributed:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/6ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-plot-for-correlation">Plot for correlation</h3>
<pre><code class="lang-python"><span class="hljs-comment">##pairplots to get an intuition of potential correlations</span>

sns.pairplot(data[[<span class="hljs-string">"MPG"</span>, <span class="hljs-string">"Cylinders"</span>, <span class="hljs-string">"Displacement"</span>, <span class="hljs-string">"Weight"</span>, <span class="hljs-string">"Horsepower"</span>]], diag_kind=<span class="hljs-string">"kde"</span>)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/7ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The pair plot gives you a brief overview of how each variable behaves with respect to every other variable.</p>
<p>For example, the MPG column (our target variable) is negatively correlated with the displacement, weight, and horsepower features.</p>
<h3 id="heading-set-aside-the-test-data-set">Set aside the test data set</h3>
<p>This is one of the first things we should do, as we want to test our final model on unseen/unbiased data.</p>
<p>There are many ways to split the data into training and testing sets but we want our test set to represent the overall population and not just a few specific categories. Thus, instead of using simple and common <code>train_test_split()</code> method from sklearn, we use <strong>stratified sampling.</strong></p>
<blockquote>
<p>Stratified Sampling — We create homogeneous subgroups called strata from the overall population and sample the right number of instances to each stratum to ensure that the test set is representative of the overall population.</p>
</blockquote>
<p>In task 4, we saw how the data is distributed over each category of the Cylinder column. We’re using the Cylinder column to create the strata:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> StratifiedShuffleSplit

split = StratifiedShuffleSplit(n_splits=<span class="hljs-number">1</span>, test_size=<span class="hljs-number">0.2</span>, random_state=<span class="hljs-number">42</span>)
<span class="hljs-keyword">for</span> train_index, test_index <span class="hljs-keyword">in</span> split.split(data, data[<span class="hljs-string">"Cylinders"</span>]):
    strat_train_set = data.loc[train_index]
    strat_test_set = data.loc[test_index]
</code></pre>
<p>Checking for the distribution in training set:</p>
<pre><code class="lang-python"><span class="hljs-comment">##checking for cylinder category distribution in training set</span>

strat_train_set[<span class="hljs-string">'Cylinders'</span>].value_counts() / len(strat_train_set)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/8ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Testing set:</p>
<pre><code class="lang-python">strat_test_set[<span class="hljs-string">"Cylinders"</span>].value_counts() / len(strat_test_set)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/9ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>You can compare these results with the output of <code>train_test_split()</code> to find out which one produces better splits.</p>
<h3 id="heading-checking-the-origin-column">Checking the Origin Column</h3>
<p>The Origin column about the origin of the vehicle has discrete values that look like the code of a country.</p>
<p>To add some complication and make it more explicit, I converted these numbers to strings:</p>
<pre><code class="lang-python"><span class="hljs-comment">##converting integer classes to countries in Origin </span>

columntrain_set[<span class="hljs-string">'Origin'</span>] = train_set[<span class="hljs-string">'Origin'</span>].map({<span class="hljs-number">1</span>: <span class="hljs-string">'India'</span>, <span class="hljs-number">2</span>: <span class="hljs-string">'USA'</span>, <span class="hljs-number">3</span> : <span class="hljs-string">'Germany'</span>})
train_set.sample(<span class="hljs-number">10</span>)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/10ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We’ll have to preprocess this categorical column by one-hot encoding these values:</p>
<pre><code class="lang-python"><span class="hljs-comment">##one hot encoding</span>
train_set = pd.get_dummies(train_set, prefix=<span class="hljs-string">''</span>, prefix_sep=<span class="hljs-string">''</span>)
train_set.head()
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/00.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-testing-for-new-variables-analyze-the-correlation-of-each-variable-with-the-target-variable">Testing for new variables — Analyze the correlation of each variable with the target variable</h3>
<pre><code class="lang-python">
<span class="hljs-comment">## testing new variables by checking their correlation w.r.t. MPG</span>
data[<span class="hljs-string">'displacement_on_power'</span>] = data[<span class="hljs-string">'Displacement'</span>] / data[<span class="hljs-string">'Horsepower'</span>]
data[<span class="hljs-string">'weight_on_cylinder'</span>] = data[<span class="hljs-string">'Weight'</span>] / data[<span class="hljs-string">'Cylinders'</span>]
data[<span class="hljs-string">'acceleration_on_power'</span>] = data[<span class="hljs-string">'Acceleration'</span>] / data[<span class="hljs-string">'Horsepower'</span>]
data[<span class="hljs-string">'acceleration_on_cyl'</span>] = data[<span class="hljs-string">'Acceleration'</span>] / data[<span class="hljs-string">'Cylinders'</span>]

corr_matrix = data.corr()
corr_matrix[<span class="hljs-string">'MPG'</span>].sort_values(ascending=<span class="hljs-literal">False</span>)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/11ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We found <code>acceleration_on_power</code> and <code>acceleration_on_cyl</code> as two new variables which turned out to be more positively correlated than the original variables.</p>
<p>This brings us to the end of the Exploratory Analysis. We are ready to proceed to our next step of preparing the data for Machine Learning.</p>
<h1 id="heading-data-preparation-using-sklearn">Data Preparation using Sklearn</h1>
<p>One of the most important aspects of Data Preparation is that we have to keep automating our steps in the form of functions and classes. This makes it easier for us to integrate the methods and pipelines into the main product.</p>
<p>Here are the major tasks to prepare the data and encapsulate functionalities:</p>
<h3 id="heading-preprocessing-categorical-attribute-converting-the-oval">Preprocessing Categorical Attribute — Converting the Oval</h3>
<pre><code class="lang-python"><span class="hljs-comment">##onehotencoding the categorical values</span>
<span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> OneHotEncoder

cat_encoder = OneHotEncoder()
data_cat_1hot = cat_encoder.fit_transform(data_cat)
data_cat_1hot   <span class="hljs-comment"># returns a sparse matrix</span>

data_cat_1hot.toarray()[:<span class="hljs-number">5</span>]
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/12ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-data-cleaning-imputer">Data Cleaning — Imputer</h3>
<p>We’ll be using the <code>SimpleImputer</code> class from the impute module of the Sklearn library:</p>
<pre><code class="lang-python"><span class="hljs-comment">##handling missing values</span>
<span class="hljs-keyword">from</span> sklearn.impute <span class="hljs-keyword">import</span> SimpleImputer

imputer = SimpleImputer(strategy=<span class="hljs-string">"median"</span>)imputer.fit(num_data)
</code></pre>
<h3 id="heading-attribute-addition-adding-custom-transformation">Attribute Addition — Adding custom transformation</h3>
<p>In order to make changes to datasets and create new variables, sklearn offers the BaseEstimator class. Using it, we can develop new features by defining our own class.</p>
<p>We have created a class to add two new features as found in the EDA step above:</p>
<ul>
<li><p>acc_on_power — Acceleration divided by Horsepower</p>
</li>
<li><p>acc_on_cyl — Acceleration divided by the number of Cylinders</p>
</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.base <span class="hljs-keyword">import</span> BaseEstimator, TransformerMixin

acc_ix, hpower_ix, cyl_ix = <span class="hljs-number">4</span>, <span class="hljs-number">2</span>, <span class="hljs-number">0</span>

<span class="hljs-comment">##custom class inheriting the BaseEstimator and TransformerMixin</span>
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomAttrAdder</span>(<span class="hljs-params">BaseEstimator, TransformerMixin</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, acc_on_power=True</span>):</span>
        self.acc_on_power = acc_on_power  <span class="hljs-comment"># new optional variable</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fit</span>(<span class="hljs-params">self, X, y=None</span>):</span>
        <span class="hljs-keyword">return</span> self  <span class="hljs-comment"># nothing else to do</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform</span>(<span class="hljs-params">self, X</span>):</span>
        acc_on_cyl = X[:, acc_ix] / X[:, cyl_ix] <span class="hljs-comment"># required new variable</span>
        <span class="hljs-keyword">if</span> self.acc_on_power:
            acc_on_power = X[:, acc_ix] / X[:, hpower_ix]
            <span class="hljs-keyword">return</span> np.c_[X, acc_on_power, acc_on_cyl] <span class="hljs-comment"># returns a 2D array</span>

        <span class="hljs-keyword">return</span> np.c_[X, acc_on_cyl]

attr_adder = CustomAttrAdder(acc_on_power=<span class="hljs-literal">True</span>)
data_tr_extra_attrs = attr_adder.transform(data_tr.values)
data_tr_extra_attrs[<span class="hljs-number">0</span>]
</code></pre>
<h3 id="heading-setting-up-data-transformation-pipeline-for-numerical-and-categorical-attributes">Setting up Data Transformation Pipeline for numerical and categorical attributes</h3>
<p>As I said, we want to automate as much as possible. Sklearn offers a great number of classes and methods to develop such automated pipelines of data transformations.</p>
<p>The major transformations are to be performed on numerical columns, so let’s create the numerical pipeline using the <code>Pipeline</code> class:</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">num_pipeline_transformer</span>(<span class="hljs-params">data</span>):</span>
    <span class="hljs-string">'''
    Function to process numerical transformations
    Argument:
        data: original dataframe 
    Returns:
        num_attrs: numerical dataframe
        num_pipeline: numerical pipeline object

    '''</span>
    numerics = [<span class="hljs-string">'float64'</span>, <span class="hljs-string">'int64'</span>]

    num_attrs = data.select_dtypes(include=numerics)

    num_pipeline = Pipeline([
        (<span class="hljs-string">'imputer'</span>, SimpleImputer(strategy=<span class="hljs-string">"median"</span>)),
        (<span class="hljs-string">'attrs_adder'</span>, CustomAttrAdder()),
        (<span class="hljs-string">'std_scaler'</span>, StandardScaler()),
        ])
    <span class="hljs-keyword">return</span> num_attrs, num_pipeline
</code></pre>
<p>In the above code snippet, we have cascaded a set of transformations:</p>
<ul>
<li><p>Imputing Missing Values — using the <code>SimpleImputer</code> class discussed above.</p>
</li>
<li><p>Custom Attribute Addition— using the custom attribute class defined above.</p>
</li>
<li><p>Standard Scaling of each Attribute — always a good practice to scale the values before feeding them to the ML model, using the <code>standardScaler</code> class.</p>
</li>
</ul>
<h2 id="heading-combined-pipeline-for-both-numerical-and-categorical-columns">Combined Pipeline for both Numerical and Categorical columns</h2>
<p>We have the numerical transformation ready. The only categorical column we have is Origin for which we need to one-hot encode the values.</p>
<p>Here’s how we can use the <code>ColumnTransformer</code> class to capture both of these tasks in one go.</p>
<pre><code class="lang-python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pipeline_transformer</span>(<span class="hljs-params">data</span>):</span>
    <span class="hljs-string">'''
    Complete transformation pipeline for both
    nuerical and categorical data.

    Argument:
        data: original dataframe 
    Returns:
        prepared_data: transformed data, ready to use
    '''</span>
    cat_attrs = [<span class="hljs-string">"Origin"</span>]
    num_attrs, num_pipeline = num_pipeline_transformer(data)
    full_pipeline = ColumnTransformer([
        (<span class="hljs-string">"num"</span>, num_pipeline, list(num_attrs)),
        (<span class="hljs-string">"cat"</span>, OneHotEncoder(), cat_attrs),
        ])
    prepared_data = full_pipeline.fit_transform(data)
    <span class="hljs-keyword">return</span> prepared_data
</code></pre>
<p>To the instance, provide the numerical pipeline object created from the function defined above. Then call the <code>OneHotEncoder()</code> class to process the Origin column.</p>
<h2 id="heading-final-automation">Final Automation</h2>
<p>With these classes and functions defined, we now have to integrate them into a single flow which is going to be simply two function calls.</p>
<ol>
<li>Preprocessing the Origin Column to convert integers to Country names:</li>
</ol>
<pre><code class="lang-python"><span class="hljs-comment">##preprocess the Origin column in data</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">preprocess_origin_cols</span>(<span class="hljs-params">df</span>):</span>
    df[<span class="hljs-string">"Origin"</span>] = df[<span class="hljs-string">"Origin"</span>].map({<span class="hljs-number">1</span>: <span class="hljs-string">"India"</span>, <span class="hljs-number">2</span>: <span class="hljs-string">"USA"</span>, <span class="hljs-number">3</span>: <span class="hljs-string">"Germany"</span>})    
    <span class="hljs-keyword">return</span> df
</code></pre>
<ol start="2">
<li>Calling the final <code>pipeline_transformer</code> function defined above:</li>
</ol>
<pre><code class="lang-python"><span class="hljs-comment">##from raw data to processed data in 2 steps</span>

preprocessed_df = preprocess_origin_cols(data)
prepared_data = pipeline_transformer(preprocessed_df)prepared_data
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/13ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Voilà, your data is ready to use in just two steps!</p>
<p>The next step is to start training our ML models.</p>
<h1 id="heading-selecting-and-training-machine-learning-models"><strong>Selecting and Training Machine Learning Models</strong></h1>
<p>Since this is a regression problem, I chose to train the following models:</p>
<ol>
<li><p><strong>Linear Regression</strong></p>
</li>
<li><p><strong>Decision Tree Regressor</strong></p>
</li>
<li><p><strong>Random Forest Regressor</strong></p>
</li>
<li><p><strong>SVM Regressor</strong></p>
</li>
</ol>
<p>I’ll explain the flow for Linear Regression and then you can follow the same for all the others.</p>
<p>It’s a simple <strong>4-step process:</strong></p>
<ol>
<li><p>Create an instance of the model class.</p>
</li>
<li><p>Train the model using the fit() method.</p>
</li>
<li><p>Make predictions by first passing the data through pipeline transformer.</p>
</li>
<li><p>Evaluating the model using Root Mean Squared Error (typical performance metric for regression problems)</p>
</li>
</ol>
<pre><code class="lang-python">
<span class="hljs-keyword">from</span> sklearn.linear_model <span class="hljs-keyword">import</span> LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(prepared_data, data_labels)

<span class="hljs-comment">##testing the predictions with first 5 rows</span>
sample_data = data.iloc[:<span class="hljs-number">5</span>]
sample_labels = data_labels.iloc[:<span class="hljs-number">5</span>]
sample_data_prepared = pipeline_transformer(sample_data)

print(<span class="hljs-string">"Prediction of samples: "</span>, lin_reg.predict(sample_data_prepared))
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/14ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><strong>Evaluating model:</strong></p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> mean_squared_error

mpg_predictions = lin_reg.predict(prepared_data)
lin_mse = mean_squared_error(data_labels, mpg_predictions)
lin_rmse = np.sqrt(lin_mse)lin_rmse
</code></pre>
<p><strong>RMSE for Linear regression: 2.95904</strong></p>
<h1 id="heading-cross-validation-and-hyperparameter-tuning-using-sklearn"><strong>Cross-Validation and Hyperparameter Tuning using Sklearn</strong></h1>
<p>Now, if you perform the same for Decision Tree, you’ll see that you have achieved a 0.0 RMSE value which is not possible – there is no “perfect” Machine Learning Model (we’ve not reached that point yet).</p>
<p><strong>Problem:</strong> we are testing our model on the same data we trained on, which is a problem. Now, we can’t use the test data yet until we finalize our best model that is ready to go into production.</p>
<p><strong>Solution:</strong> <a target="_blank" href="https://scikit-learn.org/stable/modules/cross_validation.html"><strong>Cross-Validation</strong></a></p>
<p><a target="_blank" href="https://scikit-learn.org/stable/modules/cross_validation.html">Scikit-Learn’s K-fold cross-validation</a> feature randomly splits the training set into <code>K</code> distinct subsets called folds. Then it trains and evaluates the model K times, picking a different fold for evaluation every time and training on the other K-1 folds.</p>
<p>The result is an array containing the K evaluation scores. Here’s how I did for 10 folds:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> cross_val_score

scores = cross_val_score(tree_reg, 
                         prepared_data, 
                         data_labels, 
                         scoring=<span class="hljs-string">"neg_mean_squared_error"</span>, 
                         cv = <span class="hljs-number">10</span>)
tree_reg_rmse_scores = np.sqrt(-scores)
</code></pre>
<p>The scoring method gives you negative values to denote errors. So while calculating the square root, we have to add negation explicitly.</p>
<p>For Decision Tree, here is the list of all scores:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/15ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Take the average of these scores:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/16ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-fine-tuning-hyperparameters">Fine-Tuning Hyperparameters</h2>
<p>After testing all the models, you’ll find that RandomForestRegressor has performed the best but it still needs to be fine-tuned.</p>
<p>A model is like a radio station with a lot of knobs to handle and tune. Now, you can either tune all these knobs manually or provide a range of values/combinations that you want to test.</p>
<p>We use GridSearchCV to find out the best combination of hyperparameters for the RandomForest model:</p>
<pre><code class="lang-python">
<span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> GridSearchCV

param_grid = [
    {<span class="hljs-string">'n_estimators'</span>: [<span class="hljs-number">3</span>, <span class="hljs-number">10</span>, <span class="hljs-number">30</span>], <span class="hljs-string">'max_features'</span>: [<span class="hljs-number">2</span>, <span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">8</span>]},
    {<span class="hljs-string">'bootstrap'</span>: [<span class="hljs-literal">False</span>], <span class="hljs-string">'n_estimators'</span>: [<span class="hljs-number">3</span>, <span class="hljs-number">10</span>], <span class="hljs-string">'max_features'</span>: [<span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">4</span>]},
  ]

forest_reg = RandomForestRegressor()

grid_search = GridSearchCV(forest_reg, param_grid,
                           scoring=<span class="hljs-string">'neg_mean_squared_error'</span>,
                           return_train_score=<span class="hljs-literal">True</span>,
                           cv=<span class="hljs-number">10</span>,
                          )

grid_search.fit(prepared_data, data_labels)
</code></pre>
<p>GridSearchCV requires you to pass the parameter grid. This is a python dictionary with parameter names as keys mapped with the list of values you want to test for that param.</p>
<p>We can pass the model, scoring method, and cross-validation folds to it.</p>
<p>Train the model and it returns the best parameters and results for each combination of parameters:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/17ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-check-feature-importance">Check Feature Importance</h2>
<p>We can also check the feature importance by enlisting the features and zipping them up with the best_estimator’s feature importance attribute as follows:</p>
<pre><code class="lang-python"><span class="hljs-comment"># feature importances</span>
feature_importances = grid_search.best_estimator_.feature_importances_

extra_attrs = [<span class="hljs-string">"acc_on_power"</span>, <span class="hljs-string">"acc_on_cyl"</span>]
numerics = [<span class="hljs-string">'float64'</span>, <span class="hljs-string">'int64'</span>]
num_attrs = list(data.select_dtypes(include=numerics))

attrs = num_attrs + extra_attrs
sorted(zip(attrs, feature_importances), reverse=<span class="hljs-literal">True</span>)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/18ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We see that <code>acc_on_power</code>, which is the derived feature, has turned out to be the most important feature.</p>
<p>You might want to keep iterating a few times before finalizing the best configuration.</p>
<p>The model is now ready with the best configuration.</p>
<h1 id="heading-evaluate-the-entire-system">Evaluate the Entire System</h1>
<p>It’s time to evaluate this entire system:</p>
<pre><code class="lang-python"><span class="hljs-comment">##capturing the best configuration</span>
final_model = grid_search.best_estimator_

<span class="hljs-comment">##segregating the target variable from test set</span>
X_test = strat_test_set.drop(<span class="hljs-string">"MPG"</span>, axis=<span class="hljs-number">1</span>)
y_test = strat_test_set[<span class="hljs-string">"MPG"</span>].copy()

<span class="hljs-comment">##preprocessing the test data origin column</span>
X_test_preprocessed = preprocess_origin_cols(X_test)

<span class="hljs-comment">##preparing the data with final transformation</span>
X_test_prepared = pipeline_transformer(X_test_preprocessed)

<span class="hljs-comment">##making final predictions</span>
final_predictions = final_model.predict(X_test_prepared)
final_mse = mean_squared_error(y_test, final_predictions)
final_rmse = np.sqrt(final_mse)
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/19ete.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you want to look at my complete project, here is the <a target="_blank" href="https://github.com/dswh/fuel-consumption-end-to-end-ml">GitHub repository:</a></p>
<p>With that, you have your final model ready to go into production.</p>
<p>For deployment, we save our model into a file using the <code>pickle</code> model and develop a <strong>Flask</strong> web service to be deployed in <strong>Heroku</strong>. Let's see how that works now.</p>
<h1 id="heading-what-do-you-need-to-deploy-the-application">What do you need to deploy the application?</h1>
<p>In order to deploy any trained model, you need the following:</p>
<ul>
<li><p><strong>A trained model ready to deploy</strong> — save the model into a file to be further loaded and used by the web service.</p>
</li>
<li><p><strong>A web service</strong> — that gives a purpose for your model to be used in practice. For our fuel consumption model, it can be using the vehicle configuration to predict its efficiency. We’ll use <strong>Flask</strong> to develop this service.</p>
</li>
<li><p><strong>A cloud service provider</strong> — you need special cloud servers to deploy the application. For simplicity, we are going to use Heroku for this (I'll cover AWS and GCP in other articles).</p>
</li>
</ul>
<p>Let’s get started by looking at each of these processes one by one.</p>
<h1 id="heading-saving-the-trained-model"><strong>Saving the Trained Model</strong></h1>
<p>Once you’re confident enough to take your trained and tested model into the production-ready environment, the first step is to save it into a .h5 or .bin file using a library like <code>pickle</code> .</p>
<p>Make sure you have <code>pickle</code> installed in your environment.</p>
<p>Next, let’s import the module and dump the model into a <code>.bin</code> file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pickle

<span class="hljs-comment">##dump the model into a file</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">"model.bin"</span>, <span class="hljs-string">'wb'</span>) <span class="hljs-keyword">as</span> f_out:
    pickle.dump(final_model, f_out) <span class="hljs-comment"># write final_model in .bin file</span>
    f_out.close()  <span class="hljs-comment"># close the file</span>
</code></pre>
<p>This will save your model in your present working directory unless you specify some other path.</p>
<p>It’s time to test if we are able to use this file to load our model and make predictions. We are going to use the same vehicle config as we defined above:</p>
<pre><code class="lang-python"><span class="hljs-comment">##vehicle config</span>
vehicle_config = {
    <span class="hljs-string">'Cylinders'</span>: [<span class="hljs-number">4</span>, <span class="hljs-number">6</span>, <span class="hljs-number">8</span>],
    <span class="hljs-string">'Displacement'</span>: [<span class="hljs-number">155.0</span>, <span class="hljs-number">160.0</span>, <span class="hljs-number">165.5</span>],
    <span class="hljs-string">'Horsepower'</span>: [<span class="hljs-number">93.0</span>, <span class="hljs-number">130.0</span>, <span class="hljs-number">98.0</span>],
    <span class="hljs-string">'Weight'</span>: [<span class="hljs-number">2500.0</span>, <span class="hljs-number">3150.0</span>, <span class="hljs-number">2600.0</span>],
    <span class="hljs-string">'Acceleration'</span>: [<span class="hljs-number">15.0</span>, <span class="hljs-number">14.0</span>, <span class="hljs-number">16.0</span>],
    <span class="hljs-string">'Model Year'</span>: [<span class="hljs-number">81</span>, <span class="hljs-number">80</span>, <span class="hljs-number">78</span>],
    <span class="hljs-string">'Origin'</span>: [<span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">1</span>]
}
</code></pre>
<p>Let’s load the model from the file:</p>
<pre><code class="lang-python"><span class="hljs-comment">##loading the model from the saved file</span>
<span class="hljs-keyword">with</span> open(<span class="hljs-string">'model.bin'</span>, <span class="hljs-string">'rb'</span>) <span class="hljs-keyword">as</span> f_in:
    model = pickle.load(f_in)
</code></pre>
<p>Make predictions on the <code>vehicle_config</code>:</p>
<pre><code class="lang-python"><span class="hljs-comment">##defined in prev_blog</span>
predict_mpg(vehicle_config, model)


<span class="hljs-comment">##output: array([34.83333333, 18.50666667, 20.56333333])</span>
</code></pre>
<p>The output is the same as we predicted earlier using <code>final_model</code>.</p>
<h1 id="heading-developing-a-web-service"><strong>Developing a web service</strong></h1>
<p>The next step is to package this model into a web service that, when given the data through a POST request, returns the MPG (Miles per Gallon) predictions as a response.</p>
<p>I am using the Flask web framework, a commonly used lightweight framework for developing web services in Python. In my opinion, it is probably the easiest way to implement a web service.</p>
<p>Flask gets you started with very little code and you don’t need to worry about the complexity of handling with HTTP requests and responses.</p>
<p>Here are the steps:</p>
<ul>
<li><p>Create a new directory for your flask application.</p>
</li>
<li><p>Set up a dedicated environment with dependencies installed using pip.</p>
</li>
<li><p>Install the following packages:</p>
</li>
</ul>
<pre><code class="lang-javascript">pandas
numpy
sklearn
flask
gunicorn
seaborn
</code></pre>
<p>The next step is to activate this environment and start developing a simple endpoint to test the application:</p>
<p>Create a new file, <code>main.py</code> and import the flask module:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask
</code></pre>
<p>Create a Flask app by instantiating the Flask class:</p>
<pre><code class="lang-python"><span class="hljs-comment">##creating a flask app and naming it "app"</span>
app = Flask(<span class="hljs-string">'app'</span>)
</code></pre>
<p>Create a route and a function corresponding to it that will return a simple string:</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route('/test', methods=['GET'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">test</span>():</span>
    <span class="hljs-keyword">return</span> <span class="hljs-string">'Pinging Model Application!!'</span>
</code></pre>
<p>The above code makes use of decorators — an advanced Python feature. You can read more about decorators <a target="_blank" href="https://realpython.com/primer-on-python-decorators/">here.</a></p>
<p>We don’t need a deep understanding of decorators, just that adding a decorator <code>@app.route</code> on top of the <code>test()</code> function assigns that web service address to that function.</p>
<p>Now, to run the application we need this last piece of code:</p>
<pre><code class="lang-python"><span class="hljs-keyword">if</span> __name__ == ‘__main__’:
    app.run(debug=<span class="hljs-literal">True</span>, host=’<span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">', port=9696)</span>
</code></pre>
<p>The run method starts our flask application service. The 3 parameters specify:</p>
<ul>
<li><p><code>debug=True</code> — restarts the application automatically when it encounters any change in the code</p>
</li>
<li><p><code>host=’0.0.0.0'</code> — makes the web service public</p>
</li>
<li><p><code>port=9696</code> — the port that we use to access the application</p>
</li>
</ul>
<p>Now, in your terminal run the <code>main.py</code>:</p>
<pre><code class="lang-javascript">python main.py
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/1dp.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Opening the URL <a target="_blank" href="http://http//0.0.0.0:9696/test">http://0.0.0.0:9696/test</a> in your browser will print the response string on the webpage:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/2dp.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>With the application now running, let’s run the model.</p>
<p>Create a new directory <code>model_files</code> to store all the model-related code.</p>
<p>In this directory, create a <code>ml_model.py file</code> which will contain the data preparation code and the predict function we wrote <a target="_blank" href="https://github.com/dswh/fuel-consumption-end-to-end-ml/blob/master/auto_mpg_prediction-part3.ipynb">here</a>.</p>
<p>Copy and paste the libraries you imported earlier in the article and the preprocessing/transformation functions. The file should look like this:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd
<span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt
<span class="hljs-keyword">import</span> seaborn <span class="hljs-keyword">as</span> sns

<span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split
<span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> OneHotEncoder
<span class="hljs-keyword">from</span> sklearn.base <span class="hljs-keyword">import</span> BaseEstimator, TransformerMixin
<span class="hljs-keyword">from</span> sklearn.impute <span class="hljs-keyword">import</span> SimpleImputer

<span class="hljs-keyword">from</span> sklearn.pipeline <span class="hljs-keyword">import</span> Pipeline
<span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> StandardScaler
<span class="hljs-keyword">from</span> sklearn.compose <span class="hljs-keyword">import</span> ColumnTransformer


<span class="hljs-comment">##functions</span>


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">preprocess_origin_cols</span>(<span class="hljs-params">df</span>):</span>
    df[<span class="hljs-string">"Origin"</span>] = df[<span class="hljs-string">"Origin"</span>].map({<span class="hljs-number">1</span>: <span class="hljs-string">"India"</span>, <span class="hljs-number">2</span>: <span class="hljs-string">"USA"</span>, <span class="hljs-number">3</span>: <span class="hljs-string">"Germany"</span>})
    <span class="hljs-keyword">return</span> df


acc_ix, hpower_ix, cyl_ix = <span class="hljs-number">3</span>, <span class="hljs-number">5</span>, <span class="hljs-number">1</span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CustomAttrAdder</span>(<span class="hljs-params">BaseEstimator, TransformerMixin</span>):</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, acc_on_power=True</span>):</span> <span class="hljs-comment"># no *args or **kargs</span>
        self.acc_on_power = acc_on_power
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">fit</span>(<span class="hljs-params">self, X, y=None</span>):</span>
        <span class="hljs-keyword">return</span> self  <span class="hljs-comment"># nothing else to do</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transform</span>(<span class="hljs-params">self, X</span>):</span>
        acc_on_cyl = X[:, acc_ix] / X[:, cyl_ix]
        <span class="hljs-keyword">if</span> self.acc_on_power:
            acc_on_power = X[:, acc_ix] / X[:, hpower_ix]
            <span class="hljs-keyword">return</span> np.c_[X, acc_on_power, acc_on_cyl]

        <span class="hljs-keyword">return</span> np.c_[X, acc_on_cyl]


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">num_pipeline_transformer</span>(<span class="hljs-params">data</span>):</span>
    numerics = [<span class="hljs-string">'float64'</span>, <span class="hljs-string">'int64'</span>]

    num_attrs = data.select_dtypes(include=numerics)

    num_pipeline = Pipeline([
        (<span class="hljs-string">'imputer'</span>, SimpleImputer(strategy=<span class="hljs-string">"median"</span>)),
        (<span class="hljs-string">'attrs_adder'</span>, CustomAttrAdder()),
        (<span class="hljs-string">'std_scaler'</span>, StandardScaler()),
        ])
    <span class="hljs-keyword">return</span> num_attrs, num_pipeline


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">pipeline_transformer</span>(<span class="hljs-params">data</span>):</span>

    cat_attrs = [<span class="hljs-string">"Origin"</span>]
    num_attrs, num_pipeline = num_pipeline_transformer(data)

    full_pipeline = ColumnTransformer([
        (<span class="hljs-string">"num"</span>, num_pipeline, list(num_attrs)),
        (<span class="hljs-string">"cat"</span>, OneHotEncoder(), cat_attrs),
        ])
    full_pipeline.fit_transform(data)
    <span class="hljs-keyword">return</span> full_pipeline    


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict_mpg</span>(<span class="hljs-params">config, model</span>):</span>

    <span class="hljs-keyword">if</span> type(config) == dict:
        df = pd.DataFrame(config)
    <span class="hljs-keyword">else</span>:
        df = config

    preproc_df = preprocess_origin_cols(df)
    print(preproc_df)
    pipeline = pipeline_transformer(preproc_df)
    prepared_df = pipeline.transform(preproc_df)
    print(len(prepared_df[<span class="hljs-number">0</span>]))
    y_pred = model.predict(prepared_df)
    <span class="hljs-keyword">return</span> y_pred
</code></pre>
<p>In the same directory add your saved <code>model.bin</code> file as well.</p>
<p>Now, in the <code>main.py</code> we are going to import the <code>predict_mpg</code> function to make predictions. But to do that we are required to create an empty <code>__init__.py</code> file to tell Python that the directory is a package.</p>
<p>Your directory should have this tree:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/3dp.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Next up, define the <code>predict/</code> route that will accept the <code>vehicle_config</code> from an HTTP POST request and return the predictions using the model and <code>predict_mpg()</code> method.</p>
<p>In your main.py, first import:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> pickle
<span class="hljs-keyword">from</span> flask <span class="hljs-keyword">import</span> Flask, request, jsonify
<span class="hljs-keyword">from</span> model_files.ml_model <span class="hljs-keyword">import</span> predict_mpg
</code></pre>
<p>Then add the <code>predict</code> route and the corresponding function:</p>
<pre><code class="lang-python"><span class="hljs-meta">@app.route('/predict', methods=['POST'])</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">predict</span>():</span>
    vehicle = request.get_json()
    print(vehicle)
    <span class="hljs-keyword">with</span> open(<span class="hljs-string">'./model_files/model.bin'</span>, <span class="hljs-string">'rb'</span>) <span class="hljs-keyword">as</span> f_in:
        model = pickle.load(f_in)
        f_in.close()
    predictions = predict_mpg(vehicle, model)

    result = {
        <span class="hljs-string">'mpg_prediction'</span>: list(predictions)
    }
    <span class="hljs-keyword">return</span> jsonify(result)
</code></pre>
<p>Here, we’ll only be accepting POST request for our function and thus we have <code>methods=[‘POST’]</code> in the decorator.</p>
<ul>
<li><p>First, we capture the data( vehicle_config) from our request using the <code>get_json()</code> method and store it in the variable vehicle.</p>
</li>
<li><p>Then we load the trained model into the model variable from the file we have in the <code>model_files</code> folder.</p>
</li>
<li><p>Now, we make the predictions by calling the predict_mpg function and passing the <code>vehicle</code> and <code>model</code>.</p>
</li>
<li><p>We create a JSON response of this array returned in the predictions variable and return this JSON as the method response.</p>
</li>
</ul>
<p>We can test this route using Postman or the <code>requests</code> package and then start the server running the main.py. Then in your notebook, add this code to send a POST request with the <code>vehicle_config</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests

url = “http://localhost:<span class="hljs-number">9696</span>/predict<span class="hljs-string">"
r = requests.post(url, json = vehicle_config)
r.text.strip()

##output: '{"</span>mpg_predictions<span class="hljs-string">":[34.60333333333333,19.32333333333333,14.893333333333333]}'</span>
</code></pre>
<p>Great! Now, comes the last part: this same functionality should work when deployed on a remote server.</p>
<h1 id="heading-deploying-the-application-on-heroku">Deploying the application on Heroku</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/4dp.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>To deploy this flask application on Heroku, you need to follow these very simple steps:</p>
<ol>
<li><p>Create a <code>Procfile</code> in the main directory — this contains the command to get the run the application on the server.</p>
</li>
<li><p>Add the following in your <code>Procfile</code>:</p>
</li>
</ol>
<pre><code class="lang-javascript">web: gunicorn wsgi:app
</code></pre>
<p>We are using gunicorn (installed earlier) to deploy the application:</p>
<blockquote>
<p><a target="_blank" href="https://gunicorn.org/"><em>Gunicorn</em></a> <em>is a pure-Python HTTP server for WSGI applications. It allows you to run any Python application concurrently by running multiple Python processes within a single dyno. It provides a perfect balance of performance, flexibility, and configuration simplicity.</em></p>
</blockquote>
<p>Now, create a <code>wsgi.py</code> file and add:</p>
<pre><code class="lang-python"><span class="hljs-comment">##importing the app from main file</span>
<span class="hljs-keyword">from</span> main <span class="hljs-keyword">import</span> app

<span class="hljs-keyword">if</span> __name__ == “__main__”: 
    app.run()
</code></pre>
<p>Make sure you delete the run code from the <code>main.py</code> .</p>
<p>Write all the python dependencies into <code>requirements.txt</code>.</p>
<p>You can use <code>pip freeze &gt; requirements.txt</code> or simply put the above-mentioned list of packages + any other package that your application is using.</p>
<p>Now, using the terminal,</p>
<ul>
<li><p>initialize an empty git repository,</p>
</li>
<li><p>add the files to the staging area,</p>
</li>
<li><p>and commit files to the local repository:</p>
</li>
</ul>
<pre><code class="lang-javascript">$ git init 
$ git add .
$ git commit -m <span class="hljs-string">"Initial Commit"</span>
</code></pre>
<p>Next, c<a target="_blank" href="https://signup.heroku.com/">reate a Heroku account</a> if you haven’t already. Then login to the Heroku CLI:</p>
<pre><code class="lang-javascript">heroku login
</code></pre>
<p>Approve the login from the browser as the page pops up.</p>
<p>Now create a flask app:</p>
<pre><code class="lang-javascript">heroku create &lt;name <span class="hljs-keyword">of</span> your app&gt;
</code></pre>
<p>I named it <code>mpg-flask-app</code>. It will create a flask app and will give us a URL on which the app will be deployed.</p>
<p>Finally, push all your code to Heroku remote:</p>
<p><code>$ git push heroku master</code></p>
<p>And Voilà! Your web service is now deployed on <a target="_blank" href="https://mpg-flask-app.herokuapp.com/predict">https://mpg-flask-app.herokuapp.com/predict</a>.</p>
<p>Again, test the endpoint using the <code>request</code> package by sending the same vehicle config:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/08/5dp.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>With that, you have all the major skills you need to start building more complex ML applications.</p>
<p>You can refer to <a target="_blank" href="https://github.com/dswh/fuel-consumption-end-to-end-ml">my GitHub repository</a> for this project.</p>
<p>And you can develop this entire project along with me:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/7nAFhUl70Lk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<h1 id="heading-next-steps">Next Steps</h1>
<p>This was still a simple project. For the next steps, I’d recommend you take up a more complex dataset – maybe pick up <a target="_blank" href="https://archive.ics.uci.edu/ml/datasets.php?format=&amp;task=cla&amp;att=&amp;area=&amp;numAtt=&amp;numIns=&amp;type=&amp;sort=nameUp&amp;view=table">a classification problem</a> and repeat <a target="_blank" href="https://towardsdatascience.com/task-cheatsheet-for-almost-every-machine-learning-project-d0946861c6d0?source=---------2------------------">these tasks</a> until deployment.</p>
<h3 id="heading-check-out-data-science-with-harshithttpswwwyoutubecomcdatasciencewithharshitsubconfirmation1-my-youtube-channel">Check out <a target="_blank" href="https://www.youtube.com/c/DataSciencewithHarshit?sub_confirmation=1">Data Science with Harshit</a> — My YouTube Channel</h3>
<p>Here is the complete tutorial (in playlist form) on my YouTube channel where you can follow me while working on this project.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/7nAFhUl70Lk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p> </p>
<p>With this channel, I plan to roll out a couple of <a target="_blank" href="https://towardsdatascience.com/hitchhikers-guide-to-learning-data-science-2cc3d963b1a2?source=---------8------------------">series covering the entire data science space</a>. Here is why you should be subscribing to the <a target="_blank" href="https://www.youtube.com/channel/UCH-xwLTKQaABNs2QmGxK2bQ">channel</a>:</p>
<ul>
<li><p>These series would cover all the required/demanded quality tutorials on each of the topics and subtopics like <a target="_blank" href="https://towardsdatascience.com/python-fundamentals-for-data-science-6c7f9901e1c8?source=---------5------------------">Python fundamentals for Data Science</a>.</p>
</li>
<li><p>Explained <a target="_blank" href="https://towardsdatascience.com/practical-reasons-to-learn-mathematics-for-data-science-1f6caec161ea?source=---------9------------------">Mathematics and derivations</a> of why we do what we do in ML and Deep Learning.</p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=a2pkZCleJwM&amp;t=2s">Podcasts with Data Scientists and Engineers</a> at Google, Microsoft, Amazon, etc, and CEOs of big data-driven companies.</p>
</li>
<li><p><a target="_blank" href="https://towardsdatascience.com/building-covid-19-analysis-dashboard-using-python-and-voila-ee091f65dcbb?source=---------2------------------">Projects and instructions</a> to implement the topics learned so far. Learn about new certifications, Bootcamp, and resources to crack those certifications like this <a target="_blank" href="https://youtu.be/yapSsspJzAw"><strong>TensorFlow Developer Certificate Exam by Google.</strong></a></p>
</li>
</ul>
<p>If this tutorial was helpful, you should check out my data science and machine learning courses on <a target="_blank" href="https://www.wiplane.com/">Wiplane Academy</a>. They are comprehensive yet compact and helps you build a solid foundation of work to showcase.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Develop and Deploy Micro-Frontends with Single-SPA ]]>
                </title>
                <description>
                    <![CDATA[ By Tyler Hawkins Micro-frontends are the future of front end web development. Inspired by microservices, which allow you to break up your backend into smaller pieces, micro-frontends allow you to build, test, and deploy pieces of your frontend app in... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/developing-and-deploying-micro-frontends-with-single-spa/</link>
                <guid isPermaLink="false">66d4617438f2dc3808b79113</guid>
                
                    <category>
                        <![CDATA[ architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Travis CI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 03 Aug 2020 11:00:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2020/07/single-spa-3.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Tyler Hawkins</p>
<p>Micro-frontends are the future of front end web development.</p>
<p>Inspired by microservices, which allow you to break up your backend into smaller pieces, micro-frontends allow you to build, test, and deploy pieces of your frontend app independently of each other. </p>
<p>Depending on the micro-frontend framework you choose, you can even have multiple micro-frontend apps — written in React, Angular, Vue, or anything else — coexisting peacefully together in the same larger app.</p>
<p>In this article, we’re going to develop an app composed of micro-frontends using <a target="_blank" href="https://single-spa.js.org/">single-spa</a> and deploy it to <a target="_blank" href="https://www.heroku.com/">Heroku</a>. </p>
<p>We’ll set up continuous integration using <a target="_blank" href="https://travis-ci.org/">Travis CI</a>. Each CI pipeline will bundle the JavaScript for a micro-frontend app and then upload the resulting build artifacts to <a target="_blank" href="https://aws.amazon.com/s3/">AWS S3</a>. </p>
<p>Finally, we’ll make an update to one of the micro-frontend apps and see how it can be deployed to production independently of the other micro-frontend apps.</p>
<h1 id="heading-overview-of-the-demo-app">Overview of the Demo App</h1>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe1.png" alt="Demo app — end result" width="600" height="400" loading="lazy">
<em>Demo app — end result</em></p>
<p>Before we discuss the step-by-step instructions, let’s get a quick overview of what makes up the demo app. This app is composed of four sub-apps:</p>
<ol>
<li>A <a target="_blank" href="https://github.com/thawkin3/single-spa-demo-root-config">container app</a> that serves as the main page container and coordinates the mounting and unmounting of the micro-frontend apps</li>
<li>A <a target="_blank" href="https://github.com/thawkin3/single-spa-demo-nav">micro-frontend navbar app</a> that’s always present on the page</li>
<li>A <a target="_blank" href="https://github.com/thawkin3/single-spa-demo-page-1">micro-frontend “page 1” app</a> that only shows when active</li>
<li>A <a target="_blank" href="https://github.com/thawkin3/single-spa-demo-page-2">micro-frontend “page 2” app</a> that also only shows when active</li>
</ol>
<p>These four apps all live in separate repos, available on GitHub, which I’ve linked to above.</p>
<p>The end result is fairly simple in terms of the user interface, but, to be clear, the user interface isn’t the point here. </p>
<p>If you’re following along on your own machine, by the end of this article you too will have all the underlying infrastructure necessary to get started with your own micro-frontend app.</p>
<p>Alright, grab your scuba gear, because it’s time to dive in!</p>
<h1 id="heading-creating-the-container-app">Creating the Container App</h1>
<p>To generate the apps for this demo, we’re going to use a command-line interface (CLI) tool called <a target="_blank" href="https://single-spa.js.org/docs/create-single-spa/">create-single-spa</a>. The version of create-single-spa at the time of writing is 1.10.0, and the version of single-spa installed via the CLI is 4.4.2.</p>
<p>We’ll follow these steps to create the container app (also sometimes called the root config):</p>
<pre><code class="lang-bash">mkdir single-spa-demo

<span class="hljs-built_in">cd</span> single-spa-demo

mkdir single-spa-demo-root-config

<span class="hljs-built_in">cd</span> single-spa-demo-root-config

npx create-single-spa
</code></pre>
<p>We’ll then follow the CLI prompts:</p>
<ol>
<li>Select “single spa root config”</li>
<li>Select “yarn” or “npm” (I chose “yarn”)</li>
<li>Enter an organization name (I used “thawkin3,” but it can be whatever you want)</li>
</ol>
<p>Great! Now, if you check out the <code>single-spa-demo-root-config</code> directory, you should see a skeleton root config app. We'll customize this in a bit, but first let's also use the CLI tool to create our other three micro-frontend apps.</p>
<h1 id="heading-creating-the-micro-frontend-apps">Creating the Micro-Frontend Apps</h1>
<p>To generate our first micro-frontend app, the navbar, we’ll follow these steps:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> ..

mkdir single-spa-demo-nav

<span class="hljs-built_in">cd</span> single-spa-demo-nav

npx create-single-spa
</code></pre>
<p>We’ll then follow the CLI prompts:</p>
<ol>
<li>Select “single-spa application / parcel”</li>
<li>Select “react”</li>
<li>Select “yarn” or “npm” (I chose “yarn”)</li>
<li>Enter an organization name, the same one you used when creating the root config app (“thawkin3” in my case)</li>
<li>Enter a project name (I used “single-spa-demo-nav”)</li>
</ol>
<p>Now that we’ve created the navbar app, we can follow these same steps to create our two page apps. But, we’ll replace each place we see “single-spa-demo-nav” with “single-spa-demo-page-1” the first time through and then with “single-spa-demo-page-2” the second time through.</p>
<p>At this point we’ve generated all four apps that we need: one container app and three micro-frontend apps. Now it’s time to hook our apps together.</p>
<h1 id="heading-registering-the-micro-frontend-apps-with-the-container-app">Registering the Micro-Frontend Apps with the Container App</h1>
<p>As stated before, one of the container app’s primary responsibilities is to coordinate when each app is “active” or not. In other words, it handles when each app should be shown or hidden. </p>
<p>To help the container app understand when each app should be shown, we provide it with what are called “activity functions.” Each app has an activity function that simply returns a boolean, true or false, for whether or not the app is currently active.</p>
<p>Inside the <code>single-spa-demo-root-config</code> directory, in the <code>activity-functions.js</code> file, we'll write the following activity functions for our three micro-frontend apps.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">prefix</span>(<span class="hljs-params">location, ...prefixes</span>) </span>{
  <span class="hljs-keyword">return</span> prefixes.some(
    <span class="hljs-function"><span class="hljs-params">prefix</span> =&gt;</span> location.href.indexOf(<span class="hljs-string">`<span class="hljs-subst">${location.origin}</span>/<span class="hljs-subst">${prefix}</span>`</span>) !== <span class="hljs-number">-1</span>
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">nav</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// The nav is always active</span>
  <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">page1</span>(<span class="hljs-params">location</span>) </span>{
  <span class="hljs-keyword">return</span> prefix(location, <span class="hljs-string">'page1'</span>);
}

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">page2</span>(<span class="hljs-params">location</span>) </span>{
  <span class="hljs-keyword">return</span> prefix(location, <span class="hljs-string">'page2'</span>);
}
</code></pre>
<p>Next, we need to register our three micro-frontend apps with single-spa. To do that, we use the <code>registerApplication</code> function. This function accepts a minimum of three arguments: the app name, a method to load the app, and an activity function to determine when the app is active.</p>
<p>Inside the <code>single-spa-demo-root-config</code> directory, in the <code>root-config.js</code> file, we'll add the following code to register our apps:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { registerApplication, start } <span class="hljs-keyword">from</span> <span class="hljs-string">"single-spa"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> isActive <span class="hljs-keyword">from</span> <span class="hljs-string">"./activity-functions"</span>;

registerApplication(
  <span class="hljs-string">"@thawkin3/single-spa-demo-nav"</span>,
  <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">"@thawkin3/single-spa-demo-nav"</span>),
  isActive.nav
);

registerApplication(
  <span class="hljs-string">"@thawkin3/single-spa-demo-page-1"</span>,
  <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">"@thawkin3/single-spa-demo-page-1"</span>),
  isActive.page1
);

registerApplication(
  <span class="hljs-string">"@thawkin3/single-spa-demo-page-2"</span>,
  <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">"@thawkin3/single-spa-demo-page-2"</span>),
  isActive.page2
);

start();
</code></pre>
<p>Now that we’ve set up the activity functions and registered our apps, the last step before we can get this running locally is to update the local import map inside the <code>index.ejs</code> file in the same directory. </p>
<p>We'll add the following code inside the <code>head</code> tag to specify where each app can be found when running locally:</p>
<pre><code class="lang-javascript">&lt;% <span class="hljs-keyword">if</span> (isLocal) { %&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"systemjs-importmap"</span>&gt;</span><span class="javascript">
    {
      <span class="hljs-string">"imports"</span>: {
        <span class="hljs-string">"@thawkin3/root-config"</span>: <span class="hljs-string">"http://localhost:9000/root-config.js"</span>,
        <span class="hljs-string">"@thawkin3/single-spa-demo-nav"</span>: <span class="hljs-string">"http://localhost:9001/thawkin3-single-spa-demo-nav.js"</span>,
        <span class="hljs-string">"@thawkin3/single-spa-demo-page-1"</span>: <span class="hljs-string">"http://localhost:9002/thawkin3-single-spa-demo-page-1.js"</span>,
        <span class="hljs-string">"@thawkin3/single-spa-demo-page-2"</span>: <span class="hljs-string">"http://localhost:9003/thawkin3-single-spa-demo-page-2.js"</span>
      }
    }
  </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span>
&lt;% } %&gt;
</code></pre>
<p>Each app contains its own startup script, which means that each app will be running locally on its own development server during local development. As you can see, our navbar app is on port 9001, our page 1 app is on port 9002, and our page 2 app is on port 9003.</p>
<p>With those three steps taken care of, let’s try out our app.</p>
<h1 id="heading-test-run-for-running-locally">Test Run for Running Locally</h1>
<p>To get our app running locally, we can follow these steps:</p>
<ol>
<li>Open four terminal tabs, one for each app</li>
<li>For the root config, in the <code>single-spa-demo-root-config</code> directory: <code>yarn start</code> (runs on port 9000 by default)</li>
<li>For the nav app, in the <code>single-spa-demo-nav</code> directory: <code>yarn start --port 9001</code></li>
<li>For the page 1 app, in the <code>single-spa-demo-page-1</code> directory: <code>yarn start --port 9002</code></li>
<li>For the page 2 app, in the <code>single-spa-demo-page-2</code> directory: <code>yarn start --port 9003</code></li>
</ol>
<p>Now, we’ll navigate in the browser to <a target="_blank" href="http://localhost:9000/">http://localhost:9000</a> to view our app.</p>
<p>We should see… some text! Super exciting.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe2.png" alt="Demo app — main page" width="600" height="400" loading="lazy">
<em>Demo app — main page</em></p>
<p>On our main page, the navbar is showing because the navbar app is always active.</p>
<p>Now, let’s navigate to <a target="_blank" href="http://localhost:9000/page1.">http://localhost:9000/page1.</a> As shown in our activity functions above, we’ve specified that the page 1 app should be active (shown) when the URL path begins with “page1.” So, this activates the page 1 app, and we should see the text for both the navbar and the page 1 app now.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe3.png" alt="Demo app — page 1 route" width="600" height="400" loading="lazy">
<em>Demo app — page 1 route</em></p>
<p>One more time, let’s now navigate to <a target="_blank" href="http://localhost:9000/page2.">http://localhost:9000/page2.</a> As expected, this activates the page 2 app, so we should see the text for the navbar and the page 2 app now.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe4.png" alt="Demo app — page 2 route" width="600" height="400" loading="lazy">
<em>Demo app — page 2 route</em></p>
<h1 id="heading-making-minor-tweaks-to-the-apps">Making Minor Tweaks to the Apps</h1>
<p>So far our app isn’t very exciting to look at, but we do have a working micro-frontend setup running locally. If you aren’t cheering in your seat right now, you should be!</p>
<p>Let’s make some minor improvements to our apps so they look and behave a little nicer.</p>
<h3 id="heading-specifying-the-mount-containers">Specifying the Mount Containers</h3>
<p>First, if you refresh your page over and over when viewing the app, you may notice that sometimes the apps load out of order, with the page app appearing above the navbar app. </p>
<p>This is because we haven’t actually specified <em>where</em> each app should be mounted. The apps are simply loaded by <a target="_blank" href="https://github.com/systemjs/systemjs">SystemJS</a>, and then whichever app finishes loading fastest gets appended to the page first.</p>
<p>We can fix this by specifying a mount container for each app when we register them.</p>
<p>In our <code>index.ejs</code> file that we worked in previously, let's add some HTML to serve as the main content containers for the page:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"nav-container"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">main</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"page-1-container"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"page-2-container"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
</code></pre>
<p>Then, in our <code>root-config.js</code> file where we've registered our apps, let's provide a fourth argument to each function call that includes the DOM element where we'd like to mount each app:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { registerApplication, start } <span class="hljs-keyword">from</span> <span class="hljs-string">"single-spa"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> isActive <span class="hljs-keyword">from</span> <span class="hljs-string">"./activity-functions"</span>;

registerApplication(
  <span class="hljs-string">"@thawkin3/single-spa-demo-nav"</span>,
  <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">"@thawkin3/single-spa-demo-nav"</span>),
  isActive.nav,
  { <span class="hljs-attr">domElement</span>: <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'nav-container'</span>) }
);

registerApplication(
  <span class="hljs-string">"@thawkin3/single-spa-demo-page-1"</span>,
  <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">"@thawkin3/single-spa-demo-page-1"</span>),
  isActive.page1,
  { <span class="hljs-attr">domElement</span>: <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'page-1-container'</span>) }
);

registerApplication(
  <span class="hljs-string">"@thawkin3/single-spa-demo-page-2"</span>,
  <span class="hljs-function">() =&gt;</span> System.import(<span class="hljs-string">"@thawkin3/single-spa-demo-page-2"</span>),
  isActive.page2,
  { <span class="hljs-attr">domElement</span>: <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'page-2-container'</span>) }
);

start();
</code></pre>
<p>Now, the apps will always be mounted to a specific and predictable location. Nice!</p>
<h3 id="heading-styling-the-app">Styling the App</h3>
<p>Next, let’s style up our app a bit. Plain black text on a white background isn’t very interesting to look at.</p>
<p>In the <code>single-spa-demo-root-config</code> directory, in the <code>index.ejs</code> file again, we can add some basic styles for the whole app by pasting the following CSS at the bottom of the <code>head</code> tag:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
  <span class="hljs-selector-tag">body</span>, <span class="hljs-selector-tag">html</span> { <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>; <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>; <span class="hljs-attribute">font-size</span>: <span class="hljs-number">16px</span>; <span class="hljs-attribute">font-family</span>: Arial, Helvetica, sans-serif; <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>; }
  <span class="hljs-selector-tag">body</span> { <span class="hljs-attribute">display</span>: flex; <span class="hljs-attribute">flex-direction</span>: column; }
  * { <span class="hljs-attribute">box-sizing</span>: border-box; }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
</code></pre>
<p>Next, we can style our navbar app by finding the <code>single-spa-demo-nav</code> directory, creating a <code>root.component.css</code> file, and adding the following CSS:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.nav</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: row;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#000</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
}

<span class="hljs-selector-class">.link</span> {
  <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
  <span class="hljs-attribute">text-decoration</span>: none;
}

<span class="hljs-selector-class">.link</span><span class="hljs-selector-pseudo">:hover</span>,
<span class="hljs-selector-class">.link</span><span class="hljs-selector-pseudo">:focus</span> {
  <span class="hljs-attribute">color</span>: <span class="hljs-number">#1098f7</span>;
}
</code></pre>
<p>We can then update the <code>root.component.js</code> file in the same directory to import the CSS file and apply those classes and styles to our HTML. We'll also change the navbar content to actually contain two links so we can navigate around the app by clicking the links instead of entering a new URL in the browser's address bar.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./root.component.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Root</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"nav"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/page1"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"link"</span>&gt;</span>
        Page 1
      <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/page2"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"link"</span>&gt;</span>
        Page 2
      <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></span>
  );
}
</code></pre>
<p>We’ll follow a similar process for the page 1 and page 2 apps as well. We’ll create a <code>root.component.css</code> file for each app in their respective project directories and update the <code>root.component.js</code> files for both apps too.</p>
<p>For the page 1 app, the changes look like this:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.container1</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#1098f7</span>;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">3rem</span>;
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./root.component.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Root</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container1"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Page 1 App<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>And for the page 2 app, the changes look like this:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.container2</span> {
  <span class="hljs-attribute">background</span>: <span class="hljs-number">#9e4770</span>;
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">3rem</span>;
}
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./root.component.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Root</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container2"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Page 2 App<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<h3 id="heading-adding-react-router">Adding React Router</h3>
<p>The last small change we’ll make is to add <a target="_blank" href="https://reacttraining.com/react-router/">React Router</a> to our app. Right now the two links we’ve placed in the navbar are just normal anchor tags, so navigating from page to page causes a page refresh. Our app will feel much smoother if the navigation is handled client-side with React Router.</p>
<p>To use React Router, we’ll first need to install it. From the terminal, in the <code>single-spa-demo-nav</code> directory, we'll install React Router using yarn by entering <code>yarn add react-router-dom</code>. (Or if you're using npm, you can enter <code>npm install react-router-dom</code>.)</p>
<p>Then, in the <code>single-spa-demo-nav</code> directory in the <code>root.component.js</code> file, we'll replace our anchor tags with React Router's <code>Link</code> components like so:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { BrowserRouter, Link } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-router-dom"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./root.component.css"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Root</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">BrowserRouter</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"nav"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/page1"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"link"</span>&gt;</span>
          Page 1
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/page2"</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"link"</span>&gt;</span>
          Page 2
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">BrowserRouter</span>&gt;</span></span>
  );
}
</code></pre>
<p>Cool. That looks and works much better!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe5.png" alt="Demo app — styled and using React Router" width="600" height="400" loading="lazy">
<em>Demo app — styled and using React Router</em></p>
<h1 id="heading-getting-ready-for-production">Getting Ready for Production</h1>
<p>At this point we have everything we need to continue working on the app while running it locally. But how do we get it hosted somewhere publicly available? </p>
<p>There are several possible approaches we can take using our tools of choice, but the main tasks are:</p>
<ol>
<li>to have somewhere we can upload our build artifacts, like a CDN, and</li>
<li>to automate this process of uploading artifacts each time we merge new code into the master branch.</li>
</ol>
<p>For this article, we’re going to use AWS S3 to store our assets, and we’re going to use Travis CI to run a build job and an upload job as part of a continuous integration pipeline.</p>
<p>Let’s get the S3 bucket set up first.</p>
<h3 id="heading-setting-up-the-aws-s3-bucket">Setting up the AWS S3 Bucket</h3>
<p>It should go without saying, but you’ll need an AWS account if you’re following along here. </p>
<p>If we are the root user on our AWS account, we can create a new IAM user that has programmatic access only. This means we’ll be given an access key ID and a secret access key from AWS when we create the new user. We’ll want to store these in a safe place since we’ll need them later. </p>
<p>Finally, this user should be given permissions to work with S3 only, so that the level of access is limited if our keys were to fall into the wrong hands.</p>
<p>AWS has some great resources for <a target="_blank" href="https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html">best practices with access keys</a> and <a target="_blank" href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html">managing access keys for IAM users</a> that would be well worth checking out if you’re unfamiliar with how to do this.</p>
<p>Next we need to create an S3 bucket. S3 stands for Simple Storage Service and is essentially a place to upload and store files hosted on Amazon’s servers. A bucket is simply a directory. </p>
<p>I’ve named my bucket “single-spa-demo,” but you can name yours whatever you’d like. You can follow the AWS guides for <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingBucket.html">how to create a new bucket</a> for more info.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe6.png" alt="AWS S3 bucket" width="600" height="400" loading="lazy">
<em>AWS S3 bucket</em></p>
<p>Once we have our bucket created, it’s also important to make sure the bucket is public and that <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html#how-do-i-enable-cors">CORS (cross-origin resource sharing) is enabled for our bucket</a> so that we can access and use our uploaded assets in our app. </p>
<p>In the permissions for our bucket, we can add the following CORS configuration rules:</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">CORSConfiguration</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">CORSRule</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">AllowedOrigin</span>&gt;</span>*<span class="hljs-tag">&lt;/<span class="hljs-name">AllowedOrigin</span>&gt;</span>
   <span class="hljs-tag">&lt;<span class="hljs-name">AllowedMethod</span>&gt;</span>GET<span class="hljs-tag">&lt;/<span class="hljs-name">AllowedMethod</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">CORSRule</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">CORSConfiguration</span>&gt;</span>
</code></pre>
<p>In the AWS console, it ends up looking like this after we hit Save:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe7.png" alt="Image" width="600" height="400" loading="lazy">
<em>CORS configuration</em></p>
<h3 id="heading-creating-a-travis-ci-job-to-upload-artifacts-to-aws-s3">Creating a Travis CI Job to Upload Artifacts to AWS S3</h3>
<p>Now that we have somewhere to upload files, let’s set up an automated process that will take care of uploading new JavaScript bundles each time we merge new code into the master branch for any of our repos.</p>
<p>To do this, we’re going to use <a target="_blank" href="https://travis-ci.org/">Travis CI</a>. As mentioned earlier, each app lives in its own repo on GitHub, so we have four GitHub repos to work with. We can <a target="_blank" href="https://docs.travis-ci.com/user/tutorial/#to-get-started-with-travis-ci-using-github">integrate Travis CI with each of our repos</a> and set up continuous integration pipelines for each one.</p>
<p>To configure Travis CI for any given project, we create a <code>.travis.yml</code> file in the project's root directory. Let's create that file in the <code>single-spa-demo-root-config</code> directory and insert the following code:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">language:</span> <span class="hljs-string">node_js</span>
<span class="hljs-attr">node_js:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">node</span>
<span class="hljs-attr">script:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">yarn</span> <span class="hljs-string">build</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">echo</span> <span class="hljs-string">"Commit sha - $TRAVIS_COMMIT"</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">dist/@thawkin3/root-config/$TRAVIS_COMMIT</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">mv</span> <span class="hljs-string">dist/*.*</span> <span class="hljs-string">dist/@thawkin3/root-config/$TRAVIS_COMMIT/</span>
<span class="hljs-attr">deploy:</span>
  <span class="hljs-attr">provider:</span> <span class="hljs-string">s3</span>
  <span class="hljs-attr">access_key_id:</span> <span class="hljs-string">"$AWS_ACCESS_KEY_ID"</span>
  <span class="hljs-attr">secret_access_key:</span> <span class="hljs-string">"$AWS_SECRET_ACCESS_KEY"</span>
  <span class="hljs-attr">bucket:</span> <span class="hljs-string">"single-spa-demo"</span>
  <span class="hljs-attr">region:</span> <span class="hljs-string">"us-west-2"</span>
  <span class="hljs-attr">cache-control:</span> <span class="hljs-string">"max-age=31536000"</span>
  <span class="hljs-attr">acl:</span> <span class="hljs-string">"public_read"</span>
  <span class="hljs-attr">local_dir:</span> <span class="hljs-string">dist</span>
  <span class="hljs-attr">skip_cleanup:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">on:</span>
    <span class="hljs-attr">branch:</span> <span class="hljs-string">master</span>
</code></pre>
<p>This implementation is what I came up with after reviewing the <a target="_blank" href="https://docs.travis-ci.com/user/deployment-v2/providers/s3/">Travis CI docs for AWS S3 uploads</a> and a <a target="_blank" href="https://github.com/single-spa/import-map-deployer/blob/master/examples/ci-for-javascript-repo/travis-digital-ocean-spaces/.travis.yml">single-spa Travis CI example config</a>.</p>
<p>Because we don’t want our AWS secrets exposed in our GitHub repo, we can store those as environment variables. You can place environment variables and their secret values within the Travis CI web console for anything that you want to keep private, so that’s where the <code>.travis.yml</code> file gets those values from.</p>
<p>Now, when we commit and push new code to the master branch, the Travis CI job will run, which will build the JavaScript bundle for the app and then upload those assets to S3. To verify, we can check out the AWS console to see our newly uploaded files:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe8.png" alt="Uploaded files as a result of a Travis CI job" width="600" height="400" loading="lazy">
<em>Uploaded files as a result of a Travis CI job</em></p>
<p>Neat! So far so good. Now we need to implement the same Travis CI configuration for our other three micro-frontend apps, but swapping out the directory names in the <code>.travis.yml</code> file as needed. After following the same steps and merging our code, we now have four directories created in our S3 bucket, one for each repo.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe9.png" alt="Four directories within our S3 bucket" width="600" height="400" loading="lazy">
<em>Four directories within our S3 bucket</em></p>
<h1 id="heading-creating-an-import-map-for-production">Creating an Import Map for Production</h1>
<p>Let’s recap what we’ve done so far. We have four apps, all living in separate GitHub repos. Each repo is set up with Travis CI to run a job when code is merged into the master branch, and that job handles uploading the build artifacts into an S3 bucket. </p>
<p>With all that in one place, there’s still one thing missing: How do these new build artifacts get referenced in our container app? In other words, even though we’re pushing up new JavaScript bundles for our micro-frontends with each new update, the new code isn’t actually used in our container app yet!</p>
<p>If we think back to how we got our app running locally, we used an import map. This import map is simply JSON that tells the container app where each JavaScript bundle can be found. </p>
<p>But, our import map from earlier was specifically used for running the app locally. Now we need to create an import map that will be used in the production environment.</p>
<p>If we look in the <code>single-spa-demo-root-config</code> directory, in the <code>index.ejs</code> file, we see this line:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"systemjs-importmap"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://storage.googleapis.com/react.microfrontends.app/importmap.json"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<p>Opening up that URL in the browser reveals an import map that looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"imports"</span>: {
    <span class="hljs-attr">"react"</span>: <span class="hljs-string">"https://cdn.jsdelivr.net/npm/react@16.13.1/umd/react.production.min.js"</span>,
    <span class="hljs-attr">"react-dom"</span>: <span class="hljs-string">"https://cdn.jsdelivr.net/npm/react-dom@16.13.1/umd/react-dom.production.min.js"</span>,
    <span class="hljs-attr">"single-spa"</span>: <span class="hljs-string">"https://cdn.jsdelivr.net/npm/single-spa@5.5.3/lib/system/single-spa.min.js"</span>,
    <span class="hljs-attr">"@react-mf/root-config"</span>: <span class="hljs-string">"https://react.microfrontends.app/root-config/e129469347bb89b7ff74bcbebb53cc0bb4f5e27f/react-mf-root-config.js"</span>,
    <span class="hljs-attr">"@react-mf/navbar"</span>: <span class="hljs-string">"https://react.microfrontends.app/navbar/631442f229de2401a1e7c7835dc7a56f7db606ea/react-mf-navbar.js"</span>,
    <span class="hljs-attr">"@react-mf/styleguide"</span>: <span class="hljs-string">"https://react.microfrontends.app/styleguide/f965d7d74e99f032c27ba464e55051ae519b05dd/react-mf-styleguide.js"</span>,
    <span class="hljs-attr">"@react-mf/people"</span>: <span class="hljs-string">"https://react.microfrontends.app/people/dd205282fbd60b09bb3a937180291f56e300d9db/react-mf-people.js"</span>,
    <span class="hljs-attr">"@react-mf/api"</span>: <span class="hljs-string">"https://react.microfrontends.app/api/2966a1ca7799753466b7f4834ed6b4f2283123c5/react-mf-api.js"</span>,
    <span class="hljs-attr">"@react-mf/planets"</span>: <span class="hljs-string">"https://react.microfrontends.app/planets/5f7fc62b71baeb7a0724d4d214565faedffd8f61/react-mf-planets.js"</span>,
    <span class="hljs-attr">"@react-mf/things"</span>: <span class="hljs-string">"https://react.microfrontends.app/things/7f209a1ed9ac9690835c57a3a8eb59c17114bb1d/react-mf-things.js"</span>,
    <span class="hljs-attr">"rxjs"</span>: <span class="hljs-string">"https://cdn.jsdelivr.net/npm/@esm-bundle/rxjs@6.5.5/system/rxjs.min.js"</span>,
    <span class="hljs-attr">"rxjs/operators"</span>: <span class="hljs-string">"https://cdn.jsdelivr.net/npm/@esm-bundle/rxjs@6.5.5/system/rxjs-operators.min.js"</span>
  }
}
</code></pre>
<p>That import map was the default one provided as an example when we used the CLI to generate our container app. What we need to do now is replace this example import map with an import map that actually references the bundles we’re using.</p>
<p>So, using the original import map as a template, we can create a new file called <code>importmap.json</code>, place it <em>outside of our repos</em> and add JSON that looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"imports"</span>: {
    <span class="hljs-attr">"react"</span>: <span class="hljs-string">"https://cdn.jsdelivr.net/npm/react@16.13.0/umd/react.production.min.js"</span>,
    <span class="hljs-attr">"react-dom"</span>: <span class="hljs-string">"https://cdn.jsdelivr.net/npm/react-dom@16.13.0/umd/react-dom.production.min.js"</span>,
    <span class="hljs-attr">"single-spa"</span>: <span class="hljs-string">"https://cdn.jsdelivr.net/npm/single-spa@5.5.1/lib/system/single-spa.min.js"</span>,
    <span class="hljs-attr">"@thawkin3/root-config"</span>: <span class="hljs-string">"https://single-spa-demo.s3-us-west-2.amazonaws.com/%40thawkin3/root-config/179ba4f2ce4d517bf461bee986d1026c34967141/root-config.js"</span>,
    <span class="hljs-attr">"@thawkin3/single-spa-demo-nav"</span>: <span class="hljs-string">"https://single-spa-demo.s3-us-west-2.amazonaws.com/%40thawkin3/single-spa-demo-nav/f0e9d35392ea0da8385f6cd490d6c06577809f16/thawkin3-single-spa-demo-nav.js"</span>,
    <span class="hljs-attr">"@thawkin3/single-spa-demo-page-1"</span>: <span class="hljs-string">"https://single-spa-demo.s3-us-west-2.amazonaws.com/%40thawkin3/single-spa-demo-page-1/4fd417ee3faf575fcc29d17d874e52c15e6f0780/thawkin3-single-spa-demo-page-1.js"</span>,
    <span class="hljs-attr">"@thawkin3/single-spa-demo-page-2"</span>: <span class="hljs-string">"https://single-spa-demo.s3-us-west-2.amazonaws.com/%40thawkin3/single-spa-demo-page-2/8c58a825c1552aab823bcbd5bdd13faf2bd4f9dc/thawkin3-single-spa-demo-page-2.js"</span>
  }
}
</code></pre>
<p>You’ll note that the first three imports are for shared dependencies: react, react-dom, and single-spa. That way we don’t have four copies of React in our app causing bloat and longer download times. Next, we have imports for each of our four apps. The URL is simply the URL for each uploaded file in S3 (called an “object” in AWS terminology).</p>
<p>Now that we have this file created, we can manually upload it to our bucket in S3 through the AWS console. </p>
<p><strong>Note</strong>: This is a pretty important and interesting caveat when using single-spa: The import map doesn’t actually live anywhere in source control or in any of the git repos. That way, the import map can be updated on the fly without requiring checked-in changes in a repo. We’ll come back to this concept in a little bit.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe10.png" alt="Import map manually uploaded to the S3 bucket" width="600" height="400" loading="lazy">
<em>Import map manually uploaded to the S3 bucket</em></p>
<p>Finally, we can now reference this new file in our <code>index.ejs</code> file instead of referencing the original import map.</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"systemjs-importmap"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"//single-spa-demo.s3-us-west-2.amazonaws.com/%40thawkin3/importmap.json"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre>
<h1 id="heading-creating-a-production-server">Creating a Production Server</h1>
<p>We are getting closer to having something up and running in production! We’re going to host this demo on Heroku, so in order to do that, we’ll need to create a simple Node.js and <a target="_blank" href="https://expressjs.com/">Express</a> server to serve our file.</p>
<p>First, in the <code>single-spa-demo-root-config</code> directory, we'll install express by running <code>yarn add express</code> (or <code>npm install express</code>). Next, we'll add a file called <code>server.js</code> that contains a small amount of code for starting up an express server and serving our main <code>index.html</code> file.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>);
<span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">"path"</span>);
<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">5000</span>;

express()
  .use(express.static(path.join(__dirname, <span class="hljs-string">"dist"</span>)))
  .get(<span class="hljs-string">"*"</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
    res.sendFile(<span class="hljs-string">"index.html"</span>, { <span class="hljs-attr">root</span>: <span class="hljs-string">"dist"</span> });
  })
  .listen(PORT, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Listening on <span class="hljs-subst">${PORT}</span>`</span>));
</code></pre>
<p>Finally, we’ll update the NPM scripts in our <code>package.json</code> file to differentiate between running the server in development mode and running the server in production mode.</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
  <span class="hljs-attr">"build"</span>: <span class="hljs-string">"webpack --mode=production"</span>,
  <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"eslint src"</span>,
  <span class="hljs-attr">"prettier"</span>: <span class="hljs-string">"prettier --write './**'"</span>,
  <span class="hljs-attr">"start:dev"</span>: <span class="hljs-string">"webpack-dev-server --mode=development --port 9000 --env.isLocal=true"</span>,
  <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node server.js"</span>,
  <span class="hljs-attr">"test"</span>: <span class="hljs-string">"jest"</span>
}
</code></pre>
<h1 id="heading-deploying-to-heroku">Deploying to Heroku</h1>
<p>Now that we have a production server ready, let’s get this thing deployed to Heroku! In order to do so, you’ll need to have a <a target="_blank" href="https://signup.heroku.com/">Heroku account created</a>, the <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli">Heroku CLI</a> installed, and be logged in. Deploying to Heroku is as easy as 1–2–3:</p>
<ol>
<li>In the <code>single-spa-demo-root-config</code> directory: <code>heroku create thawkin3-single-spa-demo</code> (changing that last argument to a unique name to be used for your Heroku app)</li>
<li><code>git push heroku master</code></li>
<li><code>heroku open</code></li>
</ol>
<p>And with that, we are up and running in production. Upon running the <code>heroku open</code> command, you should see your app open in your browser. Try navigating between pages using the nav links to see the different micro-frontend apps mount and unmount.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe11.png" alt="Demo app — up and running in production" width="600" height="400" loading="lazy">
<em>Demo app — up and running in production</em></p>
<h1 id="heading-making-updates">Making Updates</h1>
<p>At this point, you may be asking yourself, “All that work for this? Why?” And you’d be right. Sort of. This is a lot of work, and we don’t have much to show for it, at least not visually. But, we’ve laid the groundwork for whatever app improvements we’d like. </p>
<p>The setup cost for any microservice or micro-frontend is often a lot higher than the setup cost for a monolith; it’s not until later that you start to reap the rewards.</p>
<p>So let’s start thinking about future modifications. Let’s say that it’s now five or ten years later, and your app has grown. A lot. And, in that time, a hot new framework has been released, and you’re dying to re-write your entire app using that new framework. </p>
<p>When working with a monolith, this would likely be a years-long effort and may be nearly impossible to accomplish. But, with micro-frontends, you could swap out technologies one piece of the app at a time, allowing you to slowly and smoothly transition to a new tech stack. Magic!</p>
<p>Or, you may have one piece of your app that changes frequently and another piece of your app that is rarely touched. While making updates to the volatile app, wouldn’t it be nice if you could just leave the legacy code alone? </p>
<p>With a monolith, it’s possible that changes you make in one place of your app may affect other sections of your app. What if you modified some stylesheets that multiple sections of the monolith were using? Or what if you updated a dependency that was used in many different places? </p>
<p>With a micro-frontend approach, you can leave those worries behind, refactoring and updating one app where needed while leaving legacy apps alone.</p>
<p>But, how do you make these kinds of updates? Or updates of any sort, really? </p>
<p>Right now we have our production import map in our <code>index.ejs</code> file, but it's just pointing to the file we manually uploaded to our S3 bucket. If we wanted to release some new changes right now, we'd need to push new code for one of the micro-frontends, get a new build artifact, and then manually update the import map with a reference to the new JavaScript bundle.</p>
<p>Is there a way we could automate this? Yes!</p>
<h1 id="heading-updating-one-of-the-apps">Updating One of the Apps</h1>
<p>Let’s say we want to update our page 1 app to have different text showing. In order to automate the deployment of this change, we can update our CI pipeline to not only build an artifact and upload it to our S3 bucket, but to also update the import map to reference the new URL for the latest JavaScript bundle.</p>
<p>Let’s start by updating our <code>.travis.yml</code> file like so:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">language:</span> <span class="hljs-string">node_js</span>
<span class="hljs-attr">node_js:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">node</span>
<span class="hljs-attr">env:</span>
  <span class="hljs-attr">global:</span>
    <span class="hljs-comment"># include $HOME/.local/bin for `aws`</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">PATH=$HOME/.local/bin:$PATH</span>
<span class="hljs-attr">before_install:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">pyenv</span> <span class="hljs-string">global</span> <span class="hljs-number">3.7</span><span class="hljs-number">.1</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">-U</span> <span class="hljs-string">pip</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">pip</span> <span class="hljs-string">install</span> <span class="hljs-string">awscli</span>
<span class="hljs-attr">script:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">yarn</span> <span class="hljs-string">build</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">echo</span> <span class="hljs-string">"Commit sha - $TRAVIS_COMMIT"</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">dist/@thawkin3/root-config/$TRAVIS_COMMIT</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">mv</span> <span class="hljs-string">dist/*.*</span> <span class="hljs-string">dist/@thawkin3/root-config/$TRAVIS_COMMIT/</span>
<span class="hljs-attr">deploy:</span>
  <span class="hljs-attr">provider:</span> <span class="hljs-string">s3</span>
  <span class="hljs-attr">access_key_id:</span> <span class="hljs-string">"$AWS_ACCESS_KEY_ID"</span>
  <span class="hljs-attr">secret_access_key:</span> <span class="hljs-string">"$AWS_SECRET_ACCESS_KEY"</span>
  <span class="hljs-attr">bucket:</span> <span class="hljs-string">"single-spa-demo"</span>
  <span class="hljs-attr">region:</span> <span class="hljs-string">"us-west-2"</span>
  <span class="hljs-attr">cache-control:</span> <span class="hljs-string">"max-age=31536000"</span>
  <span class="hljs-attr">acl:</span> <span class="hljs-string">"public_read"</span>
  <span class="hljs-attr">local_dir:</span> <span class="hljs-string">dist</span>
  <span class="hljs-attr">skip_cleanup:</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">on:</span>
    <span class="hljs-attr">branch:</span> <span class="hljs-string">master</span>
<span class="hljs-attr">after_deploy:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">chmod</span> <span class="hljs-string">+x</span> <span class="hljs-string">after_deploy.sh</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">"./after_deploy.sh"</span>
</code></pre>
<p>The main changes here are adding a global environment variable, installing the AWS CLI, and adding an <code>after_deploy</code> script as part of the pipeline. This references an <code>after_deploy.sh</code> file that we need to create. The contents will be:</p>
<pre><code class="lang-sh"><span class="hljs-built_in">echo</span> <span class="hljs-string">"Downloading import map from S3"</span>
aws s3 cp s3://single-spa-demo/@thawkin3/importmap.json importmap.json
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Updating import map to point to new version of @thawkin3/root-config"</span>
node update-importmap.mjs
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Uploading new import map to S3"</span>
aws s3 cp importmap.json s3://single-spa-demo/@thawkin3/importmap.json --cache-control <span class="hljs-string">'public, must-revalidate, max-age=0'</span> --acl <span class="hljs-string">'public-read'</span>
<span class="hljs-built_in">echo</span> <span class="hljs-string">"Deployment successful"</span>
</code></pre>
<p>This file downloads the existing import map from S3, modifies it to reference the new build artifact, and then re-uploads the updated import map to S3. To handle the actual updating of the import map file’s contents, we use a custom script that we’ll add in a file called <code>update-importmap.mjs.</code></p>
<pre><code class="lang-javascript"><span class="hljs-comment">// Note that this file requires node@13.2.0 or higher (or the --experimental-modules flag)</span>
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;
<span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">"path"</span>;
<span class="hljs-keyword">import</span> https <span class="hljs-keyword">from</span> <span class="hljs-string">"https"</span>;

<span class="hljs-keyword">const</span> importMapFilePath = path.resolve(process.cwd(), <span class="hljs-string">"importmap.json"</span>);
<span class="hljs-keyword">const</span> importMap = <span class="hljs-built_in">JSON</span>.parse(fs.readFileSync(importMapFilePath));
<span class="hljs-keyword">const</span> url = <span class="hljs-string">`https://single-spa-demo.s3-us-west-2.amazonaws.com/%40thawkin3/root-config/<span class="hljs-subst">${process.env.TRAVIS_COMMIT}</span>/root-config.js`</span>;

https
  .get(url, <span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> {
    <span class="hljs-comment">// HTTP redirects (301, 302, etc) not currently supported, but could be added</span>
    <span class="hljs-keyword">if</span> (res.statusCode &gt;= <span class="hljs-number">200</span> &amp;&amp; res.statusCode &lt; <span class="hljs-number">300</span>) {
      <span class="hljs-keyword">if</span> (
        res.headers[<span class="hljs-string">"content-type"</span>] &amp;&amp;
        res.headers[<span class="hljs-string">"content-type"</span>].toLowerCase().trim() ===
          <span class="hljs-string">"application/javascript"</span>
      ) {
        <span class="hljs-keyword">const</span> moduleName = <span class="hljs-string">`@thawkin3/root-config`</span>;
        importMap.imports[moduleName] = url;
        fs.writeFileSync(importMapFilePath, <span class="hljs-built_in">JSON</span>.stringify(importMap, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));
        <span class="hljs-built_in">console</span>.log(
          <span class="hljs-string">`Updated import map for module <span class="hljs-subst">${moduleName}</span>. New url is <span class="hljs-subst">${url}</span>.`</span>
        );
      } <span class="hljs-keyword">else</span> {
        urlNotDownloadable(
          url,
          <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Content-Type response header must be application/javascript`</span>)
        );
      }
    } <span class="hljs-keyword">else</span> {
      urlNotDownloadable(
        url,
        <span class="hljs-built_in">Error</span>(<span class="hljs-string">`HTTP response status was <span class="hljs-subst">${res.statusCode}</span>`</span>)
      );
    }
  })
  .on(<span class="hljs-string">"error"</span>, <span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {
    urlNotDownloadable(url, err);
  });

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">urlNotDownloadable</span>(<span class="hljs-params">url, err</span>) </span>{
  <span class="hljs-keyword">throw</span> <span class="hljs-built_in">Error</span>(
    <span class="hljs-string">`Refusing to update import map - could not download javascript file at url <span class="hljs-subst">${url}</span>. Error was '<span class="hljs-subst">${err.message}</span>'`</span>
  );
}
</code></pre>
<p>Note that we need to make these changes for these three files in all of our GitHub repos so that each one is able to update the import map after creating a new build artifact. </p>
<p>The file contents will be nearly identical for each repo, but we’ll need to change the app names or URL paths to the appropriate values for each one.</p>
<h3 id="heading-a-side-note-on-the-import-map">A Side Note on the Import Map</h3>
<p>Earlier I mentioned that the import map file we manually uploaded to S3 doesn’t actually live anywhere in any of our GitHub repos or in any of our checked-in code. If you’re like me, this probably seems really odd! Shouldn’t everything be in source control?</p>
<p>The reason it’s not in source control is so that our CI pipeline can handle updating the import map with each new micro-frontend app release. </p>
<p>If the import map were in source control, making an update to one micro-frontend app would require changes in two repos: the micro-frontend app repo where the change is made, and the root config repo where the import map would be checked in. This sort of setup would invalidate one of micro-frontend architecture’s main benefits, which is that each app can be deployed completely independent of the other apps. </p>
<p>In order to achieve some level of source control on the import map, we can always use S3’s versioning feature for our bucket.</p>
<h1 id="heading-moment-of-truth">Moment of Truth</h1>
<p>With those modifications to our CI pipelines in place, it’s time for the final moment of truth: Can we update one of our micro-frontend apps, deploy it independently, and then see those changes take effect in production without having to touch any of our other apps?</p>
<p>In the <code>single-spa-demo-page-1</code> directory, in the <code>root.component.js</code> file, let's change the text from "Page 1 App" to "Page 1 App - UPDATED!" Next, let's commit that change and push and merge it to master. </p>
<p>This will kick off the Travis CI pipeline to build the new page 1 app artifact and then update the import map to reference that new file URL.</p>
<p>If we then navigate in our browser to <a target="_blank" href="https://thawkin3-single-spa-demo.herokuapp.com/page1">https://thawkin3-single-spa-demo.herokuapp.com/page1</a>, we’ll now see… drum roll please… our updated app!</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/07/mfe12.png" alt="Demo app — successfully updating one of the micro-frontend apps" width="600" height="400" loading="lazy">
<em>Demo app — successfully updating one of the micro-frontend apps</em></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>I said it before, and I’ll say it again: <strong>Micro-frontends are the future of frontend web development.</strong> </p>
<p>The benefits are massive, including independent deployments, independent areas of ownership, faster build and test times, and the ability to mix and match various frameworks if needed. </p>
<p>There are some drawbacks, such as the initial set up cost and the complexity of maintaining a distributed architecture, but I strongly believe the benefits outweigh the costs.</p>
<p>Single-spa makes micro-frontend architecture easy. Now you, too, can go break up the monolith!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Learn How to Deploy a Full Stack Web App with Heroku ]]>
                </title>
                <description>
                    <![CDATA[ By M. S. Farzan Building a full stack web app is no mean feat. Learning to deploy one to production so that you can share it with the world can add an additional layer of complexity. In this new tutorial, we'll learn how to deploy a full stack MEVN a... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-deploy-a-full-stack-web-app-with-heroku/</link>
                <guid isPermaLink="false">66d851ef8acc348be2a441c2</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Back end development  ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Front-end Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mongoose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ REST ]]>
                    </category>
                
                    <category>
                        <![CDATA[ REST API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vue ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 22 Jun 2020 01:32:32 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9a22740569d1a4ca23bc.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By M. S. Farzan</p>
<p>Building a full stack web app is no mean feat. Learning to deploy one to production so that you can share it with the world can add an additional layer of complexity.</p>
<p>In this new tutorial, we'll learn how to deploy a <a target="_blank" href="https://www.freecodecamp.org/news/build-a-full-stack-mevn-app/">full stack MEVN app</a> to Heroku!</p>
<p>Follow along (46 minute watch):</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/rUSjVri4I30" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<p>You can follow the original tutorial to build this app <a target="_blank" href="https://www.freecodecamp.org/news/build-a-full-stack-mevn-app/">here</a>.</p>
<p>Happy coding!</p>
<p>If you enjoyed this article, please consider <a target="_blank" href="https://www.nightpathpub.com/">checking out my games and books</a>, <a target="_blank" href="https://www.youtube.com/msfarzan?sub_confirmation=1">subscribing to my YouTube channel</a>, or <a target="_blank" href="https://discord.gg/RF6k3nB">joining the <em>Entromancy</em> Discord</a>.</p>
<p>M. S. Farzan, Ph.D. has written and worked for high-profile video game companies and editorial websites such as Electronic Arts, Perfect World Entertainment, Modus Games, and MMORPG.com, and has served as the Community Manager for games like <em>Dungeons &amp; Dragons Neverwinter</em> and <em>Mass Effect: Andromeda</em>. He is the Creative Director and Lead Game Designer of <em><a target="_blank" href="https://www.nightpathpub.com/rpg">Entromancy: A Cyberpunk Fantasy RPG</a></em> and author of <em><a target="_blank" href="http://nightpathpub.com/books">The Nightpath Trilogy</a></em>. Find M. S. Farzan on Twitter <a target="_blank" href="https://twitter.com/sominator">@sominator</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Three Things to Consider Before Deploying Your First Full Stack App ]]>
                </title>
                <description>
                    <![CDATA[ By M. S. Farzan Building a full stack app is no small endeavor, and deploying it comes with its own host of things to consider. I'm a tabletop game developer, and recently deployed a simple roleplaying game tracker that uses the M-E-V-N stack (you ca... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/3-things-to-consider-before-deploying-your-first-full-stack-app/</link>
                <guid isPermaLink="false">66d851dd836d1162d8815c3c</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Azure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ containers ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ mongoose ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ REST ]]>
                    </category>
                
                    <category>
                        <![CDATA[ vue ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 17 Mar 2020 21:12:22 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9c1d740569d1a4ca3007.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By M. S. Farzan</p>
<p>Building a full stack app is no small endeavor, and deploying it comes with its own host of things to consider.</p>
<p>I'm a <a target="_blank" href="https://www.nightpathpub.com/entromancy">tabletop game</a> developer, and recently deployed a simple <a target="_blank" href="https://mevn-rpg-app.herokuapp.com/">roleplaying game tracker</a> that uses the <a target="_blank" href="https://www.mongodb.com/">M</a>-<a target="_blank" href="https://expressjs.com/">E</a>-<a target="_blank" href="https://vuejs.org/">V</a>-<a target="_blank" href="https://nodejs.org/en/">N</a> stack (you can follow my tutorial to create your own app <a target="_blank" href="https://www.freecodecamp.org/news/build-a-full-stack-mevn-app/">here</a>).  </p>
<p>In deploying the app, I came across three key takeaways that may be useful as you begin considering the best way to bring your projects from development to production. </p>
<p>You can check out the code to my app on <a target="_blank" href="https://github.com/sominator/mevn-rpg-app">GitHub</a>, and I should mention that it includes Chad Carteret's <a target="_blank" href="https://codepen.io/retractedhack/pen/gPLpWe">very cool CSS statblock</a> in prettifying what's otherwise very basic HTML.</p>
<p>If you're thinking of following the same path to deployment as I have here, be sure to check out the official documentation on <a target="_blank" href="https://devcenter.heroku.com/articles/deploying-nodejs">Heroku</a>, the <a target="_blank" href="https://cli.vuejs.org/guide/deployment.html">Vue CLI</a>, and <a target="_blank" href="https://medium.com/netscape/deploying-a-vue-js-2-x-app-to-heroku-in-5-steps-tutorial-a69845ace489">this tutorial</a> by Nick Manning.</p>
<p>You'll also want to take a look at Will Abramson's <a target="_blank" href="https://www.freecodecamp.org/news/lessons-learned-from-deploying-my-first-full-stack-web-application-34f94ec0a286/">article on a similar topic</a>.</p>
<p>On to deployment!</p>
<h2 id="heading-your-front-end-and-back-end-can-be-deployed-together-or-separately-depending-on-your-apps-complexity">Your front end and back end can be deployed together or separately, depending on your app's complexity.</h2>
<p>One snag that seems to appear immediately when considering production is the structural question of how to deploy the front and back ends of your app.</p>
<p>Should the client (or static files) live in the same place as the server and database? Or should they be separate, with the front end making HTTP requests from elsewhere to the back end using <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a>?</p>
<p>The answer is yes! Or no. Maybe??</p>
<p>For better or worse, there's no one-size-fits-all solution to this question, as it will likely depend on your app's architecture and complexity. In the roleplaying game tracker that I linked to above, I have the whole stack running on a single Heroku <a target="_blank" href="https://www.heroku.com/dynos">dyno</a>, with the following folder structure:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Folder-Structure.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>All of the front and back end files live in the same place, with the Vue client built for production in a folder located at /client/dist.</p>
<p>In server.js, along with a bunch of database and routing code, there's a little line that says:</p>
<pre><code class="lang-javascript">server.use(serveStatic(__dirname + <span class="hljs-string">"/client/dist"</span>));
</code></pre>
<p>In Express, this tells the app to serve my static client files from a particular folder, and enables me to run the front and back ends all within the same environment. If you're deploying a similarly simple app, this type of solution might work for you as well.  </p>
<p>Conversely, and depending on your project's complexity, you may have to separate the front and back ends and treat them as separate applications, which is no big deal. In the app above, my client is making calls to static API endpoints that are handled by the server, like this:</p>
<pre><code class="lang-javascript">getQuests: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    axios
        .get(<span class="hljs-string">'https://mevn-rpg-app.herokuapp.com/quests'</span>)
        .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> (<span class="hljs-built_in">this</span>.questData = response.data))                   
 }
</code></pre>
<p>Technically, my client could be making those requests from anywhere - even a static GitHub Pages site. This type of solution can help separate your app into two distinct entities to tackle, which is sometimes better than trying to cram the whole project into one location.</p>
<p>One note: if you're going to be making cross-origin HTTP requests - that is, requests from a client that lives on a separate domain from the API or server - you'll need to become familiar with <a target="_blank" href="https://en.wikipedia.org/wiki/Cross-origin_resource_sharing">CORS</a>.  You can read more about it in <a target="_blank" href="https://www.freecodecamp.org/news/i-built-a-web-api-with-express-flask-aspnet/">this article</a>.  </p>
<h2 id="heading-your-code-will-need-to-change-to-support-a-production-environment">Your code will need to change to support a production environment.</h2>
<p>When you're knee deep in the development process, it can be easy to lose track of how much of your code depends on local files or other data.</p>
<p>Consider the following in a locally-running server.js:</p>
<pre><code class="lang-javascript">server.listen(<span class="hljs-number">3000</span>, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Server started!"</span>));
</code></pre>
<p>On a local machine, the code just asks the server to listen on port 3000 and log to the console that we're ready for liftoff.</p>
<p>In a production environment, the server has no concept of where the "localhost" should be, or to whose port 3000 it should be listening. With this example, you'd have to change your code to something like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> port = process.env.PORT || <span class="hljs-number">3000</span>;

server.listen(port, <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Server started!"</span>));
</code></pre>
<p>The above instructs the server to instead listen at port 3000 of the <em>process</em> that's currently running, wherever that might be (check out <a target="_blank" href="https://codeburst.io/process-env-what-it-is-and-why-when-how-to-use-it-effectively-505d0b2831e7">this article</a> for further reading on this topic).</p>
<p>Similarly, in my app, I have several modules that need to be imported by one another to function:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/03/Folder-Structure-Expanded.PNG" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In /routes/Quests.js, for example, I have a router that tells the server what to do when receiving API requests to interact with quest-related items in the database. The router needs to import a <a target="_blank" href="https://mongoosejs.com/docs/guide.html">Mongoose schema</a> from /models/quest.js to function properly. If the application were running locally, we could just say:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Quest = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/quest'</span>);
</code></pre>
<p>Pretty simple! Yet, unfortunately, our server won't know where to find the root directory of our project once deployed. In Express, we'd change our code to something like:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">'path'</span>);
<span class="hljs-keyword">const</span> Quest = <span class="hljs-built_in">require</span>(path.join(__dirname, <span class="hljs-string">'../models/quest'</span>));
</code></pre>
<p>Your particular case might be different, depending on your language and framework(s), but you'll need to get specific about what your code looks like in a production environment rather than in your local development environment.</p>
<p>Additionally, you're probably already familiar with whatever bundler you're using for your front end (e.g., <a target="_blank" href="https://webpack.js.org/">webpack</a>), and will want to build your client for production to optimize it for deployment.</p>
<h2 id="heading-you-have-a-multitude-of-deployment-platforms-from-which-to-choose">You have a multitude of deployment platforms from which to choose.</h2>
<p>If you've deployed a front end website or some other type of static app, you might be familiar with just pushing your files to some remote repository and calling it a day.</p>
<p>Deploying a full stack app (or even just a back end) is eminently more complex. You'll need a dedicated server, or something that emulates one, to respond to the HTTP requests that it will be receiving and work with an online database.</p>
<p>There are a number of services that will do this very thing for you, and the spectrum ranges based on price, scalability, complexity, and other factors.</p>
<p>There's a bunch of articles out there that compare <a target="_blank" href="https://en.wikipedia.org/wiki/Platform_as_a_service">PaaS</a> options for deployment, but here are some thoughts as you consider platforms for your first project:</p>
<ul>
    <li><strong>Heroku</strong>: If you have a small project like mine or just want to learn about deployment, a good first step could be <a href="https://www.heroku.com/">Heroku</a>.</li>
    <li><strong>AWS, Docker, and Kubernetes</strong>: If you're seeking a career in full stack web development or DevOps, now's a good time to familiarize yourself with <a href="https://aws.amazon.com/">Amazon Web Services</a> and/or container platforms like <a href="https://www.docker.com/">Docker</a> and <a href="https://kubernetes.io/">Kubernetes</a>.</li>
    <li><strong>Azure</strong>: If you're a C# or .NET developer, <a href="https://azure.microsoft.com/en-us/">Azure</a> appears to be a seamless way to deploy your apps without having to leave the safety of the Microsoft ecosystem.</li>
</ul>

<p>There are, of course, several other options out there, and your particular use-case scenario might depend on pricing or the specific feature sets that are on offer.</p>
<p>Additionally, you'll want to consider any addons that will be necessary to replicate your app's functionality in a production environment. My roleplaying game tracker, for example, uses MongoDB, but the production version certainly can't use the little database on my local machine! Instead, I've used the <a target="_blank" href="https://elements.heroku.com/addons/mongolab">mLab</a> Heroku addon to get the live site up and running with the same functionality as in my development environment.</p>
<p>Your app's success, as well as your own progress as a full stack web developer, depend on your ability to consider deployment options and create a successful pipeline for production. With a little research, I'm certain that you can find the best solution that fits all of your app's needs.</p>
<p>Happy coding!</p>
<p>If you enjoyed this article, please consider <a target="_blank" href="https://www.nightpathpub.com/">checking out my games and books</a>, <a target="_blank" href="https://www.youtube.com/msfarzan?sub_confirmation=1">subscribing to my YouTube channel</a>, or <a target="_blank" href="https://discord.gg/RF6k3nB">joining the <em>Entromancy</em> Discord</a>.</p>
<p>M. S. Farzan, Ph.D. has written and worked for high-profile video game companies and editorial websites such as Electronic Arts, Perfect World Entertainment, Modus Games, and MMORPG.com, and has served as the Community Manager for games like <em>Dungeons &amp; Dragons Neverwinter</em> and <em>Mass Effect: Andromeda</em>. He is the Creative Director and Lead Game Designer of <em><a target="_blank" href="https://www.nightpathpub.com/rpg">Entromancy: A Cyberpunk Fantasy RPG</a></em> and author of <em><a target="_blank" href="http://nightpathpub.com/books">The Nightpath Trilogy</a></em>. Find M. S. Farzan on Twitter <a target="_blank" href="https://twitter.com/sominator">@sominator</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to deploy your app to the web using Express.js and Heroku ]]>
                </title>
                <description>
                    <![CDATA[ By Peter Gleeson If you are new to the world of web development, you will spend a lot of time learning how to build static sites with HTML, CSS and JavaScript. You might then start learning how to use popular frameworks such as React, VueJS or Angula... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-deploy-your-site-using-express-and-heroku/</link>
                <guid isPermaLink="false">66d4609f230dff016690585b</guid>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express JS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTML ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ npm ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Tutorial ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Applications ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 02 Mar 2020 11:55:00 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9c9c56740569d1a4ca317c.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Peter Gleeson</p>
<p>If you are new to the world of web development, you will spend a lot of time learning how to build static sites with HTML, CSS and JavaScript.</p>
<p>You might then start learning how to use popular frameworks such as <a target="_blank" href="https://reactjs.org/">React</a>, <a target="_blank" href="https://vuejs.org/">VueJS</a> or <a target="_blank" href="https://angular.io/">Angular</a>.</p>
<p>But after trying out a few new ideas and running some sites locally, you might wonder how to actually deploy your site or app. And as it turns out, it can sometimes be difficult to know where to start.</p>
<p>Personally, I find running an Express server hosted on Heroku one of the simplest ways to get going. This article will show you how to do this.</p>
<p><a target="_blank" href="https://www.heroku.com/">Heroku</a> is a cloud platform which supports a number of different programming languages and frameworks.</p>
<p>This is not a sponsored post - there are of course many other solutions available, such as:</p>
<ul>
<li><a target="_blank" href="https://www.digitalocean.com/">Digital Ocean</a></li>
<li><a target="_blank" href="https://aws.amazon.com/">Amazon Web Services</a></li>
<li><a target="_blank" href="https://azure.microsoft.com/en-gb/">Azure</a></li>
<li><a target="_blank" href="https://cloud.google.com/">Google Cloud Platform</a></li>
<li><a target="_blank" href="https://www.netlify.com/">Netlify</a></li>
<li><a target="_blank" href="https://zeit.co/">ZEIT Now</a></li>
</ul>
<p>Check them all out and see which suits your needs best.</p>
<p>Personally, I found Heroku the quickest and easiest to start using "out of the box". The free tier is somewhat limited in terms of resources. However, I can confidently recommend it for testing purposes.</p>
<p>This example will host a simple site using an Express server. Here are the high-level steps:</p>
<ol>
<li>Setting up with Heroku, Git, npm</li>
<li>Create an Express.js server</li>
<li>Create static files</li>
<li>Deploy to Heroku</li>
</ol>
<p>It should take about 25 minutes in total (or longer if you want to spend more time on the static files).</p>
<p>This article assumes you already know:</p>
<ul>
<li>Some HTML, CSS and JavaScript basics</li>
<li>Basic command line usage</li>
<li>Beginner-level Git for version control</li>
</ul>
<p>You can find all the code in <a target="_blank" href="https://github.com/pg0408/lorem-ipsum-demo">this repository</a>.</p>
<h3 id="heading-setting-up">Setting up</h3>
<p>The first step in any project is to set up all the tools you know you'll need.</p>
<p>You'll need to have:</p>
<ul>
<li>Node and npm installed on your local machine (read how to do this <a target="_blank" href="https://nodejs.org/en/download/">here</a>)</li>
<li>Git installed (read <a target="_blank" href="https://www.atlassian.com/git/tutorials/install-git">this guide</a>)</li>
<li>The Heroku CLI installed (<a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli#download-and-install">here's how to do it</a>)</li>
</ul>
<p><strong>1. Create a new directory and initialise a Git repository</strong></p>
<p>From the command line, create a new project directory and move into it.</p>
<pre><code>$ mkdir lorem-ipsum-demo
$ cd lorem-ipsum-demo
</code></pre><p>Now you are in the project folder, initialise a new Git repository.</p>
<p>⚠️This step is important because <a target="_blank" href="https://devcenter.heroku.com/articles/how-heroku-works#deploying-applications">Heroku relies on Git</a> for deploying code from your local machine to its cloud servers ⚠️</p>
<pre><code>$ git init
</code></pre><p>As a final step, you can create a README.md file to edit at a later stage.</p>
<pre><code>$ echo <span class="hljs-string">"Edit me later"</span> &gt; README.md
</code></pre><p><strong>2. Login to the Heroku CLI and create a new project</strong></p>
<p>You can login to Heroku using the Heroku CLI (command line interface). You will need to have a free Heroku account to do this.</p>
<p>There are two options here. The default is for Heroku to let you login through the web browser. Adding the <code>-i</code> flag lets you login through the command line.</p>
<pre><code>$ heroku login -i
</code></pre><p>Now, you can create a new Heroku project. I called mine <code>lorem-ipsum-demo</code>.</p>
<pre><code>$ heroku create lorem-ipsum-demo
</code></pre><p>Naming your project:</p>
<ul>
<li>Heroku will generate a random name for your project if you don't specify one in the command.</li>
<li>The name will form part of the URL you can use to access your project, so choose one you like. </li>
<li>This also means that you need to choose a unique project name that no one else has used.</li>
<li>It is possible to rename your project later (so don't worry too much about getting the perfect name right now).</li>
</ul>
<p><strong>3. Initialise a new npm project and install Express.js</strong></p>
<p>Next, you can initialise a new npm project by creating a package.json file. Use the command below to do this.</p>
<p>⚠️This step is crucial. Heroku relies on you providing a package.json file to know this is a Node.js project when it builds your app ⚠️</p>
<pre><code>$ npm init -y
</code></pre><p>Next, <a target="_blank" href="https://expressjs.com/en/starter/installing.html">install Express</a>. Express is a widely used server framework for NodeJS.</p>
<pre><code>$ npm install express --save
</code></pre><p>Finally, you are ready to start coding!</p>
<h3 id="heading-writing-a-simple-express-server">Writing a simple Express server</h3>
<p>The next step is to create a file called <code>app.js</code>, which runs an Express server locally.</p>
<pre><code>$ touch app.js
</code></pre><p>This file will be the entry point for the app when it is ready. That means, the one command needed to launch the app will be:</p>
<pre><code>$ node app.js
</code></pre><p>But first, you need to write some code in the file.</p>
<p><strong>4. Edit the contents of app.js</strong></p>
<p>Open <code>app.js</code> in your favourite editor. Write the code shown below and click save.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// create an express app</span>
<span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">"express"</span>)
<span class="hljs-keyword">const</span> app = express()

<span class="hljs-comment">// use the express-static middleware</span>
app.use(express.static(<span class="hljs-string">"public"</span>))

<span class="hljs-comment">// define the first route</span>
app.get(<span class="hljs-string">"/"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">req, res</span>) </span>{
  res.send(<span class="hljs-string">"&lt;h1&gt;Hello World!&lt;/h1&gt;"</span>)
})

<span class="hljs-comment">// start the server listening for requests</span>
app.listen(process.env.PORT || <span class="hljs-number">3000</span>, 
    <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Server is running..."</span>));
</code></pre>
<p>The comments should help indicate what is happening. But let's quickly break the code down to understand it further:</p>
<ul>
<li>The first two lines simply require the Express module and create an instance of an Express app.</li>
<li>The next line requires the use of the <code>express.static</code> middleware. This lets you serve static files (such as HTML, CSS and JavaScript) from the directory you specify. In this case, the files will be served from a folder called <code>public</code>.</li>
<li>The next line uses <code>app.get()</code> to define a URL route. Any URL requests to the root URL will be responded to with a simple HTML message.</li>
<li>The final part starts the server. It either looks to see which port Heroku will use, or defaults to 3000 if you are running locally.</li>
</ul>
<p>⚠️The use of <code>process.env.PORT || 3000</code> in the last line is important for deploying your app successfully ⚠️</p>
<p>If you save <code>app.js</code> and start the server with:</p>
<pre><code>$ node app.js
</code></pre><p>You can visit <a target="_blank" href="http://localhost:3000/">localhost:3000</a> in your browser and see for yourself the server is running.</p>
<h3 id="heading-create-your-static-files">Create your static files</h3>
<p>The next step is to create your static files. These are the HTML, CSS and JavaScript files you will serve up whenever a user visits your project.</p>
<p>Remember in <code>app.js</code> you told the <code>express.static</code> middleware to serve static files from the <code>public</code> directory.</p>
<p>The first step is of course to create such a directory and the files it will contain.</p>
<pre><code>$ mkdir public
$ cd public
$ touch index.html styles.css script.js
</code></pre><p><strong>5. Edit the HTML file</strong></p>
<p>Open <code>index.html</code> in your preferred text editor. This will be the basic structure of the page you will serve to your visitors.</p>
<p>The example below creates a simple landing page for a <a target="_blank" href="https://en.wikipedia.org/wiki/Lorem_ipsum">Lorem Ipsum</a> generator, but you can be as creative as you like here.</p>
<pre><code class="lang-html"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com/css?family=Alegreya|Source+Sans+Pro&amp;display=swap"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"styles.css"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Lorem Ipsum generator<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>How many paragraphs do you want to generate?<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"number"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"quantity"</span> <span class="hljs-attr">min</span>=<span class="hljs-string">"1"</span> <span class="hljs-attr">max</span>=<span class="hljs-string">"20"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"1"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"generate"</span>&gt;</span>Generate<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"copy"</span>&gt;</span>Copy!<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"lorem"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"script.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<p><strong>6. Edit the CSS file</strong></p>
<p>Next up is editing the CSS file <code>styles.css</code>. Make sure this is linked in your HTML file.</p>
<p>The CSS below is for the Lorem Ipsum example. But again, feel free to be as creative as you want.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">h1</span> {
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Alegreya'</span> ;
}

<span class="hljs-selector-tag">body</span> {
    <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Source Sans Pro'</span> ;
    <span class="hljs-attribute">width</span>: <span class="hljs-number">50%</span>;
    <span class="hljs-attribute">margin-left</span>: <span class="hljs-number">25%</span>;
    <span class="hljs-attribute">text-align</span>: justify;
    <span class="hljs-attribute">line-height</span>: <span class="hljs-number">1.7</span>;
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">18px</span>;
}

<span class="hljs-selector-tag">input</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">18px</span>;
    <span class="hljs-attribute">text-align</span>: center;
}

<span class="hljs-selector-tag">button</span> {
    <span class="hljs-attribute">font-size</span>: <span class="hljs-number">18px</span>;
    <span class="hljs-attribute">color</span>: <span class="hljs-number">#fff</span>;
}

<span class="hljs-selector-id">#generate</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#09f</span>;
}

<span class="hljs-selector-id">#copy</span> {
    <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#0c6</span>;
}
</code></pre>
<p><strong>7. Edit the JavaScript file</strong></p>
<p>Finally, you might want to edit the JavaScript file <code>script.js</code>. This will let you make your page more interactive.</p>
<p>The code below defines two basic functions for the Lorem Ipsum generator. Yes, I used <a target="_blank" href="https://jquery.com/">JQuery</a> - it's quick and easy to work with.</p>
<pre><code class="lang-javascript">$(<span class="hljs-string">"#generate"</span>).click(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">var</span> lorem = $(<span class="hljs-string">"#lorem"</span>);
    lorem.html(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">var</span> quantity = $(<span class="hljs-string">"#quantity"</span>)[<span class="hljs-number">0</span>].valueAsNumber;
    <span class="hljs-keyword">var</span> data = [<span class="hljs-string">"Lorem ipsum"</span>, <span class="hljs-string">"quia dolor sit"</span>, <span class="hljs-string">"amet"</span>, <span class="hljs-string">"consectetur"</span>];
    <span class="hljs-keyword">for</span>(<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; quantity; i++){
        lorem.append(<span class="hljs-string">"&lt;p&gt;"</span>+data[i]+<span class="hljs-string">"&lt;/p&gt;"</span>);
    }
})

$(<span class="hljs-string">"#copy"</span>).click(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">var</span> range = <span class="hljs-built_in">document</span>.createRange();
    range.selectNode($(<span class="hljs-string">"#lorem"</span>)[<span class="hljs-number">0</span>]);
    <span class="hljs-built_in">window</span>.getSelection().removeAllRanges();
    <span class="hljs-built_in">window</span>.getSelection().addRange(range);
    <span class="hljs-built_in">document</span>.execCommand(<span class="hljs-string">"copy"</span>);
    <span class="hljs-built_in">window</span>.getSelection().removeAllRanges();
    }
)
</code></pre>
<p>Note that here, the <code>data</code> list is truncated to make it easier to show. In the actual app, it is a much longer list of full paragraphs. You can see the entire file in the repo, or look <a target="_blank" href="http://www.thelatinlibrary.com/cicero/fin1.shtml">here for the original source</a>.</p>
<h3 id="heading-deploying-your-app">Deploying your app</h3>
<p>After writing your static code and checking it all works as expected, you can get ready to deploy to Heroku.</p>
<p>However, there are a couple more things to do.</p>
<p><strong>8. Create a Procfile</strong></p>
<p>Heroku will need a Procfile to know how to run your app.</p>
<p>A Procfile is a "process file" which tells Heroku which command to run in order to manage a given process. In this case, the command will tell Heroku how to start your server listening on the web.</p>
<p>Use the command below to create the file.</p>
<p>⚠️This is an important step, because without a Procfile, Heroku cannot put your server online. ⚠️</p>
<pre><code>$ echo <span class="hljs-string">"web: node app.js"</span> &gt; Procfile
</code></pre><p>Notice that the Procfile has no file extension (e.g., ".txt", ".json"). </p>
<p>Also, see how the command <code>node app.js</code> is the same one used locally to run your server.</p>
<p><strong>9. Add and commit files to Git</strong></p>
<p>Remember you initiated a Git repository when setting up. Perhaps you have been adding and committing files as you have gone.</p>
<p>Before you deploy to Heroku, make sure to add all the relevant files and commit them.</p>
<pre><code>$ git add .
$ git commit -m <span class="hljs-string">"ready to deploy"</span>
</code></pre><p>The final step is to push to your Heroku master branch.</p>
<pre><code>$ git push heroku master
</code></pre><p>You should see the command line print out a load of information as Heroku builds and deploys your app.</p>
<p>The line to look for is: <code>Verifying deploy... done.</code></p>
<p>This shows that your build was successful.</p>
<p>Now you can open the browser and visit your-project-name.herokuapp.com. Your app will be hosted on the web for all to visit!</p>
<h3 id="heading-quick-recap">Quick recap</h3>
<p>Below are the steps to follow to deploy a simple Express app to Heroku:</p>
<ol>
<li>Create a new directory and initialise a Git repository</li>
<li>Login to the Heroku CLI and create a new project</li>
<li>Initialise a new npm project and install Express.js</li>
<li>Edit the contents of app.js</li>
<li>Edit the static HTML, CSS and JavaScript files</li>
<li>Create a Procfile</li>
<li>Add and commit to Git, then push to your Heroku master branch</li>
</ol>
<h3 id="heading-things-to-check-if-your-app-is-not-working">Things to check if your app is not working</h3>
<p>Sometimes, despite best intentions, tutorials on the Internet don't work exactly as you expected.</p>
<p>The steps below should help debug some common errors you might encounter:</p>
<ul>
<li>Did you initialise a Git repo in your project folder? Check if you ran <code>git init</code> earlier. Heroku relies on Git to deploy code from your local machine.</li>
<li>Did you create a package.json file? Check if you ran <code>npm init -y</code> earlier. Heroku requires a package.json file to recognise this is a Node.js project.</li>
<li>Is the server running? Make sure your Procfile uses the correct file name to start the server. Check you have <code>web: node app.js</code> and not <code>web: node index.js</code>.</li>
<li>Does Heroku know which port to listen on? Check you used <code>app.listen(process.env.PORT || 3000)</code> in your app.js file.</li>
<li>Do your static files have any errors in them? Check them by running locally and seeing if there are any bugs.</li>
</ul>
<p>Thanks for reading - if you made it this far, you might want to <a target="_blank" href="http://lorem-ipsum-demo.herokuapp.com/">checkout the finished version</a> of the demo project.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to deploy a Node Application and Database to Heroku ]]>
                </title>
                <description>
                    <![CDATA[ Heroku is a cloud-based, fully-managed platform as a service (PaaS) for building, running, and managing apps. The platform is flexible and designed with DX support for you and your team’s preferred development style and to help you stay focused and p... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-deploy-a-node-application-and-database-to-heroku/</link>
                <guid isPermaLink="false">66d84e1bbfb3c4f0b376afcb</guid>
                
                    <category>
                        <![CDATA[ 100DaysOfCode ]]>
                    </category>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Git ]]>
                    </category>
                
                    <category>
                        <![CDATA[ GitHub ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Bolaji Ayodeji ]]>
                </dc:creator>
                <pubDate>Sat, 28 Sep 2019 05:02:42 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/09/banner.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Heroku is a cloud-based, fully-managed platform as a service (PaaS) for building, running, and managing apps. The platform is flexible and designed with DX support for you and your team’s preferred development style and to help you stay focused and productive.</p>
<p>Developers, teams, and businesses of all sizes use Heroku to deploy, manage, and scale apps. Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly.</p>
<p>With features like Heroku Runtime, Heroku Postgres (SQL), Heroku Redis, Add-ons, Data Clips, App metrics, Smart containers, Enterprise-grade support, GitHub Integration and lots more, Heroku gives developers the freedom to focus on their core product without the distraction of maintaining servers, hardware, or infrastructure.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku.png" alt="Image" width="600" height="400" loading="lazy"></p>
<hr>
<p>One of Heroku's core feature is deploying, managing, and scaling apps with your favorite languages [Node, Ruby, Python, Java, PHP, Go, and more].<br>In this article, I'll show you how to take an existing Node.js app and deploy it to Heroku – everything from creating your Heroku account to adding a database to your deployed application.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>In my previous article, I wrote about "<a target="_blank" href="https://bolajiayodeji.com/building-a-slackbot-with-nodejs-and-slackbotsjs-cjz8gh7zg000exfs1tq2z5zzu">Building a SlackBot with Node.js and SlackBots.js</a>" and I promised to write a follow-up article to show how to host the SlackBot on either Heroku, Zeit or Netlify and publish it to the Slack Apps store. Well, this is the follow-up article but without the "Publishing to Slack Apps" part. We'll cover that in another article.</p>
<p>I assume you have/ know the following already:</p>
<ul>
<li><p>Read my <a target="_blank" href="https://bolajiayodeji.hashnode.dev/building-a-slackbot-with-nodejs-and-slackbotsjs-cjz8gh7zg000exfs1tq2z5zzu">previous article</a></p>
</li>
<li><p>Built the <a target="_blank" href="https://github.com/BolajiAyodeji/inspireNuggetsSlackBot">inspireNuggets SlackBot</a></p>
</li>
<li><p>Git, Node, and npm installed</p>
</li>
<li><p>A free <a target="_blank" href="https://signup.heroku.com/">Heroku account</a></p>
</li>
<li><p><a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli">Heroku CLI</a> installed</p>
</li>
</ul>
<h2 id="heading-bonus">Bonus</h2>
<p>If you don't have npm, Node, and Heroku CLI installed or a Heroku account already, here's a quick bonus [ Yes, you're welcome :) ].</p>
<h3 id="heading-installing-npm-and-node">Installing npm and Node</h3>
<ul>
<li><p><a target="_blank" href="https://nodejs.org">Node.js</a> is a JavaScript runtime built on <a target="_blank" href="https://v8.dev/">Chrome's V8 JavaScript engine</a>.</p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/">npm</a> is the package manager for Node.js. An open-source project created to help JavaScript developers easily share packaged modules of code.</p>
</li>
</ul>
<p>You can simply download Node.js <a target="_blank" href="https://nodejs.org/en/">here</a>. Don't worry, npm comes with Node.js, so doing this installs both ✨</p>
<h3 id="heading-creating-a-free-heroku-account">Creating a free Heroku account</h3>
<p>Kindly head <a target="_blank" href="https://signup.heroku.com/">here</a> and fill the Signup form. It's pretty simple.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-signup.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h3 id="heading-installing-heroku-cli">Installing Heroku CLI</h3>
<p>The Heroku Command Line Interface (CLI) makes it easy to create and manage your Heroku apps directly from the terminal. It’s an essential part of using Heroku. [ Well, you can decide to use the GitHub integration feature and Heroku Dashboard but yes you should learn how to use the CLI ]<br>Heroku CLI requires Git, the popular version control system. If you don’t already have Git installed, I wrote <a target="_blank" href="https://www.bolajiayodeji.com/setting-up-git-first-time/">this article</a> to help you.</p>
<h4 id="heading-heroku-cli-for-mac-os">Heroku CLI for Mac OS</h4>
<pre><code class="lang-python">brew tap heroku/brew &amp;&amp; brew install heroku
</code></pre>
<p>or <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli">download the installer</a>.</p>
<h4 id="heading-heroku-cli-for-ubuntu">Heroku CLI for Ubuntu</h4>
<pre><code class="lang-python">sudo snap install --classic heroku
</code></pre>
<h4 id="heading-heroku-cli-for-windows">Heroku CLI for Windows</h4>
<p>Download the installer for <a target="_blank" href="https://cli-assets.heroku.com/heroku-x64.exe">64-Bit</a> or <a target="_blank" href="https://cli-assets.heroku.com/heroku-x86.exe">32-Bit</a>.</p>
<h4 id="heading-other-installation-methods">Other installation methods</h4>
<p>Please read <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli#other-installation-methods">this</a>.</p>
<h4 id="heading-getting-started-with-heroku-cli">Getting started with Heroku CLI</h4>
<ul>
<li>Verify your installation</li>
</ul>
<pre><code class="lang-python">heroku --version
</code></pre>
<p>heroku/7.30.1 linux-x64 node-v11.14.0</p>
<ul>
<li>Login to your Heroku account</li>
</ul>
<p>There are two ways to do this:</p>
<ul>
<li><strong>Web based auth</strong></li>
</ul>
<pre><code class="lang-python">heroku login
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-web-auth.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Follow the instructions and login via your web browser then return to your terminal.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-web-auth2.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li><strong>CLI auth</strong></li>
</ul>
<p>This is a safer option as it saves your email address and an API token to <code>~/.netrc</code> for future use.</p>
<pre><code class="lang-python">heroku login -i
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-cli-auth-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<hr>
<h3 id="heading-deploying-your-nodejs-app">Deploying your Node.js App</h3>
<p>I presume you've built the SlackBot already. If you haven't, please clone the <a target="_blank" href="https://github.com/BolajiAyodeji/inspireNuggetsSlackBot">finished project</a>.</p>
<p>The project is a simple Slackbot that displays random inspiring techie quotes and jokes for developers/designers.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/BolajiAyodeji/inspireNuggetsSlackBot.git &amp;&amp; <span class="hljs-built_in">cd</span> inspireNuggetsSlackBot
</code></pre>
<p>Now let's deploy our app to Heroku ??. I'll show you two ways to do this:</p>
<h4 id="heading-deploy-via-the-heroku-git">Deploy via the Heroku Git</h4>
<p>This is done via the Heroku CLI.</p>
<h5 id="heading-checklist"><strong>☑️ Checklist</strong></h5>
<ul>
<li>Specify the version of Node.js that will be used to run your application on Heroku in your <code>package.json</code> file.</li>
</ul>
<pre><code class="lang-python"><span class="hljs-string">"engines"</span>: {
    <span class="hljs-string">"node"</span>: <span class="hljs-string">"10.16.0"</span>
  },
</code></pre>
<ul>
<li>Specify your start script.<br>  Simply create a <code>Procfile</code> (without any file extension) and add</li>
</ul>
<pre><code class="lang-python">web: node index.js
</code></pre>
<p>Heroku first looks for this Procfile. If none is found, Heroku will attempt to start a default web process via the start script in your <code>package.json</code>.</p>
<ul>
<li>Start your app locally using the heroku local command to be sure everything works fine</li>
</ul>
<pre><code class="lang-python">heroku local web
</code></pre>
<p>Your app should now be running on <a target="_blank" href="http://localhost:5000">http://localhost:5000</a>.</p>
<ul>
<li>Don't forget to <code>.gitignore</code></li>
</ul>
<pre><code class="lang-python">/node_modules
.DS_Store
/*.env
</code></pre>
<h5 id="heading-lets-deploy"><strong>? Let's Deploy</strong></h5>
<p>How this works is, you have the project working on local already and you've pushed to GitHub already.</p>
<ul>
<li>Run <code>heroku create</code></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-create.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Basically, this command creates a new Heroku app for you with some randomly generated domain and adds Heroku to your local Git repository.</p>
<ul>
<li>Now run <code>git push heroku master</code></li>
</ul>
<p>This is the magic command, it pushes your app to Heroku, installs it there, and launches it on your allocated domain.</p>
<p>In the example above, it's <a target="_blank" href="https://lit-cove-58897.herokuapp.com/">https://lit-cove-58897.herokuapp.com/</a></p>
<p>You can always make changes to your app settings and domains in your <a target="_blank" href="https://dashboard.heroku.com/">Heroku Dashboard</a></p>
<ul>
<li>Now visit your app in your browser</li>
</ul>
<pre><code class="lang-python">heroku open
</code></pre>
<ul>
<li>You can also view information about your running app using one of the logging commands. This is very useful in debugging errors.</li>
</ul>
<pre><code class="lang-python">heroku logs --tail
</code></pre>
<h4 id="heading-deploy-via-github-integration">Deploy via GitHub integration</h4>
<p>You can configure GitHub integration in the Deploy tab of apps in the <a target="_blank" href="https://dashboard.heroku.com">Heroku Dashboard</a>.</p>
<h5 id="heading-checklist-1"><strong>☑️ Checklist</strong></h5>
<ul>
<li>All previous checklists apply here – ensure you have the app deployed to GitHub already</li>
</ul>
<h5 id="heading-lets-deploy-1"><strong>? Let's Deploy</strong></h5>
<p>How this method works is that you push your entire project to GitHub and integrate it to Heroku. Every time you push, it deploys from GitHub to Heroku. Pretty cool right?</p>
<ul>
<li>Login to your Heroku Dashboard and create a new app</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/create-app.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Select your app name and region</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/new-app.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now your app has successfully been created</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-dash.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Click the deploy tab and scroll to the <strong>Deployment method</strong> section</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-deploy.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Click the <strong>Connect to GitHub</strong> button</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-github.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Now you have the <strong>Connect to GitHub section</strong>, search for the repository and deploy.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-search.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Now your app was deployed successfully</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-200.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-automatic-deploys">Automatic deploys</h4>
<p>Now your app is deployed but you'll have to keep deploying manually. You need to enable automatic deploys for a GitHub branch, so Heroku builds and deploys all pushes to that branch.</p>
<ul>
<li>Scroll to the <strong>Automatic Deploys</strong> section</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-auto.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Select the branch you want to deploy. Ideally, this should be the <code>master</code> branch but change this according to your preference.</p>
<p>Now every push to <code>master</code> (or the branch you chose) will deploy a new version of this app.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-auto-200.png" alt="Image" width="600" height="400" loading="lazy"></p>
<h4 id="heading-nodejs-buildpack">Node.js Buildpack</h4>
<p>In Heroku, Buildpacks are scripts that are run when your app is deployed. They are used to install dependencies for your app and configure your environment.</p>
<p>After deploying your app, ensure you add a Node.js buildpack to your project.</p>
<ul>
<li>Go to <strong>Settings</strong> and scroll to the <strong>Buildpack section</strong></li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-buildpack.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Click the <strong>Add Buildpack</strong> button and select Node.js in the Popup modal.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/10/heroku-add-build.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li>Now the new buildpack configuration will be used when this app is next deployed. Make some changes to your app and push to GitHub – it will automatically deploy.</li>
</ul>
<h3 id="heading-adding-a-database-to-your-deployed-app">Adding a Database to your deployed App'</h3>
<p>The Heroku add-on marketplace has a large number of data stores, from Redis and MongoDB providers, to Postgres and MySQL.</p>
<p>Heroku provides three managed data services to all customers in the form of Add-ons:</p>
<ul>
<li><p><a target="_blank" href="https://elements.heroku.com/addons/heroku-postgresql">Heroku Postgres</a></p>
</li>
<li><p><a target="_blank" href="https://elements.heroku.com/addons/heroku-redis">Heroku Redis</a></p>
</li>
<li><p><a target="_blank" href="https://elements.heroku.com/addons/cloudkarafka">Apache Kafka on Heroku</a></p>
</li>
</ul>
<p>Writing about this three will make this article too long. It's pretty simple and I'll add some links to the Heroku Docs.</p>
<ul>
<li><p><a target="_blank" href="https://devcenter.heroku.com/categories/postgres-basics">Heroku Postgresql Docs</a></p>
</li>
<li><p><a target="_blank" href="https://devcenter.heroku.com/articles/heroku-redis">Heroku Redis Docs</a></p>
</li>
<li><p><a target="_blank" href="https://devcenter.heroku.com/articles/kafka-on-heroku">Apache Kafka on Heroku Docs</a></p>
</li>
</ul>
<hr>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Every Heroku account is allocated a pool of free dyno hours. Heroku (free) dynos are great for hosting apps and personal projects. The downside, however, is that your app will fall asleep if it doesn't receive any web traffic within 30-minutes :(.</p>
<p>You can use external tools to ping your server periodically so it never falls asleep.</p>
<p>Here are some to consider:</p>
<ul>
<li><p><a target="_blank" href="https://www.npmjs.com/package/pingmydyno">Pingmydyno</a></p>
</li>
<li><p><a target="_blank" href="https://www.npmjs.com/package/heroku-self-ping">Heroku self ping</a></p>
</li>
<li><p><a target="_blank" href="http://wakemydyno.com/">Wakemydyno</a></p>
</li>
<li><p><a target="_blank" href="https://kaffeine.herokuapp.com/">Kaffeine</a></p>
</li>
</ul>
<hr>
<blockquote>
<p>Heroku is meticulously designed to help developers be as productive as possible. The platform removes frustrating obstacles and mundane tasks, so you can stay free of distraction in your development flow. Wherever you are on the learning path, Heroku helps you love app development even more. - Heroku</p>
</blockquote>
<p>The Heroku experience provides services, tools, workflows, and polyglot support—all designed to enhance developer productivity. There is more to using Heroku and I hope you explore more and build amazing stuff with Heroku.</p>
<p>If you're a student, Kindly register for the <a target="_blank" href="https://education.github.com/pack">GitHub Student Developer Pack</a> to get One free <a target="_blank" href="https://www.heroku.com/pricing">Hobby Dyno</a> for up to two years.</p>
<p>The pack give students free access to the best developer tools in one place so you can learn by doing.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to deploy a React + Node app to Heroku in 3 minutes without the command line ]]>
                </title>
                <description>
                    <![CDATA[ By Mohammad Iqbal In this tutorial we will be doing a basic React + Node app deploy to Heroku.  There are a lot of tutorials that do this only using the command line, so to change things up a bit, I will do it completely without the command line. ]]>
                </description>
                <link>https://www.freecodecamp.org/news/deploy-a-react-node-app-to/</link>
                <guid isPermaLink="false">66d45f34264384a65d5a953c</guid>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Sat, 07 Sep 2019 21:04:32 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2019/09/david-watkis-qtyGhwZOAbU-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Mohammad Iqbal</p>
<p>In this tutorial we will be doing a basic React + Node app deploy to Heroku. </p>
<p>There are a lot of tutorials that do this only using the command line, so to change things up a bit, I will do it completely without the command line. </p>
<p>For things like generating React and Express apps, we have no choice but to use the command line. For everything else we'll use a GUI.</p>
<p>I also assume you have a Github and Heroku account. They are both free, so no worries about signing up.</p>
<p>sample project:<br><a target="_blank" href="https://github.com/iqbal125/react-express-heroku">https://github.com/iqbal125/react-express-</a>sample</p>
<h2 id="heading-react-and-express-setup">React and Express Setup</h2>
<p>First, let's start by creating two directories named <strong>Server</strong> and <strong>Client.</strong> </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/image-80.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The Client directory will hold the contents of the <code>create-react-app</code> command, and Server will hold the contents of the <code>express</code> command. This library just creates a simple Express app for us automatically, similar to <code>create-react-app</code>. It needs to be installed globally, which you can do so with the command:</p>
<p><code>npm install -g express-generator</code></p>
<p>After this, simply run these commands in each of the respective directories to install the starter projects:</p>
<p><code>npx create-react-app app1</code> in the <strong>Client</strong> directory</p>
<p><code>express</code> in the <strong>Server</strong> directory </p>
<p>Change to the <strong>app1</strong> directory generated by <code>create-react-app</code> and run:</p>
<p><code>npm run build</code></p>
<p>This will generate a production build version of the project that is optimized for a production deployment, with things like the error handling code and white space removed.  </p>
<p><em>Note:</em> In a development build you would use a proxy to <strong>http://localhost:5000</strong> to communicate from your React app to your Express server, but here the React app and the Express server are just one project. The Express server serves the React files.</p>
<p>Next, cut and paste the entire <strong>build</strong> directory into the <strong>Server</strong> directory. Your project structure should look like this: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/image-81.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>We can now add some code to let our Express server know to serve our React project.:</p>
<pre><code class="lang-javascript">....

app.use(express.static(path.join(__dirname, <span class="hljs-string">'build'</span>)));


app.get(<span class="hljs-string">'/*'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  res.sendFile(path.join(__dirname, <span class="hljs-string">'build'</span>, <span class="hljs-string">'index.html'</span>));
});

....
</code></pre>
<p>The first line of code serves all our static files from the <strong>build</strong> directory. </p>
<p>The second piece of code is to keep our client side routing functional. This code essentially serves the <code>index.html</code> file on any unknown routes. Otherwise we would need to rewrite our entire routing to work with this Express server setup. </p>
<p>To test your app, just run <code>npm start</code> in the <strong>Server</strong> directory and go to <strong>http://localhost 3000</strong> in the browser. Then you should be see your running React app. </p>
<p>Now we are ready to upload this project to GitHub.</p>
<h2 id="heading-github">GitHub</h2>
<p>Instead of using the command line to upload to GitHub, we will do this with the GUI. First, go to the GitHub homepage and create a new repository. Name it whatever you want, but make sure the <strong>Initialize this Repository with a README</strong> option checked:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/image-82.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Next upload all the project files without the <strong>node_modules</strong> directory. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/image-83.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click commit and we are done. Your uploaded project files will appear on GitHub like so:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/image-84.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Now we can move on to setting up Heroku.</p>
<h2 id="heading-heroku">Heroku</h2>
<p>Go to the Heroku dashboard, create a new app, and name it whatever you like. </p>
<p>Next, go to the Deploy tab and select GitHub under Deployment method:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/image-85.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you haven't connected your GitHub account to your Heroku account yet, you will be prompted through the GitHub Auth flow. </p>
<p>After this, search for your project on GitHub and connect to it:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/image-86.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Finally, we can just deploy our app by clicking the Deploy Branch button: </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/09/image-87.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Heroku will install all the Node modules for you automatically. You can view your project by clicking on the View button. </p>
<p>And that's it, we're done! Thanks for reading. </p>
<blockquote>
<p>Connect with me on Twitter for more updates on future tutorials: <a target="_blank" href="https://twitter.com/iqbal125sf">https://twitter.com/iqbal125sf</a></p>
</blockquote>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ A Python project in 30 lines of code: how to set up an SMS notification when your favorite Twitcher is streaming ]]>
                </title>
                <description>
                    <![CDATA[ By Pierre de Wulf Hi everyone :) Today I am beginning a new series of posts specifically aimed at Python beginners. The concept is rather simple: I'll do a fun project, in as few lines of code as possible, and will try out as many new tools as possib... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/20-lines-of-python-code-get-notified-by-sms-when-your-favorite-team-scores-a-goal/</link>
                <guid isPermaLink="false">66d4608cc7632f8bfbf1e46f</guid>
                
                    <category>
                        <![CDATA[ api ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ projects ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Thu, 15 Aug 2019 14:53:06 +0000</pubDate>
                <media:content url="https://cdn-media-2.freecodecamp.org/w1280/5f9ca0d8740569d1a4ca4b21.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Pierre de Wulf</p>
<p>Hi everyone :) Today I am beginning a new series of posts specifically aimed at Python beginners. The concept is rather simple: I'll do a fun project, in as few lines of code as possible, and will try out as many new tools as possible.</p>
<p>For example, today we will learn to use the Twilio API, the Twitch API, and we'll see how to deploy the project on Heroku. I'll show you how you can have your own "Twitch Live" SMS notifier, in 30 lines of codes, and for 12 cents a month.</p>
<p><strong>Prerequisite</strong>: You only need to know how to run Python on your machine and some basic commands in git (commit &amp; push). If you need help with these, I can recommend these 2 articles to you: </p>
<p><a target="_blank" href="https://realpython.com/installing-python/">Python 3 Installation &amp; Setup Guide</a> </p>
<p><a target="_blank" href="https://www.freecodecamp.org/news/git-commands/">The Ultimate Git Command Tutorial for Beginners</a> from <a target="_blank" href="https://www.freecodecamp.org/news/author/adrianhajdin/">Adrian Hajdin</a>.</p>
<p><strong>What you'll learn</strong>:</p>
<ul>
<li>Twitch API</li>
<li>Twilio API</li>
<li>Deploying on Heroku</li>
<li>Setting up a scheduler on Heroku</li>
</ul>
<p><strong>What you will build:</strong></p>
<p>The specifications are simple: we want to receive an SMS as soon as a specific Twitcher is live streaming. We want to know when this person is going live and when they leave streaming. We want this whole thing to run by itself, all day long. </p>
<p>We will split the project into 3 parts. First, we will see how to programmatically know if a particular Twitcher is online. Then we will see how to receive an SMS when this happens. We will finish by seeing how to make this piece of code run every X minutes, so we never miss another moment of our favorite streamer's life.</p>
<h1 id="heading-is-this-twitcher-live">Is this Twitcher live?</h1>
<p>To know if a Twitcher is live, we can do two things: we can go to the Twitcher URL and try to see if the badge "Live" is there.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/08/Capture-d-e-cran-2019-08-14-a--15.49.31.png" alt="Image" width="600" height="400" loading="lazy">
<em>Screenshot of a Twitcher live streaming.</em></p>
<p>This process involves scraping and is not easily doable in Python in less than 20 or so lines of code. Twitch runs a lot of JS code and a simple request.get() won't be enough. </p>
<p>For scraping to work, in this case, we would need to scrape this page inside Chrome to get the same content like what you see in the screenshot. This is doable, but it will take much more than 30 lines of code. If you'd like to learn more, don't hesitate to check my recent <a target="_blank" href="https://www.daolf.com/posts/avoiding-being-blocked-while-scraping-ultimate-guide/">web scraping without getting blocked guide</a>. (I recently launch ScrapingBee, a <a target="_blank" href="https://www.scrapingbee.com/blog/web-scraping-tools/">web-scraping tool</a> hence my knowledge in the field ;))</p>
<p>So instead of trying to scrape Twitch, we will use their API. For those unfamiliar with the term, an API is a programmatic interface that allows websites to expose their features and data to anyone, mainly developers. In Twitch's case, their API is exposed through HTTP, witch means that we can have lots of information and do lots of things by just making a simple HTTP request.</p>
<h2 id="heading-get-your-api-key">Get your API key</h2>
<p>To do this, you have to first create a Twitch API key. Many services enforce authentication for their APIs to ensure that no one abuses them or to restrict access to certain features by certain people.</p>
<p>Please follow these steps to get your API key:</p>
<ul>
<li>Create a Twitch account</li>
<li>Now create a Twitch <a target="_blank" href="https://dev.twitch.tv/">dev account</a> -&gt; "Signing up with Twitch" top right</li>
<li>Go to your "dashboard" once logged in</li>
<li>"Register your application"</li>
<li>Name -&gt; Whatever, Oauth redirection URL -&gt; http://localhost, Category -&gt; Whatever</li>
</ul>
<p>You should now see, at the bottom of your screen, your client-id. Keep this for later.</p>
<h2 id="heading-is-that-twitcher-streaming-now">Is that Twitcher streaming now?</h2>
<p>With your API key in hand, we can now query the Twitch API to have the information we want, so let's begin to code. The following snippet just consumes the Twitch API with the correct parameters and prints the response.</p>
<pre><code class="lang-python"><span class="hljs-comment"># requests is the go to package in python to make http request</span>
<span class="hljs-comment"># https://2.python-requests.org/en/master/</span>
<span class="hljs-keyword">import</span> requests

<span class="hljs-comment"># This is one of the route where Twich expose data, </span>
<span class="hljs-comment"># They have many more: https://dev.twitch.tv/docs</span>
endpoint = <span class="hljs-string">"https://api.twitch.tv/helix/streams?"</span>

<span class="hljs-comment"># In order to authenticate we need to pass our api key through header</span>
headers = {<span class="hljs-string">"Client-ID"</span>: <span class="hljs-string">"&lt;YOUR-CLIENT-ID&gt;"</span>}

<span class="hljs-comment"># The previously set endpoint needs some parameter, here, the Twitcher we want to follow</span>
<span class="hljs-comment"># Disclaimer, I don't even know who this is, but he was the first one on Twich to have a live stream so I could have nice examples</span>
params = {<span class="hljs-string">"user_login"</span>: <span class="hljs-string">"Solary"</span>}

<span class="hljs-comment"># It is now time to make the actual request</span>
response = request.get(endpoint, params=params, headers=headers)
print(response.json())
</code></pre>
<p>The output should look like this:</p>
<pre><code class="lang-json">{
   'data':[
      {
         'id':'<span class="hljs-number">35289543872</span>',
         'user_id':'<span class="hljs-number">174955366</span>',
         'user_name':'Solary',
         'game_id':'<span class="hljs-number">21779</span>',
         'type':'live',
         'title':<span class="hljs-string">"Wakz duoQ w/ Tioo - GM 400LP - On récupère le chall après les -250LP d'inactivité !"</span>,
         'viewer_count':<span class="hljs-number">4073</span>,
         'started_at':'<span class="hljs-number">2019</span><span class="hljs-number">-08</span><span class="hljs-number">-14</span>T07:<span class="hljs-number">01</span>:<span class="hljs-number">59</span>Z',
         'language':'fr',
         'thumbnail_url':'https:<span class="hljs-comment">//static-cdn.jtvnw.net/previews-ttv/live_user_solary-{width}x{height}.jpg',</span>
         'tag_ids':[
            '<span class="hljs-number">6</span>f655045<span class="hljs-number">-9989</span><span class="hljs-number">-4</span>ef7<span class="hljs-number">-8</span>f85<span class="hljs-number">-1</span>edcec42d648'
         ]
      }
   ],
   'pagination':{
      'cursor':'eyJiIjpudWxsLCJhIjp7Ik9mZnNldCI6MX19'
   }
}
</code></pre>
<p>This data format is called JSON and is easily readable. The <code>data</code> object is an array that contains all the currently active streams. The key <code>type</code> ensures that the stream is currently <code>live</code>. This key will be empty otherwise (in case of an error, for example).</p>
<p>So if we want to create a boolean variable in Python that stores whether the current user is streaming, all we have to append to our code is:</p>
<pre><code class="lang-python">json_response = response.json()

<span class="hljs-comment"># We get only streams</span>
streams = json_response.get(<span class="hljs-string">'data'</span>, [])

<span class="hljs-comment"># We create a small function, (a lambda), that tests if a stream is live or not</span>
is_active = <span class="hljs-keyword">lambda</span> stream: stream.get(<span class="hljs-string">'type'</span>) == <span class="hljs-string">'live'</span>
<span class="hljs-comment"># We filter our array of streams with this function so we only keep streams that are active</span>
streams_active = filter(is_active, streams)

<span class="hljs-comment"># any returns True if streams_active has at least one element, else False</span>
at_least_one_stream_active = any(streams_active)

print(at_least_one_stream_active)
</code></pre>
<p>At this point, <code>at_least_one_stream_active</code> is True when your favourite Twitcher is live.</p>
<p>Let's now see how to get notified by SMS.</p>
<h1 id="heading-send-me-a-text-now">Send me a text, NOW!</h1>
<p>So to send a text to ourselves, we will use the Twilio API. Just go over <a target="_blank" href="https://www.twilio.com/try-twilio">there</a> and create an account. When asked to confirm your phone number, please use the phone number you want to use in this project. This way you'll be able to use the $15 of free credit Twilio offers to new users. At around 1 cent a text, it should be enough for your bot to run for one year.</p>
<p>If you go on the <a target="_blank" href="https://www.twilio.com/console">console</a>, you'll see your <code>Account SID</code> and your <code>Auth Token</code> , save them for later. Also click on the big red button "Get My Trial Number", follow the step, and save this one for later too.</p>
<p>Sending a text with the Twilio Python API is very easy, as they provide a package that does the annoying stuff for you. Install the package with <code>pip install Twilio</code> and just do: </p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> twilio.rest <span class="hljs-keyword">import</span> Client
client = Client(&lt;Your Account SID&gt;, &lt;Your Auth Token&gt;)
client.messages.create(
    body=<span class="hljs-string">'Test MSG'</span>,from_=&lt;Your Trial Number&gt;,to=&lt;Your Real Number&gt;)
</code></pre>
<p>And that is all you need to send yourself a text, amazing right?</p>
<h1 id="heading-putting-everything-together">Putting everything together</h1>
<p>We will now put everything together, and shorten the code a bit so we manage to say under 30 lines of Python code.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> twilio.rest <span class="hljs-keyword">import</span> Client
endpoint = <span class="hljs-string">"https://api.twitch.tv/helix/streams?"</span>
headers = {<span class="hljs-string">"Client-ID"</span>: <span class="hljs-string">"&lt;YOUR-CLIENT-ID&gt;"</span>}
params = {<span class="hljs-string">"user_login"</span>: <span class="hljs-string">"Solary"</span>}
response = request.get(endpoint, params=params, headers=headers)
json_response = response.json()
streams = json_response.get(<span class="hljs-string">'data'</span>, [])
is_active = <span class="hljs-keyword">lambda</span> stream:stream.get(<span class="hljs-string">'type'</span>) == <span class="hljs-string">'live'</span>
streams_active = filter(is_active, streams)
at_least_one_stream_active = any(streams_active)
<span class="hljs-keyword">if</span> at_least_one_stream_active:
    client = Client(&lt;Your Account SID&gt;, &lt;Your Auth Token&gt;)
    client.messages.create(body=<span class="hljs-string">'LIVE !!!'</span>,from_=&lt;Your Trial Number&gt;,to=&lt;Your Real Number&gt;)
</code></pre>
<h1 id="heading-avoiding-double-notifications">Avoiding double notifications</h1>
<p>This snippet works great, but should that snippet run every minute on a server, as soon as our favorite Twitcher goes live we will receive an SMS every minute. </p>
<p>We need a way to store the fact that we were already notified that our Twitcher is live and that we don't need to be notified anymore.</p>
<p>The good thing with the Twilio API is that it offers a way to retrieve our message history, so we just have to retrieve the last SMS we sent to see if we already sent a text notifying us that the twitcher is live.</p>
<p>Here what we are going do to in pseudocode:</p>
<pre><code><span class="hljs-keyword">if</span> favorite_twitcher_live and last_sent_sms is not live_notification:
    send_live_notification()
<span class="hljs-keyword">if</span> not favorite_twitcher_live and last_sent_sms is live_notification:
    send_live_is_over_notification()
</code></pre><p>This way we will receive a text as soon as the stream starts, as well as when it is over. This way we won't get spammed - perfect right? Let's code it:</p>
<pre><code class="lang-python"><span class="hljs-comment"># reusing our Twilio client</span>
last_messages_sent = client.messages.list(limit=<span class="hljs-number">1</span>)
last_message_id = last_messages_sent[<span class="hljs-number">0</span>].sid
last_message_data = client.messages(last_message_id).fetch()
last_message_content = last_message_data.body
</code></pre>
<p>Let's now put everything together again:</p>
<pre><code class="lang-py"><span class="hljs-keyword">import</span> requests
<span class="hljs-keyword">from</span> twilio.rest <span class="hljs-keyword">import</span> Client
client = Client(&lt;Your Account SID&gt;, &lt;Your Auth Token&gt;)

endpoint = <span class="hljs-string">"https://api.twitch.tv/helix/streams?"</span>
headers = {<span class="hljs-string">"Client-ID"</span>: <span class="hljs-string">"&lt;YOUR-CLIENT-ID&gt;"</span>}
params = {<span class="hljs-string">"user_login"</span>: <span class="hljs-string">"Solary"</span>}
response = request.get(endpoint, params=params, headers=headers)
json_response = response.json()
streams = json_response.get(<span class="hljs-string">'data'</span>, [])
is_active = <span class="hljs-keyword">lambda</span> stream:stream.get(<span class="hljs-string">'type'</span>) == <span class="hljs-string">'live'</span>
streams_active = filter(is_active, streams)
at_least_one_stream_active = any(streams_active)

last_messages_sent = client.messages.list(limit=<span class="hljs-number">1</span>)
<span class="hljs-keyword">if</span> last_messages_sent:
    last_message_id = last_messages_sent[<span class="hljs-number">0</span>].sid
    last_message_data = client.messages(last_message_id).fetch()
    last_message_content = last_message_data.body
    online_notified = <span class="hljs-string">"LIVE"</span> <span class="hljs-keyword">in</span> last_message_content
    offline_notified = <span class="hljs-keyword">not</span> online_notified
<span class="hljs-keyword">else</span>:
    online_notified, offline_notified = <span class="hljs-literal">False</span>, <span class="hljs-literal">False</span>

<span class="hljs-keyword">if</span> at_least_one_stream_active <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> online_notified:
    client.messages.create(body=<span class="hljs-string">'LIVE !!!'</span>,from_=&lt;Your Trial Number&gt;,to=&lt;Your Real Number&gt;)
<span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> at_least_one_stream_active <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> offline_notified:
    client.messages.create(body=<span class="hljs-string">'OFFLINE !!!'</span>,from_=&lt;Your Trial Number&gt;,to=&lt;Your Real Number&gt;)
</code></pre>
<p>And voilà!</p>
<p>You now have a snippet of code, in less than 30 lines of Python, that will send you a text a soon as your favourite Twitcher goes Online / Offline and without spamming you.</p>
<p>We just now need a way to host and run this snippet every X minutes.</p>
<h1 id="heading-the-quest-for-a-host">The quest for a host</h1>
<p>To host and run this snippet we will use Heroku. Heroku is honestly one of the easiest ways to host an app on the web. The downside is that it is really expensive compared to other solutions out there. Fortunately for us, they have a generous free plan that will allow us to do what we want for almost nothing.</p>
<p>If you don't already, you need to create a <a target="_blank" href="https://www.heroku.com/">Heroku account</a>. You also need to <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli#download-and-install">download and install the Heroku client</a>.</p>
<p>You now have to move your Python script to its own folder, don't forget to add a <code>requirements.txt</code> file in it. The content of the latter begins:</p>
<pre><code>requests
twilio
</code></pre><p><code>cd</code> into this folder and just do a <code>heroku create --app &lt;app name&gt;</code>.</p>
<p>If you go on your <a target="_blank" href="https://dashboard.heroku.com/apps">app dashboard</a> you'll see your new app. </p>
<p>We now need to initialize a git repo and push the code on Heroku:</p>
<pre><code>git init
heroku git:remote -a &lt;app name&gt;
git add .
git commit -am <span class="hljs-string">'Deploy breakthrough script'</span>
git push heroku master
</code></pre><p>Your app is now on Heroku, but it is not doing anything. Since this little script can't accept HTTP requests, going to <code>&lt;app name&gt;.herokuapp.com</code> won't do anything. But that should not be a problem.</p>
<p>To have this script running 24/7 we need to use a simple Heroku add-on call "Heroku Scheduler". To install this add-on, click on the "Configure Add-ons" button on your app dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/08/Capture-d-e-cran-2019-08-15-a--12.50.40.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Then, on the search bar, look for Heroku Scheduler:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/08/Capture-d-e-cran-2019-08-15-a--12.53.12.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click on the result, and click on "Provision"</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/08/Capture-d-e-cran-2019-08-15-a--12.50.59.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>If you go back to your App dashboard, you'll see the add-on:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2019/08/Capture-d-e-cran-2019-08-15-a--12.54.16.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Click on the "Heroku Scheduler" link to configure a job. Then click on "Create Job". Here select "10 minutes", and for run command select <code>python &lt;name_of_your_script&gt;.py</code>. Click on "Save job".</p>
<p>While everything we used so far on Heroku is free, the Heroku Scheduler will run the job on the $25/month instance, but prorated to the second. Since this script approximately takes 3 seconds to run, for this script to run every 10 minutes you should just have to spend 12 cents a month.</p>
<h1 id="heading-ideas-for-improvements">Ideas for improvements</h1>
<p>I hope you liked this project and that you had fun putting it into place. In less than 30 lines of code, we did a lot, but this whole thing is far from perfect. Here are a few ideas to improve it:</p>
<ul>
<li>Send yourself more information about the current streaming (game played, number of viewers ...)</li>
<li>Send yourself the duration of the last stream once the twitcher goes offline</li>
<li>Don't send you a text, but rather an email</li>
<li>Monitor multiple twitchers at the same time</li>
</ul>
<p>Do not hesitate to tell me in the comments if you have more ideas.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>I hope that you liked this post and that you learned things reading it. I truly believe that this kind of project is one of the best ways to learn new tools and concepts, I recently launched a <a target="_blank" href="https://www.scrapingninja.co">web scraping API</a> where I learned a lot while making it.</p>
<p>Please tell me in the comments if you liked this format and if you want to do more.</p>
<p>I have many other ideas, and I hope you will like them. Do not hesitate to share what other things you build with this snippet, possibilities are endless.</p>
<p>Happy Coding.</p>
<p>Pierre </p>
<h2 id="heading-dont-want-to-miss-my-next-post">Don't want to miss my next post:</h2>
<p>You can subscribe <a target="_blank" href="https://www.daolf.com/stay_updated/">here</a> to my newsletter.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to make a custom Reddit notification system with Python ]]>
                </title>
                <description>
                    <![CDATA[ By Kelsey Wang Don’t you just love automated emails? I know I do. I mean, who doesn’t enjoy waking up to 236 new messages from Nike, Ticketmaster, and Adobe Creative Cloud every morning? What a fantastic way to start my day! ?? Anyway, today I’ll be ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/make-a-custom-reddit-notification-system-with-python-4dd560667b35/</link>
                <guid isPermaLink="false">66c35ac871e87702d4e5b6fb</guid>
                
                    <category>
                        <![CDATA[ Heroku ]]>
                    </category>
                
                    <category>
                        <![CDATA[ General Programming ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ reddit ]]>
                    </category>
                
                    <category>
                        <![CDATA[ tech  ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 08 Apr 2019 16:27:21 +0000</pubDate>
                <media:content url="https://cdn-media-1.freecodecamp.org/images/1*jwiUzuo1t9kRdDdqTdoYbw.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Kelsey Wang</p>
<p>Don’t you just <em>love</em> automated emails? I know I do. I mean, who doesn’t enjoy waking up to 236 new messages from Nike, Ticketmaster, and Adobe Creative Cloud every morning? What a fantastic way to start my day! ??</p>
<p>Anyway, today I’ll be showing you how to drown your inbox in more clutter, for God-knows-what reason. We’re going to be <strong>using Python to create a custom Reddit email-notification system.</strong> That means we’ll be writing a script that looks for Reddit posts matching some keywords and then emails us when such posts appear.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*jwiUzuo1t9kRdDdqTdoYbw.png" alt="Image" width="800" height="427" loading="lazy">
<em>Want quality email content like this? Read on!</em></p>
<p>There are a few reasons that you might be doing this. Maybe you’re really excited about some topic on Reddit. Maybe you’re trying to discover a new karma-farming technique because Internet points are important to you. Maybe you want to send annoying emails to your friends. Or maybe you just want more emails in your inbox to deal with your crippling loneliness. Oops, sorry — went too far. Let’s get started.</p>
<h3 id="heading-looking-through-reddit">Looking through Reddit</h3>
<p>Reddit has a <a target="_blank" href="https://www.reddit.com/dev/api/">nice API</a> that you can do a lot with. To make things even easier, we will be using <a target="_blank" href="https://praw.readthedocs.io/en/latest/">PRAW</a>, the Python Reddit API Wrapper.</p>
<p>You’ll need a Reddit account first. Once you have one, go <a target="_blank" href="https://www.reddit.com/prefs/apps">here</a> to create an app. Name it anything, and make sure “script” is selected. As per the docs, you can just put <code>[http://localhost:8080](http://localhost:8080)</code> for your redirect URI.</p>
<p>Now, you’re ready to start that nifty script! In the code below, <strong>I look through a subreddit, picking out posts that match my needs.</strong></p>
<p>I consider a post a <em>match</em> if it is relevant enough and if it is popular enough. More specifically, the post is relevant enough when it has a <code>keyword_count</code> that’s not -1 (I’ll explain this below) and popular enough when it has a <code>weighted_score</code> greater than a predefined <code>MIN_RELEVANT_WEIGHTED_SCORE</code>. The weighted score simply factors in the score of the post and the number of comments on the post. Anyway, this is what best fit my needs, so feel free to better define what a match means to you.</p>
<p>Now, I promised you I would talk about the <code>keyword_count</code> party going on. Spoiler: it’s not really a party. I just devised this simple way of assessing relevancy: there are required terms and secondary terms. A post is relevant if and only if all the required terms are in the title, and at least X number of secondary terms are in the title (where X is some predefined number). Again, this part can be re-imagined in infinitely different ways, but this is just what I did.</p>
<p>Now we have everything to comb through our subreddit and tease out the good stuff about conspiracies or whatever. Cool. So, like my homie Ariana says, “thank u, next.”</p>
<h3 id="heading-emailing-notifications">Emailing notifications</h3>
<p>Time to start spamming. In the code below, I’m using <a target="_blank" href="https://docs.python.org/3/library/smtplib.html">smtplib</a> (the Simple Mail Transfer Protocol client) to help me send my emails. I then craft the beautiful email with HTML, using the info from Reddit that we got above to populate it. And the best (or worst?) part is, if you want to notify everyone you know about the latest and greatest Reddit posts, you can simply add more email addresses to the <code>email_list</code>.</p>
<p>Important side note: make sure the email you use to send the emails have <a target="_blank" href="https://support.google.com/accounts/answer/6010255?hl=en">less secure app access</a> enabled if it’s a Gmail address, or this will not work.</p>
<h3 id="heading-make-it-run-forever">Make it run forever</h3>
<p>If you don’t have time to continually browse Reddit, you don’t have time to continually run this script. I used Heroku Scheduler to run this script every 10 minutes, as suggested by this <a target="_blank" href="https://stackoverflow.com/questions/39139165/running-simple-python-script-continuously-on-heroku">Stack Overflow</a> answer. It’s pretty easy to follow: add in a few additional files and a dummy web server, push to Heroku, add the Heroku Scheduler add-on, and <em>BAM!</em> You’re set until you run out of free dyno-hours. ??</p>
<p>Is this the best solution? No. But is it sufficient for my purposes? Yep. If you know of a similarly trivial way to do this, please let me know!</p>
<h3 id="heading-in-conclusion">In conclusion</h3>
<p>That’s pretty much all to this project. This <a target="_blank" href="https://github.com/kelseyywang/reddit-notifs">GitHub repo</a> contains all my code. Because of all the work that literally everyone else has already done, it’s quite a simple task to build this custom Reddit notification system. Gotta love the ✨magic✨ of software development.</p>
<p><img src="https://cdn-media-1.freecodecamp.org/images/1*bhzl5sep8VGmZTjM7bWe8Q.jpeg" alt="Image" width="800" height="533" loading="lazy">
<em>Me after setting up my custom Reddit notifications</em></p>
<p>If you made it all the way down to here, please comment “North Dakota is the top producer of barley in the USA” in the box below.</p>
<p>Thanks for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
