<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Joseph Caudle</title>
    <description>The latest articles on DEV Community by Joseph Caudle (@josephcaudle).</description>
    <link>https://dev.to/josephcaudle</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1312129%2F38fce83f-c244-4300-a7cf-ce9dcc0e84db.png</url>
      <title>DEV Community: Joseph Caudle</title>
      <link>https://dev.to/josephcaudle</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/josephcaudle"/>
    <language>en</language>
    <item>
      <title>PostGIS and Heroku Postgres… Location, Location, Location!</title>
      <dc:creator>Joseph Caudle</dc:creator>
      <pubDate>Wed, 17 Apr 2024 13:00:00 +0000</pubDate>
      <link>https://dev.to/josephcaudle/postgis-and-heroku-postgres-location-location-location-51id</link>
      <guid>https://dev.to/josephcaudle/postgis-and-heroku-postgres-location-location-location-51id</guid>
      <description>&lt;p&gt;I’ve lost too much time fiddling around with configurations and services just to spin up a compute instance in AWS. Sometimes, I just need a production-ready environment to play around with to test out applications and ideas. With Heroku, I can get that with just a few simple commands at the CLI.&lt;/p&gt;

&lt;p&gt;Recently, I learned that Heroku also includes &lt;a href="https://devcenter.heroku.com/articles/heroku-postgres-extensions-postgis-full-text-search"&gt;support for PostGIS&lt;/a&gt;. I’ve personally never used PostGIS before. I know of several proprietary competitors to the product, so I figured this would be a great time to try out the open-source option.&lt;/p&gt;

&lt;p&gt;In this article, I’ll show you how to get a PostGIS-enabled Postgres instance running on Heroku. Then, I’ll run some sample queries on the database, just to give you a feel for how it works. And the best part? You can follow along and do your own exploring as we go!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What is PostGIS?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Even if you’ve used Postgres for a while, you might not be familiar with PostGIS. The GIS stands for &lt;a href="https://en.wikipedia.org/wiki/Geographic_information_system_software"&gt;Geographic Information System&lt;/a&gt;. There are many solutions in the space. But the thing that makes PostGIS nice is that it’s based on the well-loved PostgreSQL database. In addition to all of the performance you’d expect from Postgres, we get a full-featured tool for storing geospatial data. Not only does PostGIS provide a good storage solution for this type of data, but it can be seamlessly integrated with several applications that can consume this data (such as ArcGIS and Tableau).&lt;/p&gt;

&lt;p&gt;TL;DR—If you need to process, store, or query location data, PostGIS is a great option for doing that. Fortunately, it’s as simple as adding an addon to a Heroku app to get a new Postgres instance up and running. So, let’s do that now.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How can we use PostGIS?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To get started, you’ll need an app of any size. Then, you add an instance of &lt;a href="https://devcenter.heroku.com/articles/heroku-postgresql"&gt;Heroku Postgres&lt;/a&gt; to your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Create a Heroku app&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;For my demo, I’m going to create an empty app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nnA28FIE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dz2cdn1.dzone.com/storage/temp/17623255-1713036209421.png%2520align%3D%2522left%2522" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nnA28FIE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dz2cdn1.dzone.com/storage/temp/17623255-1713036209421.png%2520align%3D%2522left%2522" alt="" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Attach a Heroku Postgres add-on&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once the app is created, I can create the Heroku Postgres add-on. Because my sample data set is too big for a &lt;a href="https://elements.heroku.com/addons/heroku-postgresql#pricing"&gt;Mini plan&lt;/a&gt; instance, I need to use the Basic plan instead. I can do this from the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;heroku login

&lt;span class="nv"&gt;$ &lt;/span&gt;heroku addons:create heroku-postgresql:basic &lt;span class="nt"&gt;-a&lt;/span&gt; postgis-demo
Creating heroku-postgresql:basic on ⬢ postgis-demo... ~&lt;span class="nv"&gt;$0&lt;/span&gt;.013/hour &lt;span class="o"&gt;(&lt;/span&gt;max &lt;span class="nv"&gt;$9&lt;/span&gt;/month&lt;span class="o"&gt;)&lt;/span&gt;
Database has been created and is available
 &lt;span class="o"&gt;!&lt;/span&gt; This database is empty. If upgrading, you can transfer
 &lt;span class="o"&gt;!&lt;/span&gt; data from another database with pg:copy
Created postgresql-fitted-78461 as DATABASE_URL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once I’ve created my Postgres database, I only have a few more steps &lt;a href="https://devcenter.heroku.com/articles/heroku-postgres-extensions-postgis-full-text-search"&gt;to set up PostGIS&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Create the PostGIS extension&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Heroku Postgres has many possible extensions we could install. To list them, we can ask our instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="n"&gt;heroku&lt;/span&gt; &lt;span class="n"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;psql&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;
&lt;span class="c1"&gt;--&amp;gt; Connecting to postgresql-fitted-78461&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;Expanded&lt;/span&gt; &lt;span class="n"&gt;display&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;


&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;show&lt;/span&gt; &lt;span class="n"&gt;extwlist&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;…&lt;/span&gt;
&lt;span class="n"&gt;address_standardizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;address_standardizer_data_us&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;amcheck&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;autoinc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;bloom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;btree_gin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;btree_gist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;citext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;cube&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;dict_int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;earthdistance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;fuzzystrmatch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;hstore&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;insert_username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;intarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;isn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;lo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;ltree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;moddatetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pg_partman&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pg_stat_statements&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pg_trgm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pgcrypto&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;pgrowlocks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;postgis_raster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;postgis_topology&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;refint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sslinfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;tablefunc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;tcn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;tsm_system_rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;tsm_system_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;unaccent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ossp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We see &lt;code&gt;postgis&lt;/code&gt; in the list of available extensions. From there, we can create the extension.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can confirm the extension is installed and check the version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;postgis_version&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="n"&gt;RECORD&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="c1"&gt;---+--------------------------------------&lt;/span&gt;
&lt;span class="n"&gt;postgis_version&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="n"&gt;USE_GEOS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;USE_PROJ&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;USE_STATS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alright! It looks like we’re up and running with PostGIS 3.4.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Load initial dataset&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now that I’ve got the PostGIS extension enabled, I need to load in a dataset to play around with. I’m using the dataset provided by the &lt;a href="https://postgis.net/workshops/postgis-intro/index.html"&gt;Introduction to PostGIS book&lt;/a&gt;. The downloaded data bundle is a 21.5 MB zip file. In the &lt;code&gt;data&lt;/code&gt; subfolder of the extracted archive, there’s a 9.5 MB file called &lt;code&gt;nyc_data.backup&lt;/code&gt;. This is a file with all of the census data from the 2000 census of New York City, along with all of the streets, neighborhoods, and subway stations in the city.&lt;/p&gt;

&lt;p&gt;We can restore the data backup directly to our Heroku Postgres instance by using &lt;a href="https://devcenter.heroku.com/articles/heroku-postgres-backups#restore-a-backup"&gt;the &lt;code&gt;heroku pg:backups:restore&lt;/code&gt; command&lt;/a&gt;. This is incredibly convenient. However, keep in mind the following caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The backup file that you can restore from cannot be uploaded from your local machine. It must be available online. Fortunately, I found a &lt;a href="https://github.com/Giorgi/PostgresSamples/blob/main/nyc_data.backup"&gt;GitHub repo&lt;/a&gt; that makes &lt;code&gt;nyc_data.backup&lt;/code&gt; available.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Performing database restore starts by completely resetting your Heroku Postgres instance, including your installation of the &lt;code&gt;postgis&lt;/code&gt; extension. So, even though we showed above how to install the extension manually, we’ll need to add a flag when we restore our database to pre-install the extension before loading the data.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the command we would use to restore the database backup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;heroku pg:backups:restore &lt;span class="se"&gt;\&lt;/span&gt;
  https://github.com/Giorgi/PostgresSamples/raw/main/nyc_data.backup &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; postgis &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-a&lt;/span&gt; postgis-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our backup file is specified through a publicly accessible URL. You can always download the dataset from the PostGIS tutorial, extract the &lt;code&gt;nyc_data.backup&lt;/code&gt; file, and post it online to a location of your own choosing.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-e postgis&lt;/code&gt; flag specifies that we want to install the &lt;code&gt;postgis&lt;/code&gt; extension prior to loading the backup’s schema and data.&lt;/p&gt;

&lt;p&gt;That was it! Not bad for a few simple commands. We have our database and data.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Why Heroku?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;If you already know how to set up Postgres on a local machine, you might be wondering why I went with Heroku. For me, the biggest reason is simplicity. Besides choosing a large enough Heroku Postgres plan for the analysis I plan to do and installing the PostGIS extension, there’s nothing else I need to do to get up and running.&lt;/p&gt;

&lt;p&gt;Also, &lt;a href="https://devcenter.heroku.com/articles/collab"&gt;collaborating&lt;/a&gt; on any analysis I do is easy. I can grant other people access to my database as collaborators, or I can quickly build an application on top of the database and share access through a normal web interface, rather than the Postgres client.&lt;/p&gt;

&lt;p&gt;Finally, when I’m done working on a project and I don’t need it any longer, I can just delete the app on Heroku and it’s all gone. No data files on my computer to worry about. No extra software installed locally. I’m able to enjoy a quick excursion into a new technology and then move on when I’m done.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Working with PostGIS&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let’s take a look at how PostGIS works. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Work just as you would with Postgres&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The first thing to remember is that PostGIS is an extension within Postgres. That means that you can also perform any standard Postgres query.&lt;/p&gt;

&lt;p&gt;Let’s say I wanted to find out how many streets in New York start with B. A simple SQL query will tell me:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;nyc_streets&lt;/span&gt;
&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;   &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;LIKE&lt;/span&gt; &lt;span class="s1"&gt;'B%'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;-------&lt;/span&gt;
  &lt;span class="mi"&gt;1282&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How about the number of neighborhoods in each borough? Again, a simple SQL query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;boroname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;   &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;nyc_neighborhoods&lt;/span&gt;
&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;   &lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;boroname&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;boroname&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;---------------+-------&lt;/span&gt;
 &lt;span class="n"&gt;Queens&lt;/span&gt;        &lt;span class="o"&gt;|&lt;/span&gt;    &lt;span class="mi"&gt;30&lt;/span&gt;
 &lt;span class="n"&gt;Brooklyn&lt;/span&gt;      &lt;span class="o"&gt;|&lt;/span&gt;    &lt;span class="mi"&gt;23&lt;/span&gt;
 &lt;span class="n"&gt;Staten&lt;/span&gt; &lt;span class="n"&gt;Island&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;    &lt;span class="mi"&gt;24&lt;/span&gt;
 &lt;span class="n"&gt;The&lt;/span&gt; &lt;span class="n"&gt;Bronx&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;    &lt;span class="mi"&gt;24&lt;/span&gt;
 &lt;span class="n"&gt;Manhattan&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;    &lt;span class="mi"&gt;28&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So far we’ve just done standard PostgreSQL. Now, let’s take a look at how to use PostGIS features.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Examples of working with geospatial geometries&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Because our dataset includes all New York streets, we can ask how many kilometers of streets there are in the city with this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_Length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                           &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;street_length&lt;/span&gt;
                           &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;nyc_streets&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
   &lt;span class="n"&gt;street_length&lt;/span&gt;
&lt;span class="c1"&gt;--------------------&lt;/span&gt;
 &lt;span class="mi"&gt;10418&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;904717199996&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can also calculate areas, such as the acreage of the entirety of Manhattan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_Area&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4047&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;acreage&lt;/span&gt;
                           &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;nyc_neighborhoods&lt;/span&gt;
                           &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;boroname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Manhattan'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="n"&gt;acreage&lt;/span&gt;
&lt;span class="c1"&gt;-------------------&lt;/span&gt;
 &lt;span class="mi"&gt;13965&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;32012239119&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that these calculations come from the geospatial data, not from columns related to aggregated data of this sort. Not only that, but these queries execute extremely quickly.&lt;/p&gt;

&lt;p&gt;One final query that I’m really amazed by involves the use of &lt;a href="https://postgis.net/workshops/postgis-intro/joins.html"&gt;spatial joins&lt;/a&gt;. Much like standard database joins, spatial joins can unite multiple tables, but on the basis of spatial relationships. For example, we can query for which neighborhood a specific subway station is in, using the spatial data. To do this, we can use &lt;code&gt;ST_Contains&lt;/code&gt; from PostGIS to determine if the geometry of the neighborhood completely contains the geometry of the subway station. Based on the name of the subway (in &lt;code&gt;nyc_subway_stations&lt;/code&gt;), we query for the neighborhood (in &lt;code&gt;nyc_neighborhoods&lt;/code&gt;) for which &lt;code&gt;ST_Contains&lt;/code&gt; is true. Our query looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;postgis&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;demo&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="k"&gt;DATABASE&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt;
                           &lt;span class="n"&gt;subways&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;subway_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="n"&gt;neighborhoods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;neighborhood_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                           &lt;span class="n"&gt;neighborhoods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;boroname&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;borough&lt;/span&gt;
                         &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;nyc_neighborhoods&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;neighborhoods&lt;/span&gt;
                         &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;nyc_subway_stations&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;subways&lt;/span&gt;
                         &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;ST_Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;neighborhoods&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subways&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                         &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;subways&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Broad St'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="n"&gt;subway_name&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;neighborhood_name&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="n"&gt;borough&lt;/span&gt;
&lt;span class="c1"&gt;-------------+--------------------+-----------&lt;/span&gt;
 &lt;span class="n"&gt;Broad&lt;/span&gt; &lt;span class="n"&gt;St&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Financial&lt;/span&gt; &lt;span class="n"&gt;District&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;Manhattan&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostGIS provides even more advanced location querying functionality with &lt;a href="https://postgis.net/workshops/postgis-intro/geometries.html"&gt;geometries&lt;/a&gt;, but that’s outside the scope of our simple demo here.&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h1&gt;

&lt;p&gt;Having never used PostGIS before, I’m really impressed by what it can do. There’s a lot more I could do with this database too, since I’ve only made it about halfway through the official &lt;a href="https://postgis.net/workshops/postgis-intro/index.html"&gt;Introduction to PostGIS&lt;/a&gt; book. Not only that, I can build and deploy applications on top of PostGIS by using any number of &lt;a href="https://www.heroku.com/languages"&gt;languages supported&lt;/a&gt; by Heroku. In particular, I’m thinking I might want to find a use case for building a Rails app on top of PostGIS. I already found &lt;a href="https://devcenter.heroku.com/articles/postgis"&gt;some documentation&lt;/a&gt; on how I can get started.&lt;/p&gt;

&lt;p&gt;But for now, I don’t need this instance anymore, so I’m going to clean it up and delete my app. From the CLI, this is what I need to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;heroku apps:destroy postgis-demo
 ▸    WARNING: This will delete ⬢ postgis-demo including all add-ons.
 ▸    To proceed, &lt;span class="nb"&gt;type &lt;/span&gt;postgis-demo or re-run this &lt;span class="nb"&gt;command &lt;/span&gt;with &lt;span class="nt"&gt;--confirm&lt;/span&gt; postgis-demo

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; postgis-demo
Destroying ⬢ postgis-demo &lt;span class="o"&gt;(&lt;/span&gt;including all add-ons&lt;span class="o"&gt;)&lt;/span&gt;... &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait, that’s all? Yeah, that’s all. With a single command and confirmation, everything is torn down and I don’t need to worry about it anymore.&lt;br&gt;
&lt;br&gt;
 &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;heroku apps
You have no apps.

&lt;span class="nv"&gt;$ &lt;/span&gt;heroku addons
No add-ons.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that I’ve deleted my app, you have an incredible opportunity: The unique app name &lt;code&gt;postgis-demo&lt;/code&gt; is available for the first reader who wants to grab it on Heroku! Are you ready to build your next great PostGIS app? Today is the day!&lt;/p&gt;

</description>
      <category>heroku</category>
      <category>postgres</category>
      <category>postgis</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>Secure your Heroku Apps with SSL</title>
      <dc:creator>Joseph Caudle</dc:creator>
      <pubDate>Sat, 16 Mar 2024 18:55:39 +0000</pubDate>
      <link>https://dev.to/josephcaudle/secure-your-heroku-apps-with-ssl-41il</link>
      <guid>https://dev.to/josephcaudle/secure-your-heroku-apps-with-ssl-41il</guid>
      <description>&lt;h2&gt;
  
  
  How to secure your apps with SSL certificates for custom domains
&lt;/h2&gt;

&lt;p&gt;If you’re building an application that needs to go toward production, you’ll undoubtedly need to serve it up securely with SSL. What that entails varies from provider to provider, and you’ll encounter differing levels of complexity (and cost) in getting it all set up.&lt;/p&gt;

&lt;p&gt;Fortunately, if you’re using Heroku to run your application, this is pretty straightforward. Because I’ve been giving Heroku another spin recently, I’m going to spend this article looking at what you’ll need to get going with SSL on the platform and why you might choose some features over others.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Heroku provide for SSL right out of the box?
&lt;/h2&gt;

&lt;p&gt;Heroku focuses on ease of use, and I love that. Whether it’s the one-click deploy straight from my GitHub repo or the plugin ecosystem that lets me drop all kinds of great things into my app from the command line, I always appreciate the out-of-the-box options I have at my fingertips. Likewise, SSL on Heroku provides some great default options.&lt;/p&gt;

&lt;p&gt;Heroku offers two main ways of &lt;a href="https://devcenter.heroku.com/articles/understanding-ssl-on-heroku"&gt;working with SSL certificates on their platform&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://devcenter.heroku.com/articles/automated-certificate-management"&gt;Automated Certificate Management (ACM)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devcenter.heroku.com/articles/ssl"&gt;Heroku SSL&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For most apps, ACM will give you a really simple, automated certificate management experience—that is the name, after all. But there may be times when you’ll need the more robust Heroku SSL option. We’ll take a look at ACM first. Then, we’ll investigate why you might need Heroku SSL and how you’d use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automated Certificate Management and how it works
&lt;/h2&gt;

&lt;p&gt;ACM is built on top of &lt;a href="https://letsencrypt.org"&gt;Let’s Encrypt&lt;/a&gt;—a free, automated certificate authority, run by a nonprofit organization committed to enhancing the security of the web in general. While organizations can &lt;a href="https://www.abetterinternet.org/sponsor/"&gt;sponsor the Let’s Encrypt project&lt;/a&gt;, there is no cost for using Let’s Encrypt-issued SSL certificates. Because the certificates are free, Heroku provides Automated Certificate Management with any of their basic plans at no additional charge.&lt;/p&gt;

&lt;p&gt;While Let’s Encrypt automates the process of requesting and issuing certificates, ACM automates the process of installing those certificates on the relevant apps for the domains they’re issued for. Other methods exist for automated management of certificates, but they’re paid add-ons, so I won’t be looking at them here. Instead, let’s show you how to add ACM to a demo app I’ve already deployed to Heroku.&lt;/p&gt;

&lt;p&gt;To be clear, there isn’t a whole lot you need to configure for ACM. In fact, if you haven’t added a custom domain to your app, the Heroku-generated URL (on the herokuapp.com domain) for your app already has SSL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Enable ACM
&lt;/h3&gt;

&lt;p&gt;Your first step is to turn on ACM. The easiest way to do this for any deployed app is with &lt;a href="https://devcenter.heroku.com/articles/heroku-cli"&gt;the Heroku CLI&lt;/a&gt;. Assuming you’re logged into Heroku via the CLI, all you need to do is run a single command to turn on ACM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku certs:auto:enable
Enabling Automatic Certificate Management... starting.
See status with heroku certs:auto or wait until active with
heroku certs:auto --wait
=== Your certificate will now be managed by Heroku.
Check the status by running heroku certs:auto.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you enable ACM before setting up a custom domain on your app, you’ll get a message like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku certs:auto
=== Automatic Certificate Management is enabled on pure-brushlands-82324

=== Add a custom domain to your app by running: heroku domains:add &amp;lt;yourdomain.com&amp;gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Add custom domain
&lt;/h3&gt;

&lt;p&gt;At this point, you simply need to do what the Heroku CLI says and add a domain to your app. You can do this in the CLI too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku domains:add really-cool-stuff.app
Configure your app's DNS provider to point to the DNS Target lively-basin-0v9xkh99iaz1yc0xldmakla5.herokudns.com.
    For help, see https://devcenter.heroku.com/articles/custom-domains

The domain really-cool-stuff.app has been enqueued for addition
Run heroku domains:wait 'really-cool-stuff.app' to wait for completion
Adding really-cool-stuff.app to ⬢ pure-brushlands-82324... done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Configure DNS
&lt;/h3&gt;

&lt;p&gt;At this point, you need to update your DNS records on your domain to point to the DNS target provided by Heroku. The &lt;a href="https://devcenter.heroku.com/articles/custom-domains"&gt;instructions in the help documentation it links to&lt;/a&gt; are detailed and clear. Typically, this means adding a CNAME record that directs to the new location provided by Heroku.&lt;/p&gt;

&lt;p&gt;Once you have your DNS configured properly, you can check on the status of your certificates. You should see output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku certs:auto
=== Automatic Certificate Management is enabled on pure-brushlands-82324

Certificate details:
Common Name(s): really-cool-stuff.app
Domain(s):      fe6ab605-91f9-4261-974c-ee85c043dbf7
Expires At:     2024-05-27 17:08 UTC
Issuer:         /CN=R3/O=Let's Encrypt/C=US
Starts At:      2024-02-27 17:09 UTC
Subject:        /CN=really-cool-stuff.app
SSL certificate is verified by a root authority.

Domain                       Status       Last Updated
───────────────────────────  ───────────  ────────────
really-cool-stuff.app        Cert issued  4 minutes

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

&lt;/div&gt;



&lt;p&gt;If you’re like me and you made a mistake when setting up the DNS records 🤦🏻, then you might see some warnings on the app dashboard in the web interface like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fsfwut7mh68aiodxxko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fsfwut7mh68aiodxxko.png" alt="ACM Failing" width="800" height="160"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or this one:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxjrhk9g0n9a4sl1jxpp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvxjrhk9g0n9a4sl1jxpp.png" alt="Domain failed validation" width="800" height="167"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fortunately, ACM repeats its validation attempts regularly for an entire hour. So, I was able to fix my issues quickly. I got to a green status pretty easily.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcguk57yqogn31ysdvmi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flcguk57yqogn31ysdvmi.png" alt="Certificate automatically managed" width="800" height="182"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’ve ever had to set up SSL on another platform before, you’re probably painfully aware that it usually isn’t this simple (and that’s putting it mildly). That said, Let’s Encrypt has some limitations that may require you to use the more robust offering: Heroku SSL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Heroku SSL and why you might want it
&lt;/h2&gt;

&lt;p&gt;While ACM gives you a really simple experience, with that simplicity also comes somewhat limited functionality. Let’s consider two examples where ACM might not meet your unique needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 1: When you need wildcard certificates
&lt;/h3&gt;

&lt;p&gt;Let’s Encrypt is restricted to providing &lt;a href="https://letsencrypt.org/docs/glossary/#def-CN"&gt;common name&lt;/a&gt; certificates on only one single domain name zone at a time. Sometimes, you might actually want to have a wildcard certificate; this is the type of functionality you might need if you have a multi-tenant application that switches between tenants at the subdomain level. ACM doesn’t support this. But Heroku SSL does.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example 2: When you need Organizational or Extended Validation (OV/EV)
&lt;/h3&gt;

&lt;p&gt;Let’s Encrypt also doesn’t &lt;em&gt;really&lt;/em&gt; guarantee that you are who you say you are. What I mean is that the type of certificate issued by Let’s Encrypt only attests that the entity requesting the certificate also controls the domain name that it’s securing. There are some scenarios when you need to ensure more than just that your domain traffic is encrypted in transit. You might need to verify that your application is actually associated with your company. In this case, you’d want to purchase an SSL certificate with either Organizational Validation (OV) or Extended Validation (EV). Those usually require checks that can’t be simply automated by a product like Let’s Encrypt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using Heroku SSL
&lt;/h3&gt;

&lt;p&gt;In the above cases, you’ll need to manually upload your own certificate. As usual, this process has been beautifully simplified by Heroku (but it’s still more work than just telling the platform to start issuing certificates with ACM).&lt;/p&gt;

&lt;p&gt;To add or update the certificate for your Heroku app, and to use a certificate you provide, you need to run a single command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ heroku certs:add server.crt server.key
Adding SSL to example... done
exampleapp now served by exemplary-sushi-4gr7rb6h8djkvo9j5zf16mfp.herokudns.com.
Certificate details:
Expires At: 2022-08-18 21:53:18 GMT
Issuer: C=US; ST=CA; L=SF; O=Heroku; CN=www.example.com
Starts At: 2021-08-18 21:53:18 GMT
...

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

&lt;/div&gt;



&lt;p&gt;Ok, so it’s not &lt;em&gt;that&lt;/em&gt; much more work than using ACM.&lt;/p&gt;

&lt;p&gt;Note that you can’t add multiple intermediate certificates to make a valid chain toward whatever root certificate has signed your new certificate. However, you can &lt;a href="https://help.salesforce.com/s/articleView?id=000386535&amp;amp;type=1"&gt;merge your intermediate certificates&lt;/a&gt; into one file so they can be uploaded as a single file.&lt;/p&gt;

&lt;p&gt;After uploading your certificate through Heroku SSL, you’ll be able to see your application protected by the SSL certificate you provided on your own. While Heroku SSL still keeps things pretty simple, keep in mind that &lt;strong&gt;you’re responsible for keeping your certificates up to date&lt;/strong&gt;. You’ll also need knowledge of how to work with certificates. While these might not be deal-breakers for you, they’re important points to remember—especially when you consider that ACM takes care of automatic renewals of SSL certificates. With ACM, the simple commands to set it up really are “set it and forget it.” That said, Heroku designed their entire certificate management system in such a way that even if you need the most advanced options, it’s still quite simple and manageable, even if you don’t have extensive certificate experience.&lt;/p&gt;

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

&lt;p&gt;While there is more you can do with SSL on Heroku, both the ACM and Heroku SSL options can provide the functionality to fit most use cases. Heroku’s &lt;a href="https://help.heroku.com/n/11/domains-routing/ssl-sni"&gt;documentation in this space&lt;/a&gt; also provides enough detail to point you in the right direction. If you haven’t secured your app with SSL (shame on you!), then the simplicity of ACM should remove any excuse you have to secure your site properly. If your use case is more complicated, then Heroku SSL should give you what you need.&lt;/p&gt;

</description>
      <category>heroku</category>
      <category>ssl</category>
    </item>
  </channel>
</rss>
