<?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: Rob Conery</title>
    <description>The latest articles on DEV Community by Rob Conery (@robconery).</description>
    <link>https://dev.to/robconery</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%2F138692%2Fb489f9e7-3284-474d-9885-aef9eeacbcfb.jpeg</url>
      <title>DEV Community: Rob Conery</title>
      <link>https://dev.to/robconery</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/robconery"/>
    <language>en</language>
    <item>
      <title>Postgres For Those Who Can’t Even, Part 2 – Working with Node and JSON</title>
      <dc:creator>Rob Conery</dc:creator>
      <pubDate>Wed, 05 Feb 2020 21:06:32 +0000</pubDate>
      <link>https://dev.to/robconery/postgres-for-those-who-can-t-even-part-2-working-with-node-and-json-42fo</link>
      <guid>https://dev.to/robconery/postgres-for-those-who-can-t-even-part-2-working-with-node-and-json-42fo</guid>
      <description>&lt;p&gt;This is part 2 of a series of posts I’m doing for a friend who’s a JavaScript developer that, according to him, knows next to nothing about Postgres. &lt;a href="https://dev.to/robconery/postgresql-for-those-who-can-t-even-part-1-36kh"&gt;You can read part 1 right here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I &lt;a href="https://rob.conery.io/category/postgres/" rel="noopener noreferrer"&gt;write a lot about Postgres&lt;/a&gt;, but I don’t think I’ve written enough about how to get started from the absolute beginning, so that’s what we’re doing here.&lt;/p&gt;

&lt;p&gt;In this post, I’m continuing with his questions to me – but this time it has less to do with the database side of things and more to do with Node and how you can use Postgres for fun and profit. Let’s roll.&lt;/p&gt;

&lt;h2&gt;
  
  
  How should I structure my code?
&lt;/h2&gt;

&lt;p&gt;This question has more to do with your preferences or what your company/boss have set up. I can show you how &lt;em&gt;I&lt;/em&gt; do things, but your situation is probably a lot different.&lt;/p&gt;

&lt;p&gt;OK, enough prevaricating. Here’s what I’ve done in the past with super simple projects that where I’m just musing around.&lt;/p&gt;

&lt;h3&gt;
  
  
  Give PG It’s Own Module
&lt;/h3&gt;

&lt;p&gt;I like putting all my code inside of a &lt;code&gt;lib&lt;/code&gt; directory, and then inside there I’ll create a a &lt;code&gt;pg&lt;/code&gt; directory with specific connection things etc for Postgres. It looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaper-attachments.dropbox.com%2Fs_30B0209EA5A4D093DB15C347247819FD01F943B96E3D94DDBA52EFBEEF183D14_1580766887619_shot_49.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaper-attachments.dropbox.com%2Fs_30B0209EA5A4D093DB15C347247819FD01F943B96E3D94DDBA52EFBEEF183D14_1580766887619_shot_49.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You’ll also notice I have a &lt;code&gt;.env&lt;/code&gt; file, which is something that goes into every single project of mine. It’s a file that holds environmental variables that I’ll be using in my project. In this case, I &lt;em&gt;do not want&lt;/em&gt; my connection string hardcoded anywhere – so I pop it into a &lt;code&gt;.env&lt;/code&gt; file where it’s loaded automatically by my shell (zshell and, for those interested, I use the &lt;code&gt;dotenv&lt;/code&gt; plugin with &lt;a href="https://ohmyz.sh/" rel="noopener noreferrer"&gt;Oh-My-Zsh)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There’s a single file inside of the &lt;code&gt;lib/pg&lt;/code&gt; directory called &lt;code&gt;runner.js&lt;/code&gt;, and it has one job: &lt;em&gt;run the raw SQL queries&lt;/em&gt; using pg-promise:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pgp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg-promise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;pgp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;oneOrNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;execute&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;none&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;I usually have 3 flavors of query runners:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One that will return 0 to &lt;em&gt;n&lt;/em&gt; records&lt;/li&gt;
&lt;li&gt;One that will return a single record&lt;/li&gt;
&lt;li&gt;One that executes a “passthrough” query that doesn’t return a result&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also like to have one that closes the connections down. Normally you wouldn’t call this in your code because the driver (which is pg-promise in this case) manages this for you and you want to be sure you draw on its pool of connections – don’t spin your own. That said, sometimes you might want to run a script or two, maybe some integration tests might hit the DB – either way a graceful shutdown is nice to have.&lt;/p&gt;

&lt;p&gt;We can use this code in the rest of our app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./lib/pg/runner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;select * from master_plan limit 10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Neat! It works well but yes, we’ll end up with SQL all over our code so let’s fix that.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Little Bit of Abstraction
&lt;/h3&gt;

&lt;p&gt;The nice thing about Node is that your modules can be single files, or you can expand them to be quite complex – without breaking the code that depends on them. I don’t want my app code to think about the SQL that needs to be written – I’d rather just offer a method that gives the data I want. In that case, I’ll create an &lt;code&gt;index.js&lt;/code&gt; file for my &lt;code&gt;pg&lt;/code&gt; module, which returns a single method for my query called &lt;code&gt;masterPlan&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./runner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;masterPlan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`select * from master_plan limit &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shutDown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
  &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;runner&lt;/code&gt; here is the same runner that I used before, this time it’s in the same directory as the calling code. I’ve exposed two methods on the index as that’s all I need for right now. This is kind of like a &lt;a href="https://dev.to/kylegalbraith/getting-familiar-with-the-awesome-repository-pattern--1ao3"&gt;Repository Pattern&lt;/a&gt;, which comes with a few warnings attached.&lt;/p&gt;

&lt;p&gt;People have been arguing about data access for decades. What patterns to use, how those patterns fit into the larger app you’re building, etc, etc, etc. It’s really annoying.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Applications always start small&lt;/em&gt; and then grow. That’s where the issues come in. The Repository Pattern looks nice and seems wonderful until you find yourself writing &lt;code&gt;Orders.getByCustomer&lt;/code&gt; and &lt;code&gt;Customer.getOrders&lt;/code&gt;, wondering if this is really what you wanted to do with your life.&lt;/p&gt;

&lt;p&gt;This is a rabbit hole I don’t want to go down further so, I’ll kindly suggest that if you have a simple app with 10-20 total queries, this level of control and simplicity of approach might work really well. If your app will grow (which I’m sure it will whether you think so or not), it’s probably a good idea to use some kind of library or relational mapper (ORM), which I’ll get to in just a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I put JSON in it?
&lt;/h2&gt;

&lt;p&gt;One of the fun things about Node is that you can work with JSON everywhere. It’s fun, I think, to not worry about data types, migrations, and relational theory when you’re trying to get your app off the ground.&lt;/p&gt;

&lt;p&gt;The neat thing about Postgres is that it supports this and it’s blazing fast. Let’s see how you can set this up with Postgres.&lt;/p&gt;

&lt;h3&gt;
  
  
  Saving a JSONB Document
&lt;/h3&gt;

&lt;p&gt;Postgres has native support for binary JSON using a datatype called “JSONB”. It behaves just like JSON but you can’t have duplicate keys. It’s also super fast because you can index it in a variety of ways.&lt;/p&gt;

&lt;p&gt;Since we’re going to store our data in a JSONB field, we can create a “meta” table in Postgres that will hold that data. All we need is a primary key, a timestamp and the field to hold the JSON:&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="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;my_document_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="n"&gt;jsonb&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We can now save data to it using a query 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="k"&gt;insert&lt;/span&gt; &lt;span class="k"&gt;into&lt;/span&gt; &lt;span class="n"&gt;my_document_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'{"name":"Burke Holland"}'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And &lt;em&gt;yuck&lt;/em&gt;. Why would anyone want to do something like this? Writing delimited JSON by hand is gross, let’s be good programmers and wrap this in a function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./runner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;//in pg/index.js&lt;/span&gt;
&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveDocument&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;insert into my_document_table (doc) values ($1)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This works really well, primarily because our Node driver (pg-promise) understands how to translate JavaScript objects into something Postgres can deal with. We just pass that in as an argument.&lt;/p&gt;

&lt;p&gt;But we can do better than this, don’t you think?&lt;/p&gt;

&lt;h3&gt;
  
  
  Sprinkling Some Magical Abstraction
&lt;/h3&gt;

&lt;p&gt;One of the cool things about using a NoSQL system is that you can create a document table on the fly. We can do that easily with Postgres but we just need to tweak our &lt;code&gt;saveDocument&lt;/code&gt; function a bit. In fact we need to &lt;em&gt;tweak a lot of things&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Let’s be good programmers and create a brand new file called &lt;code&gt;jsonb.js&lt;/code&gt; inside our &lt;code&gt;pg&lt;/code&gt; directory, right next to our &lt;code&gt;runner.js&lt;/code&gt; file. The first thing we’ll do is to create a way to save &lt;em&gt;any&lt;/em&gt; document and, if we get an error about a table not existing, we’ll create it on the fly!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;save&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`insert into &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; (doc) values ($1) returning *`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newDoc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
    &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;newDoc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;does not exist&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="c1"&gt;//create the table on the fly&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createDocTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createDocTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    create table &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;(
    id serial primary key,
    doc jsonb not null,
    created_at timestamp not null default now()
  )`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`
    create index idx_json_&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 
    on &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; 
    USING GIN (doc jsonb_path_ops)
  `&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;We have two groovy functions that we can use to save a document to Postgres with the sweetness of a typical NoSQL, friction-free experience. A few things to note about this code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We’re catching a specific error when a table doesn’t exist in the database. There’s probably a better way to do that, so feel free to play around. If there’s an error, we’re creating the table and then calling the &lt;code&gt;save&lt;/code&gt; function one more time.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;createDocTable&lt;/code&gt; function also pops an index on the table which uses &lt;code&gt;jsonb_path_ops&lt;/code&gt;. That argument tells Postgres to index &lt;em&gt;every key&lt;/em&gt; in the document. This might not be what you want, but indexing is a good thing for smaller documents.&lt;/li&gt;
&lt;li&gt;We’re using a fun clause at the end of our &lt;code&gt;insert&lt;/code&gt; SQL statement, specifically &lt;code&gt;returning *&lt;/code&gt; which will return the entire, newly-created record, which we can then pass on to our calling code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see if it works!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//index.js of our project&lt;/span&gt;
&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mavis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mavis@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shutDown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaper-attachments.dropbox.com%2Fs_30B0209EA5A4D093DB15C347247819FD01F943B96E3D94DDBA52EFBEEF183D14_1580852000207_shot_51.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaper-attachments.dropbox.com%2Fs_30B0209EA5A4D093DB15C347247819FD01F943B96E3D94DDBA52EFBEEF183D14_1580852000207_shot_51.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well look at that would ya! It works a treat.&lt;/p&gt;

&lt;p&gt;But what about updates and deletes? Deleting a document is a simple SQL statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`delete from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; where id=$1`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;You can decide what to return from here if you want, I’m just returning &lt;code&gt;true&lt;/code&gt;. Updating is a different matter, however.&lt;/p&gt;

&lt;h3&gt;
  
  
  Updating an existing JSONB document
&lt;/h3&gt;

&lt;p&gt;One of the problems with JSONB and Postgres in the past (&amp;lt; 9.5) was that in order to update a document you had to wholesale update it – a “partial” update wasn’t possible. With Postgres 9.5 that changed with the &lt;code&gt;jsonb_set&lt;/code&gt; method, which requires a key and a JSONB element.&lt;/p&gt;

&lt;p&gt;So, if we wanted to change Mavis’s email address, we could use this SQL statement:&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="k"&gt;update&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt; 
&lt;span class="k"&gt;set&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jsonb_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'{"email"}'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'"mavis@example.com"'&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

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

&lt;/div&gt;



&lt;p&gt;That syntax is weird, don’t you think? I do. It’s just not very intuitive as you need to pass an “array literal” to define the key and a string value as the new value.&lt;/p&gt;

&lt;p&gt;To me it’s simpler to just concatenate a new value and do a wholesale save. It’s nice to know that a partial update is &lt;em&gt;possible&lt;/em&gt; if you need it, but overall I’ve never had a problem just running a complete update like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;modify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`update customers SET
              doc = (doc || $1) 
              where id = $2 returning *; `&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;||&lt;/code&gt; operator that you see there is the JSONB concatenation operator which will update an existing key in a document or add one if it’s not there. Give it a shot! See if it updates as you expect.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying a JSONB document by ID
&lt;/h3&gt;

&lt;p&gt;This is the nice thing about using a relational system like Postgres: querying by &lt;code&gt;id&lt;/code&gt; &lt;em&gt;is just a simple SQL statement.&lt;/em&gt; Let’s create a new function for our &lt;code&gt;jsonb&lt;/code&gt; module called &lt;code&gt;get&lt;/code&gt;, which will return a document by ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`select * from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; where id=$1`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Simple enough! You’ll notice that i’m adding the &lt;code&gt;id&lt;/code&gt; of the row in Postgres to the document itself. I could drop that into the document itself, if I wanted, but it’s simple enough to tack it on as you see. In fact, I think I’d like to ensure the &lt;code&gt;created_at&lt;/code&gt; timestamp is on too, so let’s formalize this with some transformations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transformRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createdAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transformSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;transformRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This will take the raw record from Postgres and turn it into something a bit more usable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Querying a document using criteria
&lt;/h3&gt;

&lt;p&gt;We can pull data out of our database using an id, but we need another way to query if we’re going to use this properly.&lt;/p&gt;

&lt;p&gt;You can query documents in Postgres using a special operator: &lt;code&gt;@&amp;gt;&lt;/code&gt;. There are other operators, but this is the one we’ll need for 1) querying specific keys and 2) making sure we use an index. There are all kinds of operators and functions for JSONB within Postgres and you can &lt;a href="https://www.postgresql.org/docs/12/functions-json.html" rel="noopener noreferrer"&gt;read more about them here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To query a document for a given key, you can do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;
&lt;span class="nx"&gt;where&lt;/span&gt; &lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;{"name":"Burke Holland"}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;This query is simply for documents where the key/value &lt;code&gt;{name:&lt;/code&gt; &lt;code&gt;"&lt;/code&gt;&lt;code&gt;Burke Holland&lt;/code&gt;&lt;code&gt;"&lt;/code&gt;&lt;code&gt;}&lt;/code&gt; exists. That critieria is simply JSON, which means we can pass that right through to our driver… and behold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;exports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`select * from &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tableName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; where doc @&amp;gt; $1`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;record&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;transformSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;record&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Let’s run this and see if it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;customers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mavis@test.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shutDown&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaper-attachments.dropbox.com%2Fs_30B0209EA5A4D093DB15C347247819FD01F943B96E3D94DDBA52EFBEEF183D14_1580852939679_shot_52.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fpaper-attachments.dropbox.com%2Fs_30B0209EA5A4D093DB15C347247819FD01F943B96E3D94DDBA52EFBEEF183D14_1580852939679_shot_52.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hey that’s pretty swell! You don’t need to use dedicated JSON operators to query a JSONB document in Postgres. If you’re comfortable with SQL, you can just execute a regular old query and it works just fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="nx"&gt;customers&lt;/span&gt;
&lt;span class="nf"&gt;where &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;ilike&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Mav%&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Here, we’re pulling the &lt;code&gt;name&lt;/code&gt; key from the document using the JSON text selector (&lt;code&gt;-&amp;gt;&amp;gt;&lt;/code&gt;), and then doing a fuzzy comparison using &lt;code&gt;ilike&lt;/code&gt; (case-insensitive comparison). This works pretty well but it can’t use the index we setup and that might make your DBA mad.&lt;/p&gt;

&lt;p&gt;That doesn’t mean you can’t index it – you can!&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="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;index&lt;/span&gt; &lt;span class="n"&gt;idx_customer_name&lt;/span&gt; 
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'name'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Works just like any other index!&lt;/p&gt;

&lt;h3&gt;
  
  
  Play around, have some fun…
&lt;/h3&gt;

&lt;p&gt;I &lt;a href="https://gist.github.com/robconery/93aaec861fdf0cf3f5ff3f30f1cf11d5" rel="noopener noreferrer"&gt;made a gist&lt;/a&gt; out of all of this if you want to goof around. There are things to add, like updates/partial updates, and I encourage you to play and have a good time.&lt;/p&gt;

&lt;p&gt;If you’re wondering, however, if someone, somewhere, might have baked this stuff into a toolset… indeed! They did…&lt;/p&gt;

&lt;h2&gt;
  
  
  Are there any ORM-like tools in it? What do you recommend?
&lt;/h2&gt;

&lt;p&gt;So here’s the thing: if you’re coming to this post from a Java/C#/Enterprise-y background, the “ORM” tools in the Node world are going to look … well a bit different. I don’t know the reason why and I could pontificate about Node in the enterprise or how Node’s moduling system pushes the idea of isolation… but… let’s just skip all of that OK?&lt;/p&gt;

&lt;p&gt;The bottom line is this: you can do data access with Node, but if you’re looking for an industrial strength thing to rival Entity Framework you might be dissapointed. With that said – let’s have a look…&lt;/p&gt;

&lt;h3&gt;
  
  
  My favorite: MassiveJS
&lt;/h3&gt;

&lt;p&gt;I am 100% completely biased when it comes to &lt;a href="https://massivejs.org/" rel="noopener noreferrer"&gt;MassiveJS&lt;/a&gt; because… well &lt;em&gt;I created it&lt;/em&gt; along with my friend &lt;a href="https://www.openmymind.net/" rel="noopener noreferrer"&gt;Karl Seguin&lt;/a&gt; back in 2011 or so. The idea was to build a simple data access tool that would help you avoid writing too much SQL. It morphed into something much, much fun.&lt;/p&gt;

&lt;p&gt;With version 2 I devoted Massive to Postgres completely and was joined by the current owner of the project, &lt;a href="https://di.nmfay.com/about" rel="noopener noreferrer"&gt;Dian Fay&lt;/a&gt;. I can’t say enough good things about Dian – she’s amazing at every level and has turned this little project into something quite amazing. Devoting Massive 100% to Postgres freed us up to do all kinds of cool things – including one of the things I love most: &lt;a href="https://massivejs.org/docs/working-with-documents" rel="noopener noreferrer"&gt;document storage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The code you read above was inspired by the work we did with JSONB and Massive. You can have a fully-functioning document storage solution that kicks MongoDB in the face in terms of speed, fuzzy searches, full-text indexing, ACID guarantees and a whole lot more. Massive gives you the same, simple document API and frictionless experience you get with Mongo with a much better database engine underneath.&lt;/p&gt;

&lt;p&gt;To work with Massive, you create an instance of your database which reads in all of your tables and then allows you to query them as if they were properties (the examples below are taken from the documentation):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;massive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;massive&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;massive&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;localhost&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appdb&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appuser&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apppwd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;poolSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;//save will update or insert based on the presence of an&lt;/span&gt;
&lt;span class="c1"&gt;//ID field&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;homepage&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// retrieve active tests 21-30&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://massivejs.org/docs/working-with-documents" rel="noopener noreferrer"&gt;Working with documents&lt;/a&gt; looks much the same as the relational stuff above, but it’s stored as JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;saveDoc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Week 12 Throughput&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1 East&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2 East&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&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;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}]&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Finally, the thing I love most about the project is what Dian has done with the documentation (linked above). She goes into detail about every aspect of the tool – even &lt;a href="https://massivejs.org/docs/framework-examples" rel="noopener noreferrer"&gt;how to use it with popular web frameworks&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sequelize
&lt;/h3&gt;

&lt;p&gt;One of the more popular data access tools – let’s call it a full on ORM – is &lt;a href="https://sequelize.org/v5/" rel="noopener noreferrer"&gt;Sequelize&lt;/a&gt;. This tool is a traditional ORM in every sense in that it allows you create classes and save them to multiple different &lt;a href="https://sequelize.org/v5/manual/dialects.html" rel="noopener noreferrer"&gt;storage engines&lt;/a&gt;, including Postgres, MySQL/MariaDB SQLite and SQL Server. It’s &lt;em&gt;kind of not an ORM&lt;/em&gt; though because there is no mapping (the "M") that you can do aside from a direct 1:1, ActiveRecord style. For that, you can project what you need using &lt;code&gt;map&lt;/code&gt; and I’ll just leave that discussion right there.&lt;/p&gt;

&lt;p&gt;If you’ve used ActiveRecord (Rails or the pattern itself) before then you’ll probably feel really comfortable with Sequelize. I used it once on a project and found its use straightforward and simple to understand. &lt;a href="https://sequelize.org/v5/manual/getting-started.html" rel="noopener noreferrer"&gt;Getting started&lt;/a&gt; was also straightforward, as with any ORM, and the only question is how well an ActiveRecord pattern fits your project’s needs &lt;strong&gt;both now and into the future&lt;/strong&gt;. That’s for you to decide and this is where I hit the architectural eject button again (even though I did once before which didn’t seem to work).&lt;/p&gt;

&lt;p&gt;Let’s have a look at some of the examples that come from the documentation.&lt;/p&gt;

&lt;p&gt;Connecting is straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Sequelize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;sequelize&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sequelize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Sequelize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;postgres://user:pass@example.com:5432/dbname&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Declaring a “model” in Sequelize is matter of creating a class and extending from &lt;code&gt;Sequelize.Model&lt;/code&gt; or using a built-in definition method. I prefer the latter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// attributes&lt;/span&gt;
  &lt;span class="na"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;allowNull&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;
    &lt;span class="c1"&gt;// allowNull defaults to true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// options&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Sequelize is capable of using this model definition and generating, or "sychronizing" your database just like Django’s ORM does. That’s really helpful in the early days of your project or if you just hate migrations as much as I do.&lt;/p&gt;

&lt;p&gt;Sequelize is an outstanding data tool that allows you to work with your database in a seamless way. It has powerful queries and can handle some pretty intense filtering:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a project&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="p"&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="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt; 

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

&lt;/div&gt;



&lt;p&gt;If you’ve worked with Rails and ActiveRecord Sequelize should feel familiar when it comes to associations, hooks and scopes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;beforeValidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mood&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;happy&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;afterValidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Toni&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;sequelize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nx"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sequelize&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;STRING&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;sequelize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;modelName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;project&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hasOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;And there you have it. The documentation for Sequelize is very complete as well, with examples and SQL translations so you know what query will be produced for every call.&lt;/p&gt;

&lt;h3&gt;
  
  
  But what about…?
&lt;/h3&gt;

&lt;p&gt;There are so many tools out there that can help you with Node and data access and I’m sure I’ve left a few off, so feel free to add your favorite in the comments. Please be sure it works with Postgres AND please be sure to indicate why you like it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Postgres is neat and all but how do I deploy my database?
&lt;/h2&gt;

&lt;p&gt;Great question! That will have to be a topic for Part 3, unfortunately as this post is quite long and I have a lot of ideas. We’ll go simple and low fidelity with a simple docker container push, and then look at some of the hosted, industrial strength solutions out there – including &lt;a href="https://azure.microsoft.com/en-us/services/postgresql/" rel="noopener noreferrer"&gt;Azure’s Managed Postgres offering!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>postgres</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>PostgreSQL For Those Who Can’t Even, Part 1</title>
      <dc:creator>Rob Conery</dc:creator>
      <pubDate>Fri, 24 Jan 2020 19:03:50 +0000</pubDate>
      <link>https://dev.to/robconery/postgresql-for-those-who-can-t-even-part-1-36kh</link>
      <guid>https://dev.to/robconery/postgresql-for-those-who-can-t-even-part-1-36kh</guid>
      <description>&lt;p&gt;Just yesterday I was talking to a friend about Postgres (not uncommon) and he said something that I found &lt;em&gt;shocking&lt;/em&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I can’t even with Postgres, I know JACK SQUAT&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This person calls themself my &lt;em&gt;friend&lt;/em&gt; too! I just don’t even know what’s real anymore.&lt;/p&gt;

&lt;p&gt;So, &lt;strong&gt;Friendo&lt;/strong&gt; is a Node person who enjoys using a document database. Can’t blame him – it’s easy to setup, easy to run and you don’t need to stress out about SQL and relational theory. That said, there are benefits to wrapping structure and rules around your data – it &lt;em&gt;is&lt;/em&gt; the lifeblood of your business after all.&lt;/p&gt;

&lt;p&gt;If you’re like Friendo and you want to start from the very beginning with Postgres, read on! I’ll use his questions to me for the rest of this post. He has &lt;em&gt;a lot&lt;/em&gt; of questions, so I’m going to break this up into parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Part 1 (this post) is for people who’ve never thought about a database before, let alone set one up and run a query&lt;/li&gt;
&lt;li&gt;Part 2 (next post) will be for Node people wondering what/why/how they could work with Postgres&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I encourage you to play along if you’re curious. If you’re having fun and want to do more, &lt;a href="https://bigmachine.io/products/a-curious-moon/"&gt;I wrote a really fun book&lt;/a&gt; about Postgres and the data from the Cassini mission (which you’ll see below) that you’re welcome to check out too!&lt;/p&gt;

&lt;h2&gt;
  
  
  Where is Postgres? How do I get it and run it?
&lt;/h2&gt;

&lt;p&gt;The easiest possible thing you can do is to run a docker image, which you can do by executing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 5432:5432 postgres:12.1

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



&lt;p&gt;That will download and run a Postgres image, exposing the default Postgres port of 5432.&lt;/p&gt;

&lt;p&gt;If you’re not a Docker person and are on a Mac, you can also &lt;a href="https://postgresapp.com"&gt;head over to postgresapp.com&lt;/a&gt; where you can download a free executable app.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I manage it with a tool?
&lt;/h2&gt;

&lt;p&gt;Tooling for Postgres is both abundant and wanting. There is no clear cut answer to this question other than to offer the following options for a given context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Just playing around: Mac&lt;/strong&gt; If you’re on a Mac go get yourself a free copy of &lt;a href="https://eggerapps.at/postico/"&gt;Postico&lt;/a&gt;. It’s easy and you can quickly connect and start playing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bfKCh0D4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579124251449_table-content-view.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bfKCh0D4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579124251449_table-content-view.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Just playing around: Windows (and Mac)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There’s the free &lt;a href="https://docs.microsoft.com/en-us/sql/azure-data-studio/download-azure-data-studio?view=sql-server-ver15"&gt;Azure Data Studio&lt;/a&gt; which uses the same interface as VS Code. There are extensions and all kinds of goodies you can download if you want as well.&lt;/p&gt;

&lt;p&gt;To hook up to Postgres, make sure you grab the &lt;a href="https://docs.microsoft.com/en-us/sql/azure-data-studio/postgres-extension?view=sql-server-ver15"&gt;Postgres extension&lt;/a&gt;. You can install it right from the IDE by clicking on the square thingies in the bottom left of the left-most pane.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7Kgk_zvS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579124323609_image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7Kgk_zvS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579124323609_image.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Something substantial and you’re willing to pay for it (Windows and Mac)&lt;/strong&gt;My go-to tool for working with Postgres is &lt;a href="https://www.navicat.com/en/products/navicat-for-postgresql"&gt;Navicat&lt;/a&gt;. It’s a bit on the spendy side but you can do all kinds of cool things, including reports, charting, import/export, data modeling and more. I love this thing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--i95Y6KSs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579124634184_image.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--i95Y6KSs--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579124634184_image.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Don’t know what to choose? Just download &lt;strong&gt;Azure Data Studio&lt;/strong&gt; and let’s get to work!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our first login&lt;/strong&gt; Let’s connect to our new shiny Postgres server. Open up Azure Data Studio and make sure you have the Postgres extension installed. You’ll know if you do because you’ll see the option to connect to PostgreSQL in the connection dialog:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7fzlNYhH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579209401730_shot_05.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7fzlNYhH--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579209401730_shot_05.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The server name is “localhost” and the Docker image comes with the login preset – “postgres” as the user name and “postgres” as the password.&lt;/p&gt;

&lt;p&gt;We’ll go with the default database and, finally, name our connection “Local Docker”. Click “Connect” and you’re good to go.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our first database&lt;/strong&gt; Most GUI tools have some way of creating a database right through the UI. Azure Data Studio doesn’t (for Postgres at least) but that’s OK, we’ll create one for ourselves.&lt;/p&gt;

&lt;p&gt;If you’ve connected already, you might be wondering “what, exactly, am I connected to”? Good question Friendo! You’re connected to the default database, “postgres”:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Se8WEg0g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579209663733_shot_06.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Se8WEg0g--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579209663733_shot_06.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the admin playground, where you can do DBA stuff and feel rad. We’re going to use our connection to this database to create another one, where we’re going to drop some data. To do that, we need to write a new query. Click that button that says “New Query”:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ww1H5F61--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579209798287_shot_07.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ww1H5F61--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579209798287_shot_07.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the new query window add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;database&lt;/span&gt; &lt;span class="n"&gt;cassini&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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



&lt;p&gt;Now hit “F5” to run the query. You should see a success message like so:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--g10hNth1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579209996613_shot_08.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--g10hNth1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579209996613_shot_08.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you see a syntax error, check your SQL code and make sure there are no errors. You’ll also notice that nothing changed in the left information pane – there’s no “cassini” database! What gives!&lt;/p&gt;

&lt;p&gt;Ease up Friendo! Just right click on the “Databases” folder and refresh – you should see your new database. Once you see, double-click it and in we go!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Our first table&lt;/strong&gt; Our database is going to hold some fun information from the Cassini Mission, the probe that we sent to Saturn back in 1997. All of the data generated by the project is public domain, and it’s pretty fun to use &lt;em&gt;that data&lt;/em&gt; rather then some silly blog posts don’t ya think?&lt;/p&gt;

&lt;p&gt;There’s &lt;a href="https://pds-atmospheres.nmsu.edu/data_and_services/atmospheres_data/Cassini/Cassini.html"&gt;a whole lot of data&lt;/a&gt; you can download, but let’s keep things reasonable and go with the “Master Plan” – the dates, times and descriptions of everything Cassini did during it’s 20 year mission to Saturn. I trimmed it just a bit to bring the file size down, so if you want to play along you can &lt;a href="https://www.dropbox.com/s/fno2olahpdoh3r7/master_plan.csv?dl=0"&gt;download the CSV from here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bZYY9zjw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579210762545_shot_09.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bZYY9zjw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579210762545_shot_09.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We’ll load this gorgeous data in just one second. We have to create a table for it first! Let’s do that now by opening a new query window in Azure Data Explorer (which I hope you remember how to do). Make sure you’re connected to the “cassini” database, and then enter the following SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;master_plan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;team&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

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



&lt;p&gt;This command will, as you might be able to guess, create a table called “master_plan”. A few things to note:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Postgres likes things in lower case and will do it for you unless you force it to do otherwise, which we won’t.&lt;/li&gt;
&lt;li&gt;We don’t have a primary key defined, this is intentional and you’ll see why in a second.&lt;/li&gt;
&lt;li&gt;There are a number of ways to store strings in Postgres, but the simplest is &lt;code&gt;text&lt;/code&gt;, without a length description. This is counterintuitive for people coming from other databases who think this will take up space. It won’t, Postgres is much smarter than that.&lt;/li&gt;
&lt;li&gt;Why are we storing a field called “date” as &lt;code&gt;text&lt;/code&gt;? For a very good reason which I’ll go over in just a minute.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OK, run this and we should have a table. Let’s load some data!&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I load data into it?
&lt;/h2&gt;

&lt;p&gt;We’re going to load data directly from a CSV, which Postgres can do using the &lt;code&gt;COPY&lt;/code&gt; command. For this to work properly, however, we need to be sure of a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We need to have the absolute path to the CSV file.&lt;/li&gt;
&lt;li&gt;The structure of the file needs to match the structure of our table.&lt;/li&gt;
&lt;li&gt;The data types need to match, in terms of format, the data types of our table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last bit is the toughest part. CSV (and spreadsheets in general) tend to be a minefield of poorly chewed data-droppings, mostly because spreadsheet programs suck at enforcing data rules.&lt;/p&gt;

&lt;p&gt;We have two ways to get around this: suffer the pain and correct the data when we import it or &lt;strong&gt;make sure all the import columns in our database table are&lt;/strong&gt; &lt;code&gt;text&lt;/code&gt;. The latter is the easiest because correcting the data using database queries tends to be easier than editing a CSV file, so that’s what we’ll do. Also: &lt;em&gt;it’s a good idea not to edit the source of an import.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Right – let’s get to it! If you’re running Docker you’ll need to copy the &lt;code&gt;master_plan&lt;/code&gt; CSV file into your running container. I put my file in my home directory on my host. If you’ve done the same, you can use this command to copy the file into your container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker cp ~/master_plan.csv [CONTAINER ID]:master_plan.csv

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



&lt;p&gt;Once it’s there, you can execute the &lt;code&gt;COPY&lt;/code&gt; command to push data into the &lt;code&gt;master_plan&lt;/code&gt; table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; &lt;span class="n"&gt;master_plan&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="s1"&gt;'/master_plan.csv'&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;DELIMITER&lt;/span&gt; &lt;span class="s1"&gt;','&lt;/span&gt; &lt;span class="n"&gt;HEADER&lt;/span&gt; &lt;span class="n"&gt;CSV&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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



&lt;p&gt;This command will grab the CSV file from our container’s root directory (as that’s where we copied it) and pop the data in positionally into our table. We just have to be sure that the columns align, which they do!&lt;/p&gt;

&lt;p&gt;The last line specifies our delimiter (which is a comma) and that there are column headers. The final bit tells Postgres this is a CSV file.&lt;/p&gt;

&lt;p&gt;Let’s make sure the data is there and looks right. Right-click on the table and select “Select top 1000 rows” and you should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vfSiSzOZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579725726323_shot_24.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vfSiSzOZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579725726323_shot_24.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Yay data! Before we do anything else, let’s add a primary key so I don’t freak out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;master_plan&lt;/span&gt;
&lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;serial&lt;/span&gt; &lt;span class="k"&gt;primary&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

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



&lt;p&gt;Great! Now we’re ready to connect from Node.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do I connect to it from Node?
&lt;/h2&gt;

&lt;p&gt;Let’s keep this as simple as possible, for now. Start by creating a directory for the code we’re about to write and then initializing a Node project. Feel free to use Yarn or NPM or whatever!&lt;/p&gt;

&lt;p&gt;Open up a terminal and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir pg_demo
cd pg_demo
npm init -y
npm install pg-promise
touch index.js

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



&lt;p&gt;These commands should work in Powershell on Windows just fine.&lt;/p&gt;

&lt;p&gt;We’ll be using the &lt;a href="https://github.com/vitaly-t/pg-promise"&gt;promise-based Postgres driver&lt;/a&gt; from Vitaly Tomalev called &lt;code&gt;pg-promise&lt;/code&gt;, one of my favorites. The default Node driver for Postgres works with standard callbacks, and we want promises! There are also a few enhancements that Vitaly thew in which are quite nice, but I’ll leave that for you to explore.&lt;/p&gt;

&lt;p&gt;The first step is to require the library and connect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pgp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pg-promise&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)({});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;pgp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;postgres://postgres:postgres@localhost/cassini&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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



&lt;p&gt;I’m connecting to Postgres using a URL-based connection string that has the format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres://user:password@server/db_name

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



&lt;p&gt;Since we’re using Docker, our default username and password is “postgres”. You can, of course, change that as needed.&lt;/p&gt;

&lt;p&gt;Once we’ve set up the connection, let’s execute a query using some very simple SQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;select * from master_plan limit 10&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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



&lt;p&gt;Because pg-promise is promise-based, I can use the &lt;code&gt;async&lt;/code&gt; and &lt;code&gt;await&lt;/code&gt; keywords to run a simple query. &lt;code&gt;db.any&lt;/code&gt; will return a list of results and all I need to do is to pass in a SQL string, as you see i did. I made sure to &lt;code&gt;limit&lt;/code&gt; the results to 10 because I don’t want all 60,000 records bounding back at me.&lt;/p&gt;

&lt;p&gt;To execute the query, I call the method and handle the returned promise. I’ll pop the result out to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;end&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

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



&lt;p&gt;The last line in the &lt;code&gt;finally&lt;/code&gt; block closes off the default connection pool, which isn’t required but the Node process won’t terminate unless you do (you’ll have to ctrl-c to stop it otherwise).&lt;/p&gt;

&lt;p&gt;You can run the file using &lt;code&gt;node index.js&lt;/code&gt; from the terminal, and you should see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tVO8P3HJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579728450821_shot_25.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tVO8P3HJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://paper-attachments.dropbox.com/s_1FC55FF691E3C173A43C1C315DD0B563BE10884F81292ABAC9C59C8E67BDDA03_1579728450821_shot_25.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Glorious data! Notice it all comes back in lovely, formatted JSON, just as we like.&lt;/p&gt;

&lt;p&gt;There’s a lot more we can do, but this post is already quite long and I think Friendo might have a few more questions for me. I’ll see if he does and I’ll follow up next time!&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Fine Tuning Full Text Search with PostgreSQL 12</title>
      <dc:creator>Rob Conery</dc:creator>
      <pubDate>Tue, 29 Oct 2019 21:17:30 +0000</pubDate>
      <link>https://dev.to/robconery/fine-tuning-full-text-search-with-postgresql-12-589m</link>
      <guid>https://dev.to/robconery/fine-tuning-full-text-search-with-postgresql-12-589m</guid>
      <description>&lt;p&gt;I’ve &lt;a href="https://dev.to/robconery/setting-up-a-fast-comprehensive-search-routine-with-postgresql-11p0-temp-slug-2252311"&gt;written about Full Text Indexing in PostgreSQL&lt;/a&gt; before but I was a bit more focused on speed and general use. Today I want to focus on something a lot more useful: _ &lt;strong&gt;relevance&lt;/strong&gt; _.&lt;/p&gt;

&lt;p&gt;If you want to play along and have some fun, the SQL for what I’m about to do can be &lt;a href="https://rob.conery.io/wp-content/uploads/2019/10/ndc_syndney.zip" rel="noopener noreferrer"&gt;downloaded from here&lt;/a&gt; (11K zipped SQL file).&lt;/p&gt;

&lt;h2&gt;
  
  
  Make Those Results Meaningful!
&lt;/h2&gt;

&lt;p&gt;The data I’m working with is from NDC Sydney and is a list of speakers, their talks, keywords and the time/day of the talk. Simple stuff, but it does present an interesting question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How would you implement a full text index for this body of data?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Turning the speaker’s name, the title and the keywords into a blob of text and then indexing it &lt;em&gt;will&lt;/em&gt; work, but it’s &lt;strong&gt;simply not enough&lt;/strong&gt; if we expect the results to actually mean something to our users. This is where things get complicated – which also means they get FUN so &lt;strong&gt;strap yourself in, let’s get TEXTUAL.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are You Looking For Anyway?
&lt;/h2&gt;

&lt;p&gt;There’s no way we can do this without fully understanding what our users want out of our search functionality, so let’s come up with some scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jane has been to 5 conferences already this year and just wants to know what’s new with DevOps and Azure. She takes out her phone and, while walking, enters the words as they come to her mind: “ &lt;strong&gt;devops azure&lt;/strong&gt; “&lt;/li&gt;
&lt;li&gt;This is Kunjan’s first conference and he doesn’t know where to start – all he knows is that &lt;a href="https://quorralayne.com" rel="noopener noreferrer"&gt;Heather Downing&lt;/a&gt; is speaking and he really wants to be sure he can see her talks so he searches exactly on that: “ &lt;strong&gt;Heather Downing”.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Nenne is excited about Blazor and knows the dev team is here, showing it off. She can’t remember their names – just the project name – so she searches on that: “ &lt;strong&gt;blazor&lt;/strong&gt; “.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Problems
&lt;/h2&gt;

&lt;p&gt;We have three difference kinds of searches here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first is &lt;em&gt;contextual&lt;/em&gt;, which means that Jane knows the &lt;em&gt;topics&lt;/em&gt; she’s interested in and wants to throw a list of words at our search, hoping for a ranked match.&lt;/li&gt;
&lt;li&gt;The second is &lt;em&gt;specific&lt;/em&gt;, Kunjan wants to see a specific speaker’s talk – that means we need to be sure that we can return a hit on exact part of a first or last name.&lt;/li&gt;
&lt;li&gt;Finally, Nenne’s query is &lt;em&gt;relative&lt;/em&gt;, which means she knows a term (the project name) and wants to see results relative to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we’re to show these people something meaningful we’ll need to come up with a strategy for building our full text index. Thankfully, Postgres has the tools we need.&lt;/p&gt;

&lt;p&gt;Let’s take a quick second to (quickly) understand what goes on behind the scenes as our full text index is being created – it’s really helpful when trying to debug things. Then we’ll move on and create solutions for each of these problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Behind the Scenes
&lt;/h2&gt;

&lt;p&gt;A full text index is actually a data type in Postgres called &lt;code&gt;tsvector&lt;/code&gt;. It’s a weird name, but what it does is pretty simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select to_tsvector('english', 'nothing too tricky here');
     to_tsvector     
---------------------
 'noth':1 'tricki':3
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’m using Postgres’s built-in &lt;code&gt;to_tsvector&lt;/code&gt; function to &lt;em&gt;tokenize&lt;/em&gt; my string of words into &lt;em&gt;lexemes&lt;/em&gt;. What’s a “lexeme” you ask? Hey, &lt;a href="https://en.wikipedia.org/wiki/Lexeme" rel="noopener noreferrer"&gt;good question&lt;/a&gt;!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A lexeme is a unit of lexical meaning that underlies a set of words that are related through inflection. It is a basic abstract unit of meaning, a unit of morphological analysis in linguistics that roughly corresponds to a set of forms taken by a single root word.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;Wikipedia&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can apply various &lt;em&gt;stems&lt;/em&gt; to a lexeme to create a set of different words. So &lt;code&gt;noth&lt;/code&gt; in this case could be stemmed to “nothing” or “nothingness”. The integers that you see in the results above are the position within the text body. The first word is “nothing” so we have a 1 and tricky is the third word. This comes in handy later on when we want to know positional information (which we will!).&lt;/p&gt;

&lt;p&gt;Finally, you’ll notice that &lt;code&gt;too&lt;/code&gt; and &lt;code&gt;here&lt;/code&gt; have been stripped. These are “stop words” (or noise words) and aren’t indexed.&lt;/p&gt;

&lt;p&gt;But how does all of this tokenization happen?&lt;/p&gt;

&lt;p&gt;Postgres ships with a number of dictionaries that parse a given blob of text. If you want to raise the hood on this, you can run the &lt;code&gt;ts_parse&lt;/code&gt; function to see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select * from ts_parse('default', 'nothing too tricky here');

 tokid | token  
-------+---------
     1 | nothing
    12 |  
     1 | too
    12 |  
     1 | tricky
    12 |  
     1 | here
(7 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first argument to this function is the search configuration, which I’m setting to &lt;code&gt;default&lt;/code&gt; as I don’t want to break anything. What I get back is a list of tokens and their id. 1, for instance, is an ascii word and 12 is blank space.&lt;/p&gt;

&lt;p&gt;You can see a lot more information if you use the &lt;code&gt;ts_debug&lt;/code&gt; function, which is designed to help you if you’re fiddling with the search config stuff:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi0.wp.com%2Frob.conery.io%2Fwp-content%2Fuploads%2F2019%2F10%2Fscreenshot_206.jpg%3Ffit%3D640%252C266%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fi0.wp.com%2Frob.conery.io%2Fwp-content%2Fuploads%2F2019%2F10%2Fscreenshot_206.jpg%3Ffit%3D640%252C266%26ssl%3D1" alt="Fine Tuning Full Text Search with PostgreSQL 12"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think this is interesting, but &lt;strong&gt;it’s also academic&lt;/strong&gt; for our needs. Let’s get back on track and setup our search index.&lt;/p&gt;

&lt;h2&gt;
  
  
  Task 1: No Stems for Names!
&lt;/h2&gt;

&lt;p&gt;Before we index anything, we need to consider &lt;em&gt;what&lt;/em&gt; the thing is and also &lt;em&gt;what it is not&lt;/em&gt;. A little vague, but let’s start with names.&lt;/p&gt;

&lt;p&gt;Names are specific. While one could make the argument that some names might be more common in a given language, I think we can agree that’s problematic. In that sense, tokenizing a name as if its English words doesn’t make sense.&lt;/p&gt;

&lt;p&gt;Heather’s last name is “Downing”, which could refer to what she might do to a glass of cold water after a long run or what she did to enemy planes during the war. Neither of those is the case, yet that’s exactly how the tokenizer will treat her name.&lt;/p&gt;

&lt;p&gt;That’s how full text queries work in Postgres: &lt;em&gt;matching lexemes&lt;/em&gt;. The &lt;code&gt;to_tsquery&lt;/code&gt; function you see here simply tokenized the term given to it, applying the rules of the dictionary you specify, which is &lt;code&gt;english&lt;/code&gt; in my case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select to_tsquery('english', 'downing');
 to_tsquery 
------------
 'down'
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can fix this problem by _ &lt;strong&gt;using a different dictionary&lt;/strong&gt; &lt;em&gt;. This makes perfect sense since we’re don’t consider names part of a language! For this, Postgres gives us the _simple dictionary&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select to_tsquery('simple', 'downing');
 to_tsquery 
------------
 'downing'
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The simple dictionary doesn’t create a lexeme from the token given to it – it just returns the raw word (unless it’s a noise word) lower-cased. This will work perfect for indexing our names:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select to_tsvector('simple', body -&amp;gt;&amp;gt; 'name') from ndc;

    to_tsvector                                   
-----------------------
 'alex':1 'mackey':2
 'adam':1 'furmanek':2
 'kristy':1 'sachse':2
 'downing':2 'heather':1
 'passos':2 'thiago':1
 'arif':1 'wider':2
 'preece':2 'ryan':1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Perfect. We’ll use this when building our overall index in just a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying Weights to Keywords
&lt;/h2&gt;

&lt;p&gt;Proper tagging is difficult to do. I’m not going to spend time on how to do that – let’s just assume that you and your app have a cool set of tags you’re happy with. Now comes the big question: &lt;strong&gt;are those tags words&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;On one hand, it seems like the answer should be &lt;strong&gt;yes&lt;/strong&gt;. Tags are contextual and tend to be things like “database”, “career”, “azure” etc. But what about the tags “virtual-machines” or “virtual-network”?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select to_tsvector('virtual-network');
                 to_tsvector                 
---------------------------------------------
 'network':3 'virtual':2 'virtual-network':1

select to_tsvector('virtual-machines');
                to_tsvector                
-------------------------------------------
 'machin':3 'virtual':2 'virtual-machin':1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both of these tags will match on the term “virtual”, &lt;em&gt;no matter what it’s followed by&lt;/em&gt;. That means we’ll get a hit on “virtual-conference”, “virtual-meeting”, and “virtually everything” since the word “virtually” will turn into the lexeme “virtual”. That might be OK, it really depends on your tagging strategy. For me, I’ll be using the simple dictionary once again because tags are specific, simple terms for this conference.&lt;/p&gt;

&lt;p&gt;OK – now let’s address the weighting. We can apply weights to our tags by using the &lt;code&gt;setweight&lt;/code&gt; function in Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
setweight(
  to_tsvector('simple', (body -&amp;gt;&amp;gt; 'tags')), 
'A')
from ndc;
                   setweight                                                    
-------------------------------------------
 'cloud':1A 'fun':2A
 'microsoft':2A 'net':1A
 'agile':1A 'design':2A 'devops':8A 'methodology':9A 'people':3A 'skills':6A 'soft':5A 'soft-skills':4A 'ux':7A
 'agile':1A 'ethics':6A 'people':2A 'skills':5A 'soft':4A 'soft-skills':3A
 'cloud':2A 'database':3A 'microsoft':4A 'net':1A
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Weighting is simply a matter of applying a letter suffix to the positional integer. As you can see, &lt;code&gt;cloud:1A&lt;/code&gt; has replaced &lt;code&gt;cloud:1&lt;/code&gt;. That will be used when we run our query later on.&lt;/p&gt;

&lt;p&gt;Oh yeah – something neat to note here is that &lt;strong&gt;Postgres is smart enough to take a JSONB array value and turn it into a text array&lt;/strong&gt; for us, on the fly, and then apply indexing :).&lt;/p&gt;

&lt;h2&gt;
  
  
  Weighting Considerations
&lt;/h2&gt;

&lt;p&gt;At this point we need to figure out relative weighting for the information we’ll be searching. If you have only text blob your indexing, then it doesn’t make sense to apply weighting – but that’s rarely the case in an online app.&lt;/p&gt;

&lt;p&gt;The thing you need to consider when weighting is what “hits” are valued more than others? Weighting doesn’t affect which records will be recognized, it simply lifts those records to the top depending on how you weighted them (A through G).&lt;/p&gt;

&lt;p&gt;I’m going to make the choice that if someone enters a tag, that should be raised to the top. Next would be someone’s name (though you could argue it should be the other way around) and finally whatever was found in the title:&lt;/p&gt;

&lt;p&gt;Given this, we can build our entire search index with something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
setweight(to_tsvector('english', (body -&amp;gt;&amp;gt; 'tags')), 'A') || ' ' ||
setweight(to_tsvector('simple', (body -&amp;gt;&amp;gt; 'name')), 'B') || ' ' || 
setweight(to_tsvector('english', (body -&amp;gt;&amp;gt; 'title')), 'C')::tsvector
as search_index
from ndc;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;_ &lt;strong&gt;Note&lt;/strong&gt; : you’ll notice that I’m using the &lt;code&gt;||&lt;/code&gt; operator to concatenate the values together, including a space between them. If you don’t do this you’ll get words jammed together and crappy results._&lt;/p&gt;

&lt;p&gt;We’ve applied the top weight, A, to&lt;code&gt;tags&lt;/code&gt; and B to &lt;code&gt;name&lt;/code&gt; with &lt;code&gt;title&lt;/code&gt; coming in last with C. This is just relative ranking, which means that terms found in the keywords are ranked higher than the title, for instance. That will help Jane find her DevOps at Azure talks.&lt;/p&gt;

&lt;p&gt;Kunjan will find Heather’s talk as we’re not stemming – so he won’t get confused with bad results. And finally Nenne will easily find her “Blazor” talk as the name appears in the title.&lt;/p&gt;

&lt;p&gt;The only tricky part to this is if a speaker’s name appears in the title of a talk – so “Juana Blazor” might throw off the result – but there’s simply no way we can know which our user might want. We &lt;em&gt;can&lt;/em&gt;, however, make the decision that hits in the names should be counted higher! Which is what we did.&lt;/p&gt;

&lt;p&gt;Let’s add a generated column to our &lt;code&gt;ndc&lt;/code&gt; table and test it out!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alter table ndc
add search tsvector
generated always as (
   (
   setweight(to_tsvector('english', (body -&amp;gt;&amp;gt; 'tags')), 'A') || ' ' ||
   setweight(to_tsvector('simple', (body -&amp;gt;&amp;gt; 'name')), 'B') || ' ' || 
   setweight(to_tsvector('english', (body -&amp;gt;&amp;gt; 'title')), 'C'))::tsvector
) stored;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a new feature in Postgres 12 – generated columns. They’re virtual columns that are (for now) stored on disk and completely managed by Postgres. Whenever our record is updated our search index will be too!&lt;/p&gt;

&lt;p&gt;We’re now ready to start querying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Constructing a Proper Query
&lt;/h2&gt;

&lt;p&gt;Let’s start with the 3rd example first: “blazor”, which in Nenne’s query. This isn’t a keyword match because it’s not part of our tags, but it &lt;em&gt;is&lt;/em&gt; a project title which will, hopefully, appear in a title somewhere. In that case, we can run the following query just fine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
body -&amp;gt;&amp;gt; 'title' as title, 
body -&amp;gt;&amp;gt; 'name' as name
from ndc
where search @@ 
to_tsquery('english', 'blazor');

-[RECORD 1]--------------------------------
title | Blazor, a new framework for browser-based .NET apps
name | Steve Sanderson
-[RECORD 2]--------------------------------
title | Blazor in more depth
name | Steve Sanderson Ryan Nowak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Groovy! We’re using our &lt;code&gt;tsvector&lt;/code&gt; field, &lt;code&gt;search&lt;/code&gt;, and running a comparison with &lt;code&gt;@@&lt;/code&gt; to the &lt;code&gt;to_tsquery&lt;/code&gt; function. We get back some results and we can see that we have “Blazor” in the title. Great!&lt;/p&gt;

&lt;p&gt;At that point Nenne remembers that Steve Sanderson is one of her favorite speakers, so she decides to search both “blazor” and “Sanderson”:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: syntax error in tsquery: "blazor sanderson"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Oh no! What happened&lt;/strong&gt;? The short answer is that &lt;code&gt;to_tsquery&lt;/code&gt; expects a single word as an argument, which seems really weird at first! I mean… &lt;strong&gt;this is a full text search dude! WTF?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The problem is that &lt;strong&gt;Postgres doesn’t know what you want to do with more than one word&lt;/strong&gt;. Is it just a collection of words? Or is it a &lt;em&gt;phrase&lt;/em&gt; which has some structure to it. The query “blazor Sanderson” doesn’t mean anything to you or me, but Jane’s query “Azure DevOps” could be considered a phrase, where the term “Azure” needs to come before “DevOps”.&lt;/p&gt;

&lt;p&gt;For that, we can modify our query using &lt;code&gt;plainto_tsquery&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
body -&amp;gt;&amp;gt; 'title' as title, 
body -&amp;gt;&amp;gt; 'name' as name
from ndc
where search @@ 
plainto_tsquery('english', 'blazor sanderson');

-[RECORD 1]------------------------------------
title | Blazor, a new framework for browser-based .NET apps
name | Steve Sanderson
-[RECORD 2]------------------------------------
title | Blazor in more depth
name | Steve Sanderson Ryan Nowak
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes! boom! That works really well. The function &lt;code&gt;plainto_tsquery&lt;/code&gt; takes a plain text blob and treats it just like a bunch of words. In fact you can see exactly what it does by asking Postgres:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; select plainto_tsquery('blazor sanderson');

    plainto_tsquery     
------------------------
 'blazor' &amp;amp; 'sanderson'
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The text gets parsed into individual words, tokenized and turned into lexemes &lt;em&gt;and then&lt;/em&gt; placed into a logical AND condition. In other words: both “blazor” and “sanderson” must be in the search index.&lt;/p&gt;

&lt;p&gt;But what about Jane’s query? She wants to know what’s knew with Azure DevOps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
body -&amp;gt;&amp;gt; 'title' as title, 
body -&amp;gt;&amp;gt; 'name' as name
from ndc
where search @@ 
plainto_tsquery('english', 'azure devops');

-[RECORD 1]-----------------------
title | Static Sites, Dynamic microservices, &amp;amp; Azure: How we built Microsoft Docs and Learn
name | Dan Fernandez
-[RECORD 2]-----------------------
title | DataDevOps for the Modern Data Warehouse on Microsoft Azure
name | Lace Lofranco
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmmm. Well that sort of worked in that we have two talks about Azure that also have the term “devops” in the title… however there’s nothing there about the Azure DevOps product. One way that we can fix this is to send in a &lt;em&gt;phrase&lt;/em&gt; rather than a blob of words using &lt;code&gt;phraseto_tsquery&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select body -&amp;gt;&amp;gt; 'title' as title, body -&amp;gt;&amp;gt; 'name' as name
from ndc
where search @@ phraseto_tsquery('english', 'azure devops');

(0 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a bit more accurate: &lt;em&gt;there aren’t any talks specifically about Azure DevOps&lt;/em&gt;. The &lt;code&gt;phraseto_tsquery&lt;/code&gt; function leverages the positional argument that’s stored with &lt;code&gt;tsvector&lt;/code&gt;, making sure that one word will appear before another. You can see this if you ask Postgres what’s going on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select phraseto_tsquery('azure devops');

  phraseto_tsquery  
--------------------
 'azur' &amp;lt;-&amp;gt; 'devop'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The words are tokenized into lexemes once again, but this time there’s the positional &lt;code&gt;&amp;lt;-&amp;gt;&lt;/code&gt; operator, indicating that “azure” must appear before “devops” in the string (the inclusive AND is implied).&lt;/p&gt;

&lt;p&gt;OK, let’s make sure that Kunjan can find Heather’s talk and then we’ll be done! I’ll use the regular &lt;code&gt;plainto_tsquery&lt;/code&gt; here since I want to be sure we match properly on name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
body -&amp;gt;&amp;gt; 'title' as title, 
body -&amp;gt;&amp;gt; 'name' as name
from ndc
where search @@ 
plainto_tsquery('Downing');

(0 rows)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Good grief – no results&lt;/strong&gt;!?!?! What the heck?&lt;/p&gt;

&lt;h2&gt;
  
  
  Using the Right Dictionary
&lt;/h2&gt;

&lt;p&gt;The problem we’re having is matching dictionaries. When we use &lt;code&gt;to_tsquery&lt;/code&gt; or, in this case, &lt;code&gt;plainto_tsquery,&lt;/code&gt; the words we pass in will be tokenized according to some kind of dictionary. The default has to do with the location of the server and the default configuration – but it’s typically set to the language of the region of the server.&lt;/p&gt;

&lt;p&gt;In the case of our &lt;code&gt;name&lt;/code&gt; tokens, however, we used the simple dictionary which means that lexemes didn’t get generated and therefore will cause a match problem.&lt;/p&gt;

&lt;p&gt;To see what I mean, take a look at our &lt;code&gt;plainto_tsquery&lt;/code&gt; for “Downing” using the default dictionary (which is “english” in my case):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select plainto_tsquery('Downing');

 plainto_tsquery 
-----------------
 'down'
(1 row)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;We’re trying to match a literal term to a lexeme&lt;/strong&gt; , so of course we’re going to have problems. We can get over this by using the simple dictionary with &lt;code&gt;plainto_tsquery&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
body -&amp;gt;&amp;gt; 'title' as title, 
body -&amp;gt;&amp;gt; 'name' as name from ndc where search @@ 
plainto_tsquery('simple','Downing');

-[RECORD 1]------------------------------
title | Keynote: The Care and Feeding of Software Engineers
name | Heather Downing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much better! But this raises another question…&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Query With Two Dictionaries?
&lt;/h2&gt;

&lt;p&gt;I want to be able to query with both the English and simple dictionaries – but how can I do that and still get reasonable results?&lt;/p&gt;

&lt;p&gt;The simplest way to do this with an &lt;code&gt;OR&lt;/code&gt; query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
body -&amp;gt;&amp;gt; 'name' as name, 
body -&amp;gt;&amp;gt; 'title' as title, 
body -&amp;gt;&amp;gt; 'tags' as tags
from ndc where 
search @@ plainto_tsquery('english', 'heather keynote') OR
search @@ plainto_tsquery('simple', 'heather keynote');

-[RECORD 1]-------------------------------
name | Heather Downing
title | Keynote: The Care and Feeding of Software Engineers
tags | ["agile", "people", "soft-skills", "ethics"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s a bit on the verbose side, but as you can see we were able to find Heather’s keynote just fine. Note also that I’m using &lt;code&gt;plainto_tsquery&lt;/code&gt; here because I’m expecting a word salad, I can change that, however, in the case of names.&lt;/p&gt;

&lt;p&gt;We’re almost done! Now let’s sort our results in a meaningful way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ranking The Result Using Our Weighting
&lt;/h2&gt;

&lt;p&gt;Weighting doesn’t do much good unless we can apply it, so for that we’ll need to make sure there’s some form of “score” we can use when querying. For that, we have Yet Another Postgres Function: &lt;code&gt;ts_rank&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are actually &lt;em&gt;two&lt;/em&gt; of these functions. The first is &lt;code&gt;ts_rank&lt;/code&gt; which is a score based on word frequency and the second is &lt;code&gt;ts_rank_cd&lt;/code&gt;, which is based on frequency but also &lt;em&gt;coverage distance&lt;/em&gt; – which is basically how far words are apart in a query. For us, &lt;code&gt;ts_rank&lt;/code&gt; will do fine.&lt;/p&gt;

&lt;p&gt;To use these functions you have to pass in the &lt;code&gt;tsvector&lt;/code&gt; value as well as the &lt;code&gt;tsquery&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select 
ts_rank(search,q_eng) + ts_rank(search,q_simple) as rank,
body -&amp;gt;&amp;gt; 'name' as name, 
body -&amp;gt;&amp;gt; 'title' as title,
body -&amp;gt;&amp;gt; 'tags' as tags
from ndc, 
plainto_tsquery('english', 'devops') q_eng,
plainto_tsquery('simple', 'devops') q_simple
where 
search @@ q_eng OR
search @@ q_simple
order by rank desc
limit 5;

-[RECORD 1]---------------------------------
rank | 0.9074664
name | Ashley Noble
title | DevOps Trials and Tribulations of a DevOps transformation in a large Company
tags | ["devops"]
-[RECORD 2]---------------------------------
rank | 0.6383234
name | Damian Brady
title | Pragmatic DevOps - How and Why
tags | ["devops"]
-[RECORD 3]---------------------------------
rank | 0.6079271
name | Enrico Campidoglio
title | Understanding Git — Behind the Command Line
tags | ["t", "devops"]
-[RECORD 4]---------------------------------
rank | 0.6079271
name | Pooja BhaumikNick Randolph
title | Using Flutter to develop cloud enabled mobile applications
tags | ["cross-pl", "mobile", "devops"]
-[RECORD 5]---------------------------------
rank | 0.6079271
name | Klee Thomas
title | Picking up the pieces - A look at how to run post incident reviews
tags | ["agile", "devops"]

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

&lt;/div&gt;



&lt;p&gt;A few things to note about this code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I’m adding the &lt;code&gt;ts_rank&lt;/code&gt; results together because each &lt;code&gt;tsquery&lt;/code&gt; is going to have its own score. I’ll get into this in a bit.&lt;/li&gt;
&lt;li&gt;I’ve aliased the &lt;code&gt;tsquery&lt;/code&gt; functions for readability because I need to use them in the &lt;code&gt;select&lt;/code&gt; statement as well as the &lt;code&gt;where&lt;/code&gt; clause.&lt;/li&gt;
&lt;li&gt;I limited the results, because there are a lot.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;OR&lt;/code&gt; query works great and we’re able to query by names, tags and titles and we’re almost done – but as you can see the scoring is … weird.&lt;/p&gt;

&lt;p&gt;Postgres does some voodoo math behind the scenes and honestly it doesn’t really matter what those scores are all about – what does matter is that some are scored higher than others and we need to make sure our scoring scheme works as we want.&lt;/p&gt;

&lt;p&gt;Looking at the top 2 it’s easy to see it does: they have the term “devops” as tags as well as the title. This is a classic SEO rule for the web, and we should feel good about our search strategy, don’t you think? I guess it can be abused, however, if we pretend it’s 1998 and load our title and speaker’s name with keywords:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;select
ts_rank(search,q_eng) + ts_rank(search,q_simple) as rank,
body -&amp;gt;&amp;gt; 'name' as name, 
body -&amp;gt;&amp;gt; 'title' as title,
body -&amp;gt;&amp;gt; 'tags' as tags
from ndc, 
plainto_tsquery('english', 'devops') q_eng,
plainto_tsquery('simple', 'devops') q_simple
where 
search @@ q_eng OR
search @@ q_simple
order by rank desc
limit 5;

-[RECORD 1]-------------------------------------------------------------------------------------
rank | 0.9074664
name | Ashley DevOps Noble
title | DevOps Trials and DevOps Tribulations of a DevOps transformation in a large DevOps Company
tags | ["devops"]
-[RECORD 2]-------------------------------------------------------------------------------------
rank | 0.6383234
name | Damian Brady
title | Pragmatic DevOps - How and Why
tags | ["devops"]
-[RECORD 3]-------------------------------------------------------------------------------------
rank | 0.6079271
name | Enrico Campidoglio
title | Understanding Git — Behind the Command Line
tags | ["t", "devops"]
-[RECORD 4]-------------------------------------------------------------------------------------
rank | 0.6079271
name | Pooja BhaumikNick Randolph
title | Using Flutter to develop cloud enabled mobile applications
tags | ["cross-pl", "mobile", "devops"]
-[RECORD 5]-------------------------------------------------------------------------------------
rank | 0.6079271
name | Klee Thomas
title | Picking up the pieces - A look at how to run post incident reviews
tags | ["agile", "devops"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OK it’s not perfect, but it’s much better than indexing a blob of text because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We can recognize speaker names&lt;/li&gt;
&lt;li&gt;We’re weighting tag recognition over title&lt;/li&gt;
&lt;li&gt;We’re weighting tags and names over the loose text of a title&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I think for most web applications this will work really well!&lt;/p&gt;

&lt;h2&gt;
  
  
  Flexing Postgres 12
&lt;/h2&gt;

&lt;p&gt;Trying to decide between &lt;code&gt;to_tsquery&lt;/code&gt;, &lt;code&gt;plainto_tsquery&lt;/code&gt; and &lt;code&gt;phraseto_tsquery&lt;/code&gt; can be difficult. It was kind of straightforward in our case – we’re not searching on any phrases really.&lt;/p&gt;

&lt;p&gt;The Postgres team decided to be helpful in this regard, especially when it comes to web applications, so they created &lt;code&gt;websearch_to_tsquery&lt;/code&gt;. It basically treats the input as if it were entered into a Google search. To be dead honest I have no idea what’s happening under the covers here, but it’s supposed to be a bit more intelligent than &lt;code&gt;plainto_tsquery&lt;/code&gt; and a little less strict than &lt;code&gt;phraseto_tsquery&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I’ve played with it a few times and haven’t noticed much of a difference – it is worth noting however!&lt;/p&gt;

&lt;p&gt;Phew! Long post – hope it was helpful!&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>syndication</category>
    </item>
    <item>
      <title>PostgreSQL Tools for the Visually Inclined</title>
      <dc:creator>Rob Conery</dc:creator>
      <pubDate>Mon, 04 Mar 2019 23:10:28 +0000</pubDate>
      <link>https://dev.to/azure/postgresql-tools-for-the-visually-inclined-2b8b</link>
      <guid>https://dev.to/azure/postgresql-tools-for-the-visually-inclined-2b8b</guid>
      <description>&lt;p&gt;I started my career on the Microsoft stack building forms and websites using drag and drop tools. Over time that became a punchline, which is unfortunate because honestly, the productivity was insane.&lt;/p&gt;

&lt;p&gt;In 2008 I made the jump to the Linux world and I was completely disoriented. &lt;em&gt;Everything was a damn text file&lt;/em&gt;. Yes, you could use a Mac or Ubuntu or whatever Unix Desktop du Jour seemed fun but there simply was no getting around the need to know your commands, which I did.&lt;/p&gt;

&lt;p&gt;Just like learning SQL, learning your text commands makes you more efficient. &lt;em&gt;I promise you that I’m not about to flip the l33t bit&lt;/em&gt;. I’m not here to convince anyone of anything – what I do want to do is to share how I embraced the command line with respect to PostgreSQL and was damn happy for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Friendly vs. Friendly
&lt;/h2&gt;

&lt;p&gt;I’ve been meaning to write this post for years but it was &lt;a href="https://www.softwareandbooz.com/postgresql-for-a-sql-server-dba-the-tooling-stinks/"&gt;this post&lt;/a&gt; from Ryan Booz that made me fire up the editor. Ryan is a SQL Server DBA that is writing a series on how &lt;a href="https://www.softwareandbooz.com/postgresql-for-a-sql-server-dba-a-series/"&gt;he’s learning PostgreSQL&lt;/a&gt; after a 15 year (!) career as a SQL Server DBA. I can’t imagine that change is an easy one.&lt;/p&gt;

&lt;p&gt;Basically, Ryan has concerns (which I understand):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In the case of PostgreSQL, I’ve quickly come to the conclusion that bad tooling is one of the main reasons the uptake is so much more difficult and convoluted coming from the SQL Server community. Even the devs I’m currently working with that have no specific affinity for databases at all recognize that PostgreSQL just feels like more of a black box then the limited experience they had previously with SQL Server.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I can’t say he’s wrong on this, although I will say the term “bad” is a bit subjective.&lt;/p&gt;

&lt;p&gt;Let me get right to it: jumping from SQL Server to PostgreSQL is &lt;em&gt;much more&lt;/em&gt; than changing a tool. &lt;strong&gt;PostgreSQL was built on Unix&lt;/strong&gt; , with Unix in mind as the platform of choice, and typically runs best when it’s sitting on some type of Unix box. &lt;strong&gt;The Unix world has a pretty specific idiom&lt;/strong&gt; for how to go about things and it  &lt;strong&gt;certainly isn’t visual&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;As someone who learned to code visually, I had to learn what each icon meant and the visual cues for what happens where. I came to understand property pains, the lines under the text of a button that described shortcuts, and the idiomatic layout of each form. Executing a command meant pressing a button.&lt;/p&gt;

&lt;p&gt;In the Unix world you write out that command. The check boxes and dialogs are replaced by option flags and arguments. You install the tools you need and then look for the binaries that help you do a thing, then you interrogate them for help, typically using a &lt;code&gt;--help&lt;/code&gt;command (or just plain &lt;code&gt;help&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The same is true for PostgreSQL. This is the thing that I think was stumping Ryan. He’s searching for visual tooling in a world that embraces a completely different idiom. It’s like going to Paris and disliking it (and France) because the barbecue is horrible.&lt;/p&gt;

&lt;p&gt;Let’s walk through some common PostgreSQL DBA “stuff” to show what I mean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your Best Friend: psql
&lt;/h2&gt;

&lt;p&gt;When you encounter a new Unix tool for the first time (and yes, I’m labeling PostgreSQL that) you figure out the binaries for that tool. PostgreSQL has a number of them that you’ll want to get to know, including &lt;code&gt;pg_dump&lt;/code&gt; and &lt;code&gt;pg_restore&lt;/code&gt;among others. The one we want right now is &lt;code&gt;psql&lt;/code&gt;, the interactive terminal for PostgreSQL that gets installed along with the server. Let’s open it and ask it what the hell is going on:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--qPMaYyqC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1430.png%3Ffit%3D1024%252C731%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--qPMaYyqC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1430.png%3Ffit%3D1024%252C731%26ssl%3D1" alt=""&gt;&lt;/a&gt;Hello psql&lt;/p&gt;

&lt;p&gt;I’m using Mac’s Terminal app but you can use any shell you like, including Powershell and the Windows command line. I would strongly urge you, however, to crack open a Linux VM or Docker to get the “flavor” of working with PostgreSQL. You can, indeed, find barbecue in Paris but it might help to explore the local cuisine.&lt;/p&gt;

&lt;p&gt;Reading through this list of options and commands will take some patience the first time – but it’s worth it! At the top of the list are the common options, like using &lt;code&gt;-c&lt;/code&gt;for running a command and &lt;code&gt;-d&lt;/code&gt;for the database to run the command in. There’s a key statement, however, at the very end of this help screen:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--RRC35B-O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1432.png%3Ffit%3D1024%252C731%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--RRC35B-O--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1432.png%3Ffit%3D1024%252C731%26ssl%3D1" alt=""&gt;&lt;/a&gt;psql: it’s interactive!&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;psql&lt;/code&gt; tool is interactive! This will help us – so let’s log in to a database and have a look around. But which database? We’ll create one by running this on the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;createdb redfour
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;createdb&lt;/code&gt;binary has one job, in typically Unix fashion: &lt;em&gt;create a database on the local server&lt;/em&gt;. It has a counterpart binary as well: &lt;code&gt;dropdb&lt;/code&gt;. How do I know this? It’s one of those things you get used to as you work with Unix systems – figure out the binaries and what they do.&lt;/p&gt;

&lt;p&gt;How do you do that? We know about one binary so far, &lt;code&gt;psql&lt;/code&gt;, so let’s figure out where that lives and hopefully the other binaries live there too:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4T1gcQjw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1433.png%3Ffit%3D1024%252C561%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4T1gcQjw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1433.png%3Ffit%3D1024%252C561%26ssl%3D1" alt=""&gt;&lt;/a&gt;Using which and ls to tell us more&lt;/p&gt;

&lt;p&gt;This is one of those things you learn over time: asking Unix &lt;code&gt;which&lt;/code&gt; version of a tool/binary/runtime it’s using and where it’s located. The result of that command is telling me that &lt;code&gt;psql&lt;/code&gt; lives in the &lt;code&gt;/Applications/.../bin&lt;/code&gt; directory, which is pretty standard for binary tools. I copy/paste the result to the &lt;code&gt;ls&lt;/code&gt; command (list contents) and we can see the binary tools at our disposal.&lt;/p&gt;

&lt;p&gt;Yay. Let’s log in and play around.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7oS7S-ZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1436.png%3Ffit%3D1024%252C360%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7oS7S-ZM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1436.png%3Ffit%3D1024%252C360%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Hell Is Happening?
&lt;/h2&gt;

&lt;p&gt;Right now I’m at an interactive terminal within my database… and have no idea what to do next. This is the major upside of visual tooling: you have cues that you can follow which inform you as to what’s happening. It’s the difference between Halo on the Xbox and an old school MUD – it feels outdated and silly.&lt;/p&gt;

&lt;p&gt;Let’s keep going and see if that’s true. When we ran the &lt;code&gt;--help&lt;/code&gt; command before, it told us to use &lt;code&gt;\?&lt;/code&gt; to figure out commands within psql, so let’s try that first:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CLJ2aYQk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1437.png%3Ffit%3D1024%252C543%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CLJ2aYQk--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1437.png%3Ffit%3D1024%252C543%26ssl%3D1" alt=""&gt;&lt;/a&gt;Hello sea of text crashing over me!&lt;/p&gt;

&lt;p&gt;There is &lt;em&gt;so much to absorb here&lt;/em&gt;. All of these cryptic little commands &lt;em&gt;do something&lt;/em&gt; but what they do, at first, will likely be opaque to you. This is Yet Another Patient Deep Breath point, because pretty soon we’re about to light this shit on fire (in a good way). What you have, right here, is a lot of raw &lt;em&gt;power&lt;/em&gt; right at your fingertips. It just takes a few hours to understand the cadence of these commands as well as their modifiers. I’ll show you exactly what I mean in just a few minutes, for now let’s ground ourselves.&lt;/p&gt;

&lt;p&gt;Scroll down (using down arrow or your mouse) to the Informational command section. This is your bread and butter – here you can see what’s in your database at a quick glance. We can do that by using &lt;code&gt;\d&lt;/code&gt; (press Q to get out of the text view of the help page):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gRVDn8ll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1438.png%3Ffit%3D1024%252C536%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gRVDn8ll--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1438.png%3Ffit%3D1024%252C536%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our database is empty. Let’s fix that by creating a quick table for our users:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5QB_nsuT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1439.png%3Ffit%3D1024%252C539%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5QB_nsuT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1439.png%3Ffit%3D1024%252C539%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you write a SQL command within psql you can wrap the lines. Notice that the prompt changes as well, telling you that you currently have an open paren. To finish the command add a semi-colon and we’re done. &lt;em&gt;Note: I’m not going to get into SQL but &lt;a href="http://www.craigkerstiens.com/2019/02/12/sql-most-valuable-skill/"&gt;it’s really worth your time&lt;/a&gt; to learn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let’s list out our relations again:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gQb6elCj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1440.png%3Ffit%3D1024%252C539%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gQb6elCj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1440.png%3Ffit%3D1024%252C539%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lovely. We have our table and the thing that handles the id generation for that table, called a &lt;em&gt;sequence&lt;/em&gt;. Let’s ask psql more about this table using &lt;code&gt;\d users&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zT2MiyGQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1441.png%3Ffit%3D1024%252C422%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zT2MiyGQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1441.png%3Ffit%3D1024%252C422%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The structure of our table is laid out in glorious ASCII, heavy with information and completely bereft of anything resembling prettiness. For visual people, this is a turn off as it’s completely different than what they’re used to (which I understand). For people used to working in a text-based idiom, this is heavenly.&lt;/p&gt;

&lt;p&gt;Why? &lt;em&gt;It’s the speed of the thing&lt;/em&gt;. Let’s put a clock to the problem. One of your appdevs just did something ill-advised with their ORM and they think they might have broken the &lt;code&gt;users&lt;/code&gt; table. You decide to investigate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;psql redfour
\dt users
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When you’re just starting out with PostgreSQL (and psql), you’ll need to squeeze your memory a bit for the commands to inspect a table. After a while your fingers will be done typing before your next breath.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the power you want as a DBA&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;At this point I could go off on all of the psql commands available to you, however I would encourage you to explore these for yourself and see what’s possible, and how blazingly fast you can get it done. My coworker (ha ha so fun to say that now) Craig Kerstiens has written extensively on PostgreSQL, and &lt;a href="http://www.craigkerstiens.com/2013/02/13/How-I-Work-With-Postgres/"&gt;this post is extremely helpful&lt;/a&gt; for people getting used to the command line aspect of it.&lt;/p&gt;

&lt;p&gt;I want to get into why this kind of thing matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text is a Helluva Drug
&lt;/h2&gt;

&lt;p&gt;If I asked you to move data from one server to another using your favorite visual tool, how would you do it? If you do it often then the process would be a simple one and likely involve some right-clicking, traversing a menu, and kicking off a process in your tool of choice.&lt;/p&gt;

&lt;p&gt;In Unix land (and therefore Postgres land) it’s a matter of remembering a few commands. But this is where it gets interesting because &lt;em&gt;everything in Unix is a text file.&lt;/em&gt; Almost every task you can think of in Unix can be done using a text-based command. It would be like trying to find barbecue in Paris when every building is made of meat and the Seine is a river of hot coals.&lt;/p&gt;

&lt;p&gt;To show you what I mean, here’s how you might pull your production database down to your local server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pg_dump postgres://user:password@server/db &amp;gt; redfour.sql
createdb redfour
psql redfour &amp;lt; redfour.sql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;pg_dump&lt;/code&gt; binary has the singular task of turning a database into a SQL file. You can, of course, tweak and configure how this works and to find out all of the options you would use… can you guess? &lt;code&gt;pg_dump --help&lt;/code&gt; . So we’re dumping the structure and data to a SQL file, creating our database and then pushing that SQL file as a command into our new database.&lt;/p&gt;

&lt;p&gt;This entire process will execute in &amp;lt; 5 seconds on a smaller sized database (~20mb). This is why we like text and text-based interfaces – SPEED!&lt;/p&gt;

&lt;h2&gt;
  
  
  There’s Always a Way
&lt;/h2&gt;

&lt;p&gt;As you might be able to tell, I’ve had this conversation more than a few times. Visuals are very important, to be sure! But they have their place when it comes to your daily workflow as a DBA. I would argue that double-clicking, right-clicking, and drag/drop are much slower than taking the time to memorize some common commands.&lt;/p&gt;

&lt;p&gt;One place that psql sucks, however, are visuals. Executing a query on a large table can look horrible:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ctDl2W2---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1443.png%3Ffit%3D1024%252C667%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ctDl2W2---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1443.png%3Ffit%3D1024%252C667%26ssl%3D1" alt=""&gt;&lt;/a&gt;Yuckity Yuck&lt;/p&gt;

&lt;p&gt;This is DVD Rental sample database, running a &lt;code&gt;select * from film;&lt;/code&gt; query. It looks like crap! The good news is that we &lt;em&gt;should&lt;/em&gt; be able to fix this. Let’s ask psql what’s going on using &lt;code&gt;\?&lt;/code&gt; :&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gSgrjhdR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1442.png%3Ffit%3D1024%252C575%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gSgrjhdR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1442.png%3Ffit%3D1024%252C575%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are two things to notice here. The first is &lt;code&gt;\x&lt;/code&gt; which allows for expanded output, or vertical alignment of data. That looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7JXyYcr3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1444.png%3Ffit%3D1024%252C667%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7JXyYcr3--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1444.png%3Ffit%3D1024%252C667%26ssl%3D1" alt=""&gt;&lt;/a&gt;Using expanded output&lt;/p&gt;

&lt;p&gt;The other thing you can do is to set HTML as the output using &lt;code&gt;\H&lt;/code&gt;. This will execute the query, returning the results in HTML:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YCXw6Ar1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1445.png%3Ffit%3D1024%252C667%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YCXw6Ar1--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1445.png%3Ffit%3D1024%252C667%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is interesting but I want this saved to a file. To do that, I can use &lt;code&gt;\o&lt;/code&gt; (which you can see in the help menu) and specify which file:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--M5fa22YR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1448-1.png%3Ffit%3D1024%252C525%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--M5fa22YR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1448-1.png%3Ffit%3D1024%252C525%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The file produced isn’t terribly exciting, but it’s somewhat useful:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xCcX8I4r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1449.png%3Ffit%3D1024%252C648%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xCcX8I4r--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1449.png%3Ffit%3D1024%252C648%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is where we can embrace the texty nature of Unix and see what’s possible if we start jamming binaries together with some core Unix commands, which are all based on text.&lt;/p&gt;

&lt;p&gt;Let’s use psql to execute a query, but this time we’ll format things using Bootstrap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "&amp;lt;link rel='stylesheet' href='https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'&amp;gt;" &amp;gt; report.html &amp;amp;&amp;amp; psql chinook -c "select * from film" -H &amp;gt;&amp;gt; report.html &amp;amp;&amp;amp; open report.html 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F6wF_CxE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1450.png%3Ffit%3D1024%252C608%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F6wF_CxE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/03/screenshot_1450.png%3Ffit%3D1024%252C608%26ssl%3D1" alt=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;OK it’s certainly not crazy amazing but for a quick report it’s not so bad. You can alter the SQL statement to output only the columns you want, and you could formalize the call using a bash function to make it all pretty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Yeah But It’s Not Management Studio!
&lt;/h2&gt;

&lt;p&gt;Very true. You can’t double-click a table and edit the rows, for instance, and there are no spiff icons. Altering data is done with INSERT and UPDATE commands, deleting is done with DELETE. This is something that you do have to get used to, for sure, and if this is a common task for you than you might want to focus on a tool that allows that (such as &lt;a rel="noreferrer noopener" href="https://eggerapps.at/postico/"&gt;Postico&lt;/a&gt;, which is free).&lt;/p&gt;

&lt;p&gt;If there’s one reason to use psql it’s &lt;em&gt;speed&lt;/em&gt;. I would also argue &lt;em&gt;power&lt;/em&gt; but for now I’ll go with speed as the primary reason. You’re done before you know what happened and, if you have repetitive tasks, you can save your commands as text files to run as you need, when you need.&lt;/p&gt;

&lt;p&gt;Change isn’t easy, but the people I know that have made the change use psql on a daily basis and absolutely love it. They also flip into a visual tool as needed. One thing they all agree on, however, is that they don’t miss the visual stuff at all.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>syndication</category>
      <category>database</category>
    </item>
    <item>
      <title>Creating a Massively Scalable WordPress Site on Azure’s Hosted Bits</title>
      <dc:creator>Rob Conery</dc:creator>
      <pubDate>Wed, 09 Jan 2019 22:11:04 +0000</pubDate>
      <link>https://dev.to/robconery/creating-a-massively-scalable-wordpress-site-on-azures-hosted-bits-3o21</link>
      <guid>https://dev.to/robconery/creating-a-massively-scalable-wordpress-site-on-azures-hosted-bits-3o21</guid>
      <description>&lt;p&gt;I had &lt;em&gt;yet another&lt;/em&gt; discussion about WordPress the other day with a friend here at my co-work space (Impact Hub in Honolulu). I told them I was working on a deployment script for spinning up WordPress and Ghost on Azure’s infrastructure and their response was what I’m used to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WordPress? Dude… is this new gig warping your mind?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It might be. My response was a variation of the same one I always give:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;WordPress &lt;a href="https://venturebeat.com/2018/03/05/wordpress-now-powers-30-of-websites/"&gt;powers 30% of the entire web&lt;/a&gt;. I think supporting it well on Azure is a no-brainer.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I use WordPress. I like it and yes there are things that, as a developer, I wish could be different. The simplicity of the thing, however, is what really attracts me to it. So, if you’re a person who uses WordPress and you also use Azure, this post might prove useful!&lt;/p&gt;

&lt;p&gt;_ &lt;strong&gt;Disclosure&lt;/strong&gt; : I work at Microsoft in Developer Relations. My job is to figure things like this out and then 1) tell the product teams what could work better and 2) report to you how I managed to get it all to work. If this is helpful, hooray!_&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: Get Comfortable with the CLI
&lt;/h2&gt;

&lt;p&gt;There are other ways to do this, but I like “scripted recipes”. There are ARM (Azure Resource Management) templates as well that allow you to click a button and “deploy to Azure” which I think is great, but I’m a bit of a control freak and dig my shell scripts (which may not be the best so sound off if you see improvements).&lt;/p&gt;

&lt;p&gt;The other reason I like shell scripts is that you can add comments and use them as a bit of a learning tool. At least that’s what I’m hoping to do here – learning this Azure stuff for me hasn’t been easy and hopefully my notes help some of you.&lt;/p&gt;

&lt;p&gt;If you want to jump right to it, &lt;a href="https://gist.github.com/robconery/b3d1ab3146f777707a6d6fb2e851cc8d"&gt;here’s a gist&lt;/a&gt; you can read through. &lt;strong&gt;Yes, I know,&lt;/strong&gt; I could just use a Docker Compose file and be done. I wanted a better database solution and I also wanted the challenge to… back under the bridge with you!&lt;/p&gt;

&lt;p&gt;Finally – if you want to play along, make sure you have the &lt;a href="https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest"&gt;Azure CLI installed&lt;/a&gt; and that &lt;a href="https://docs.microsoft.com/en-us/cli/azure/authenticate-azure-cli?view=azure-cli-latest"&gt;you’re logged in&lt;/a&gt; with a default subscription set.&lt;/p&gt;

&lt;p&gt;The first thing we need to do is declare our variables, etc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;RG="[YOUR RESOURCE GROUP]"
APPNAME=$RG-wordpress #Name what you want
LOCATION="Central US" #put where you like

#Recommend to keep these random, but if you need to change go for it
USER=admin_$RANDOM #set this to whatever you like but it's not something that should be easy
PASS=$(uuidgen) #Again - whatever you like but keep it safe! Better to make it random
SERVERNAME=server$RANDOM #this has to be unique across azure

#accepted values for the service plan: B1, B2, B3, D1, F1, FREE, P1, P1V2, P2, P2V2, P3, P3V2, PC2, PC3, PC4, S1, S2, S3, SHARED
#B2 is good for getting started - read up on the different levels and their associated cost.
PLAN=B2

#Kick it off by creating the Resource Group
echo "Creating resource group"
az group create -n $RG -l $LOCATION
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;At the very top I’m setting the resource group as &lt;code&gt;RG&lt;/code&gt;because it’s used everywhere. I’m then setting the &lt;code&gt;APPNAME&lt;/code&gt;and &lt;code&gt;LOCATION&lt;/code&gt;, the latter with a default of “Central US”.&lt;/p&gt;

&lt;p&gt;When you create things in Azure, you use “resource groups” for logical groupings. This is extremely helpful when you want to wholesale remove everything when you’re playing around, which I am. It also keeps project-based stuff nice and tidy.&lt;/p&gt;

&lt;p&gt;The name of your website needs to be unique across Azure as well. I usually try to name mine with the resource group appended to it somehow. I’ll talk about the user name and password stuff later.&lt;/p&gt;

&lt;p&gt;The final step is to create the resource group. Now let’s create MySQL.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Spinning Up a Managed MySQL Instance
&lt;/h2&gt;

&lt;p&gt;This, to me, is the main benefit of using Azure. You get a super-scalable MySQL instance to back your fully-managed website. Most one-click installs you see online give you a single VM with MySQL installed right next to WordPress. This usually works fine (such as with this blog you’re reading) but if you’re planning on ramping up, it can be a bit scary.&lt;/p&gt;

&lt;p&gt;The first thing to decide is the size of our database. You can ramp this thing to the moon if you want, but I suggest keeping it small to start. I’ve listed out the SKU structure in the script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#The sku-name parameter value follows the convention {pricing tier}_{compute generation}_{vCores} as in the examples below:
# --sku-name B_Gen5_2 maps to Basic, Gen 5, and 2 vCores.
# --sku-name GP_Gen5_32 maps to General Purpose, Gen 5, and 32 vCores.
# --sku-name MO_Gen5_2 maps to Memory Optimized, Gen 5, and 2 vCores.

#WARNING - this might error out if your region doesn't support the SKU you set here. If it does, execute:
#az group delete -g [resource group] to drop everything and try again
#The SKU below is reasonable for a WP blog, but if you're going to host something more, consider more RAM/Cores
SKU=B_Gen5_1 #this is the cheapest one

echo "Spinning up MySQL $SERVERNAME in group $RG Admin is $USER"

# Create the MySQL service
az mysql server create --resource-group $RG \
    --name $SERVERNAME --admin-user $USER \
    --admin-password $PASS --sku-name $SKU \
    --ssl-enforcement Disabled \
    --location $LOCATION
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This code creates a smaller MySQL instance – in fact it’s the smallest one you can create. I ran into some issues with certain SKUs not being supported in certain regions, but if you get an error just drop the resource group and try again.&lt;/p&gt;

&lt;p&gt;We then spin up MySQL. The one big thing to notice here is &lt;code&gt;--ssl-enforcement Disabled&lt;/code&gt;which caused me some headaches. WordPress doesn’t use SSL out of the box, which you can change with &lt;a href="https://wordpress.org/plugins/secure-db-connection/"&gt;a plugin&lt;/a&gt; or config changes (surprise surprise). If you’re behind a firewall or part of a VPN, this might not be a huge deal. You can change this later on, however, when the plugin is installed.&lt;/p&gt;

&lt;p&gt;The final thing to point out is the &lt;code&gt;admin-user&lt;/code&gt; and &lt;code&gt;admin-password&lt;/code&gt;settings. I am &lt;em&gt;not a fan&lt;/em&gt; of leaving the root level stuff to the user, but right now that’s the way it is. To softly get around this, the script creates a random user name and generates a GUID for the password, which you can see in the first script above.&lt;/p&gt;

&lt;p&gt;We’re now ready to open a firewall and create our WordPress database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Firewall and WordPress Database
&lt;/h2&gt;

&lt;p&gt;Our server is up and running but is locked down completely, which I think is marvy. We need to be explicit about who/how/what can access our server, so we need to open a few holes in the firewall. The first will be for us, the second will be for our website later on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "Guessing your external IP address from ipinfo.io"
IP=$(curl -s ipinfo.io/ip)
echo "Your IP is $IP"

# Open up the firewall so we can access
echo "Popping a hole in firewall for IP address $IP (that's you)"
az mysql server firewall-rule create --resource-group $RG \
        --server $SERVERNAME --name AllowMyIP \
        --start-ip-address $IP --end-ip-address $IP

# Open up the firewall so wordpress can access - this is internal IP only
echo "Popping a hole in firewall for IP address $IP (that's you)"
az mysql server firewall-rule create --resource-group $RG \
        --server $SERVERNAME --name AllowAzureIP \
        --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0

echo "Creating WordPress Database"
#IMPORTANT: you have to have the client installed locally. If you don't, you'll get an error right about here.
mysql --host=$SERVERNAME.mysql.database.azure.com \
      --user=$USER@$SERVERNAME --password=$PASS \
      -e 'create database wordpress;' mysql
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The first hole is for my local IP address, which I’m grabbing using &lt;code&gt;curl&lt;/code&gt;. This is a subshell that pings ipinfo.io and reads the response, setting it to a variable. I use that response to create the first firewall rule: &lt;code&gt;AllowMyIP&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The next rule looks a bit scary. I’m creating a firewall rule that allows &lt;em&gt;any service within Azure to ping my server.&lt;/em&gt; This is what the Azure docs have to say about it (emphasis mine):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To allow applications from Azure to connect to your Azure SQL server, Azure connections must be enabled. When an application from Azure attempts to connect to your database server, the firewall verifies that Azure connections are allowed. A firewall setting with starting and ending address equal to 0.0.0.0 indicates these connections are allowed. If the connection attempt is not allowed, the request does not reach the Azure SQL Database server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;This option configures the firewall to allow all connections from Azure including connections from the subscriptions of other customers&lt;/strong&gt;. When selecting this option, make sure your login and user permissions limit access to only authorized users.&lt;/p&gt;

&lt;p&gt;&lt;cite&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/sql-database/sql-database-firewall-configure"&gt;Azure SQL Database and SQL Data Warehouse firewall rules&lt;/a&gt;&lt;br&gt;&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This might seem suboptimal when it comes to security because &lt;em&gt;it is&lt;/em&gt;. However, this is how every service works within &lt;em&gt;any&lt;/em&gt; data center. We can, if we want, further lock this thing down later on when we know the IP of our site (assuming it won’t change).&lt;/p&gt;

&lt;p&gt;The final step in the code above is to use the &lt;code&gt;mysql&lt;/code&gt;client to create the remote database. &lt;strong&gt;I couldn’t find a way around this&lt;/strong&gt;. I know that most people don’t have MySQL installed locally, so this could be a problem for you. I’m hoping to find a better solution someday.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optional Step 2a: Convenience Stuff
&lt;/h3&gt;

&lt;p&gt;I’m a control freak and I like to have access to my database. I also hate trying to remember login information so to help myself out I’ll setup a &lt;code&gt;.env&lt;/code&gt;file with the relevant bits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "Setting ENV variables locally"
MYSQL_SERVER=$SERVERNAME.mysql.database.azure.com
MYSQL_USER=$USER@$SERVERNAME
MYSQL_PASSWORD=$PASS
MYSQL_PORT=3306
MYSQL_DB=$DATABASE
echo "MYSQL_SERVER=$MYSQL_SERVER\nMYSQL_USER=$USER\nMYSQL_PASSWORD=$PASS\nMYSQL_PORT=3306" &amp;gt;&amp;gt; .env
echo "alias prod=\"mysql --host=$SERVERNAME.mysql.database.azure.com --user=$USER@$SERVERNAME --password=$PASS\" wordpress"
echo "MySQL ENV vars added to .env. You can printenv to see them, or cat .env."
echo "To access your MySQL Instance just run `prod` as an alias. You can rename this in .env."
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;If you don’t know, a &lt;code&gt;.env&lt;/code&gt;file typically contains environment variables and other things intended to help you with development. They might contain local database configuration, etc. Here, I’m outputting some environment variables and I’m also setting up an alias so I can connect to &lt;code&gt;prod&lt;/code&gt;as needed.&lt;/p&gt;

&lt;p&gt;Side note: if you use zshell and &lt;a href="https://github.com/robbyrussell/oh-my-zsh"&gt;oh-my-zsh&lt;/a&gt;! &lt;a href="https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins/dotenv"&gt;there’s a plugin&lt;/a&gt;called “dotenv” that will automatically load any &lt;code&gt;.env&lt;/code&gt; file in a directory when you navigate to it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You don’t want this checked into source control!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Creating The Web App
&lt;/h2&gt;

&lt;p&gt;We want our WordPress site to be fully managed, from the database all the way down to the website itself. This will allow us to flip a few bits in the future to add more cores to either the site or the database.&lt;/p&gt;

&lt;p&gt;This is &lt;em&gt;not a VM&lt;/em&gt;. Well, at least as far as you’re concerned. All of that is abstracted into Azure’s “service fabric”. That said, the first thing you need to do is to pick the size of your VM in the form of a plan :p:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#create the plan
#accepted values: B1, B2, B3, D1, F1, FREE, P1, P1V2, P2, P2V2, P3, P3V2, PC2, PC3, PC4, S1, S2, S3, SHARED
#B2 is good for getting started - read up on the different levels and their associated cost.
PLAN=B2

echo "Creating AppService Plan"
az appservice plan create --name $RG \
                          --resource-group $RG \
                          --sku $PLAN \
                          --is-linux
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You can think about this plan in terms of its &lt;em&gt;capacity&lt;/em&gt; and compute power. B1 is the lowest but won’t work if you’re using Linux. If your WordPress site is going to power some serious traffic, you might consider a plan with a bit more horsepower to it. It’s not the most straightforward thing, but you can &lt;a href="https://azure.microsoft.com/en-us/pricing/details/app-service/plans/"&gt;have a read about plans here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now we get to spin up our WordPress site. We could pull down the WordPress source (PHP) and then send it up to Azure, specifying deployment and runtime settings etc. Or we could simply specify the image on DockerHub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "Creating Web app"
az webapp create --resource-group $RG \
                  --plan $RG --name $APPNAME \
                  --deployment-container-image-name wordpress
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When you create a &lt;code&gt;webapp&lt;/code&gt;on Azure, it needs to know 1) how to run it and 2) how to deploy it. You can specify a Python runtime, for instance, and “local Git” as the deployment option. This would allow you to push from your local machine and have your Flask app “just run”.&lt;/p&gt;

&lt;p&gt;If you’re using Docker, however, the runtime is built into the image. All that Azure needs to do is to know where to get it. It’ll use your plan to create and run a container. In the code above, I’m telling Azure to use the &lt;code&gt;wordpress&lt;/code&gt; image, which Azure knows to &lt;a href="https://hub.docker.com/_/wordpress"&gt;pull down from DockerHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Having a look at the image description on DockerHub, you can see there are a number of environment variables that I can set for the image, namely the database connection bits. This is perfect for our needs, but how do you pass this through from Azure?&lt;/p&gt;

&lt;p&gt;The good news is that Azure will pass whatever app settings you have for your webapp directly to your container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "Adding app settings"
#add the settings for the new MYSQL bits
az webapp config appsettings set --name $APPNAME \
                                 --resource-group $RG \
                                 --settings WORDPRESS_DB_HOST=$MYSQL_SERVER \
                                 WORDPRESS_DB_USER=$MYSQL_USER \
                                 WORDPRESS_DB_PASSWORD=$MYSQL_PASSWORD \
                                 WEBSITES_PORT=80
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The environment variables are expected by the WordPress container, and I’m setting them to the MySQL database that I created above.&lt;/p&gt;

&lt;p&gt;The last setting, &lt;code&gt;WEBSITES_PORT&lt;/code&gt;, tells Azure which port to forward to in the container. I think the default is port 80, but I’m setting it here just for clarity.&lt;/p&gt;

&lt;p&gt;The final thing we need to do is a bit of a bugger for me: &lt;em&gt;we need to explicitly turn on logging.&lt;/em&gt; I wish this just happened by default, like with Heroku, but it doesn’t so we need to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#turn on logging
echo "Setting up logging"
#setup logging and monitoring
az webapp log config --application-logging true \
                    --detailed-error-messages true \
                    --web-server-logging filesystem \
                    --level information \
                    --name $APPNAME \
                    --resource-group $RG
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I’m outputting everything here because I want to know &lt;em&gt;exactly&lt;/em&gt; what happens when Azure tries to spin up this container. I’ll be honest: debugging the container stuff has been difficult.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3a: More Convenience Bits
&lt;/h3&gt;

&lt;p&gt;As I mention, I’m a huge fan of laziness and I like to add things to &lt;code&gt;.env&lt;/code&gt; files when I can. Let’s do that now so we can tail our application logs with a simple command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "Adding logs alias to .env. Invoking this will allow you to see the application logs realtime-ish."

#set an alias for convenience - add to .env
alias logs="az webapp log tail --name $APPNAME --resource-group $RG"
echo "alias logs='az webapp log tail -n $APPNAME -g $RG'" &amp;gt;&amp;gt; .env
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I’m setting the alias for the current shell and I’m also appending it to &lt;code&gt;.env&lt;/code&gt;so I can access it later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Run it!
&lt;/h2&gt;

&lt;p&gt;We’re ready to go! The only thing left to do is to navigate to our new site. The first request will take a while as Azure is pulling down our image and running it for the first time. You can see this if you tail the logs while the site is loading, which is the very last step of our script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "Opening site and viewing logs"
open https://$APPNAME.azurewebsites.net
source .env
logs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I’m opening our new URL, loading our new &lt;code&gt;.env&lt;/code&gt; file into the shell and then calling our new alias, &lt;code&gt;logs&lt;/code&gt;. This will allows us to see what’s happening at Azure as WordPress spins up. Hopefully, after a minute or so, you see something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--H827zWGV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/01/screenshot_1343.png%3Ffit%3D1024%252C763%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--H827zWGV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/01/screenshot_1343.png%3Ffit%3D1024%252C763%26ssl%3D1" alt=""&gt;&lt;/a&gt;Our new WordPress site&lt;/p&gt;

&lt;h2&gt;
  
  
  Seriously, Why Would You Do All of This?
&lt;/h2&gt;

&lt;p&gt;It’s a good question, one I’ve asked myself repeatedly over the last few days. The answer is simple: &lt;em&gt;this WordPress site is massively scalable&lt;/em&gt;. That’s kind of a big deal. It’s running right on the Azure “metal”, so to speak and I don’t need to worry about upscaling a VM.&lt;/p&gt;

&lt;p&gt;As I mention: I really like WordPress and I run my business on it. Having it all on Azure would be a nice piece of mind.&lt;/p&gt;

&lt;p&gt;As for costs, you’ll have to take a look at your subscription and decide if something like this is right for you. I have a regular subscription that I pay for and I can tell you that this setup is comparable with what I’ve seen for WordPress hosting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback, On Its Way
&lt;/h2&gt;

&lt;p&gt;Every time I do stuff like this I end up with a big writeup for the product teams to look over. It’s important to note that this isn’t a situation where I “just send an email” or file a bug. My team works closely with the product teams and they like (I think) that we’re beating on their stuff.&lt;/p&gt;

&lt;p&gt;I’ll be letting them know my thoughts on the MySQL stuff (admin user/pass, executing a remote command, etc) and a few more things that I need to get clarity on first. That’s a bit of a problem I have right now as I step into all of this: &lt;em&gt;learning it is not straightforward&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;I suppose that will be my biggest point of feedback. There’s so much to learn! It’s hard to get going and often I find myself doing things the “old way”. It’s also my job to help get this stuff in order, which I like.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>syndication</category>
      <category>mysql</category>
      <category>wordpress</category>
    </item>
    <item>
      <title>Imposter’s Handbook, Season 2 is Released</title>
      <dc:creator>Rob Conery</dc:creator>
      <pubDate>Thu, 03 Jan 2019 21:53:25 +0000</pubDate>
      <link>https://dev.to/robconery/imposters-handbook-season-2-is-released-4408</link>
      <guid>https://dev.to/robconery/imposters-handbook-season-2-is-released-4408</guid>
      <description>&lt;p&gt;Over the winter holiday break (on Christmas Eve, to be precise), &lt;a href="https://hanselman.com"&gt;Scott Hanselman&lt;/a&gt; and I released &lt;a href="http://bit.ly/2F2vS3Z"&gt;the next volume in the&lt;/a&gt;&lt;em&gt;&lt;a href="http://bit.ly/2F2vS3Z"&gt;Imposter’s Handbook&lt;/a&gt;&lt;/em&gt;&lt;a href="http://bit.ly/2F2vS3Z"&gt;series.&lt;/a&gt; It took us just over 18 months to put this thing together and I couldn’t be happier with it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--eVxo_yRw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/01/mutual.png%3Fresize%3D1024%252C768%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--eVxo_yRw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/01/mutual.png%3Fresize%3D1024%252C768%26ssl%3D1" alt=""&gt;&lt;/a&gt;Shannon’s Second Theorem, Illustrated via Deathstar&lt;/p&gt;

&lt;h2&gt;
  
  
  From Logic to Boolean Algebra, Binary to Circuits
&lt;/h2&gt;

&lt;p&gt;There was so much content I had to ax from the first volume of &lt;em&gt;The Imposter’s Handbook&lt;/em&gt; because I ran out of time and space. For instance: I knew very, very little about binary operations and even less about encryption and I desperately wanted to change that. Unfortunately, it had to wait until I had the time.&lt;/p&gt;

&lt;p&gt;That time came over the last 18 months. Scott and I dove into things like binary addition and subtraction, logic gates, and Boolean Algebra.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--wu4nhz9A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/01/xor.png%3Fresize%3D1024%252C792%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wu4nhz9A--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i1.wp.com/rob.conery.io/wp-content/uploads/2019/01/xor.png%3Fresize%3D1024%252C792%26ssl%3D1" alt=""&gt;&lt;/a&gt;Something I could never remember: XOR&lt;br&gt;&lt;/p&gt;

&lt;p&gt;It was fun to dive into these subjects but I was not expecting what came next. Even writing this now – I’m struggling to come up with a way to accurately capture the singular importance of one person’s work. He’s been compared to Einstein, Edison, Newton – all rolled into one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Shannon and Information Theory
&lt;/h2&gt;

&lt;p&gt;Claude Shannon created Information Theory with a single paper written in 1948. In it, Shannon detailed a way to describe information_ digitally_, that is with 1s and 0s. He detailed how this information might be transmitted in a virtually lossless way and, as if that wasn’t enough, he also described &lt;em&gt;just how much information that digital signal could contain&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We take this kind of thing for granted today, but keep in mind that in Shannon’s day, the only way to communicate over great distances was with telegraph or telephone!&lt;/p&gt;

&lt;p&gt;But wait, there’s more!&lt;/p&gt;

&lt;p&gt;Prior to inventing Information Theory, Shannon wrote a master’s thesis that many people regard as the &lt;em&gt;most important master’s thesis ever written&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Creation of Digital Circuits
&lt;/h2&gt;

&lt;p&gt;In the late 1930s, Claude Shannon was working on his master’s degree at MIT. He was also working with Vannevar Bush on the Differential Analyzer, a room-sized mechanical computer that would calculate differential equations typically for military use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--XrVaftWM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/01/Cambridge_differential_analyser.jpg%3Ffit%3D1024%252C748%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--XrVaftWM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/01/Cambridge_differential_analyser.jpg%3Ffit%3D1024%252C748%26ssl%3D1" alt=""&gt;&lt;/a&gt;The Twin Cambridge University Differential Analyzer (Public Domain)&lt;/p&gt;

&lt;p&gt;This machine had to be programmed by hand, which meant breaking it down and rebuilding it, using rods, wheels, and pullies as variables in a complex ballistic equation. Eventually, many of the mechanical bits were replaced with electrical switches that moved levers here and there, reducing the time it took to break the machine down and rebuild it.&lt;/p&gt;

&lt;p&gt;These switches sparked something in Shannon, who recalled a class he took at the University of Michigan called &lt;em&gt;Boolean Algebra&lt;/em&gt;. Turns out, this chap named George Boole figured out a way to apply mathematical principles to simple logical propositions. Shannon extended those ideas and figured out how the entire room-sized computer could be replaced with a series of &lt;em&gt;electrical circuits&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Shannon invented the digital circuit&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encryption, Hashing and Blockchain
&lt;/h2&gt;

&lt;p&gt;The story kind of wrote itself from that point on. Digital circuits led to digital computers which led to more efficient ways of calculating things which led to the need to transmit that information which led to the need to keep it a secret which led to where we are today.&lt;/p&gt;

&lt;p&gt;Scott and I dove into all of this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--MzTdburl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/01/rsa_1.png%3Fssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--MzTdburl--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i0.wp.com/rob.conery.io/wp-content/uploads/2019/01/rsa_1.png%3Fssl%3D1" alt=""&gt;&lt;/a&gt;Cracking a simple asymmetric cipher&lt;/p&gt;

&lt;p&gt;We explore SSH keys and how RSA works. We dive into hashes, discussing the goods and bads of each – including how Rainbow Tables can be used to quickly and easily crack credit card information that hasn’t been salted.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8k5Wymob--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/01/sha-gringer.png%3Ffit%3D1024%252C759%26ssl%3D1" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8k5Wymob--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://i2.wp.com/rob.conery.io/wp-content/uploads/2019/01/sha-gringer.png%3Ffit%3D1024%252C759%26ssl%3D1" alt=""&gt;&lt;/a&gt;Simplified doodle of SHA256&lt;/p&gt;

&lt;p&gt;We eventually end up with a discussion of cryptocurrency and blockchain, detailing why some people love it and others absolutely hate it. Both sides have some pretty good points…&lt;/p&gt;

&lt;p&gt;It was a ton of fun writing this book – made even moreso because I got to do it with a friend! &lt;a href="http://bit.ly/2F2vS3Z"&gt;I really hope you enjoy it&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>career</category>
      <category>syndication</category>
      <category>writing</category>
      <category>imposter</category>
    </item>
    <item>
      <title>Transactional Data Operations in PostgreSQL Using Common Table Expressions</title>
      <dc:creator>Rob Conery</dc:creator>
      <pubDate>Mon, 13 Aug 2018 21:28:50 +0000</pubDate>
      <link>https://dev.to/robconery/transactional-data-operations-in-postgresql-using-common-table-expressions-59g7</link>
      <guid>https://dev.to/robconery/transactional-data-operations-in-postgresql-using-common-table-expressions-59g7</guid>
      <description>&lt;p&gt;PostgreSQL has a ton of amazing features, often overlooked by developers who prefer to use abstractions to work with SQL and their database. This is a big topic and obviously can spark a ton of debate. If you’ve read my blog before &lt;a href="https://rob.conery.io/2015/02/20/its-time-to-get-over-that-stored-procedure-aversion-you-have/"&gt;you know I dislike ORMs tremendously&lt;/a&gt; … aside from LLBLGenPro because Frans is my friend and he doesn’t like it when I trash ORMs.&lt;/p&gt;

&lt;p&gt;One of the great features of PostgreSQL is &lt;a href="https://www.postgresql.org/docs/10/static/queries-with.html"&gt;Common Table Expressions, or CTEs&lt;/a&gt;, otherwise known as “WITH queries”. These are simply chained SQL expressions that allow you to pass the result of one query into another, functional style.&lt;/p&gt;

&lt;p&gt;I use them a lot for reporting, but I also use them for creating orders when someone &lt;a href="https://bigmachine.io/products"&gt;buys something from me&lt;/a&gt;. Let’s see how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up The Database
&lt;/h2&gt;

&lt;p&gt;Let’s create the core of my database. These tables are simplified, but the core of what they’re supposed to do is present:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;create extension if not exists pgcrypto;

create table orders(
  id serial primary key, 
  key uuid unique default gen_random_uuid(),
  email text not null, 
  total decimal(10,2),
  created_at timestamptz default now()  
);

create table order_items(
  id serial primary key,
  order_id int not null references orders(id) on delete cascade,
  sku text not null,
  price decimal(10,2) not null,
  quantity int not null default 1,
  discount decimal(10,2) not null default 0
);

create table downloads(
  id serial primary key,
  key uuid unique not null default gen_random_uuid(),
  order_id int not null references orders(id) on delete cascade,
  order_item_id int not null references order_items(id) on delete cascade,
  times_downloaded int not null default 0
);

create table products(
  id serial primary key not null,
  sku text unique not null,
  name text not null,
  price decimal(10,2) not null,
  created_at timestamptz not null default now()
);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I’m showing you this code for a few reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If you want to play along (which I encourage), you can&lt;/li&gt;
&lt;li&gt;The defaults and structure make working with CTEs much simpler&lt;/li&gt;
&lt;li&gt;SQL is straightforward and easy if you take the time to learn it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All of that said, there are a few things I’d love to call out about this design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I’m using &lt;code&gt;on delete cascade&lt;/code&gt; for the foreign keys to ensure that I don’t have orphans&lt;/li&gt;
&lt;li&gt;I’m ensuring that null values don’t creep into my database&lt;/li&gt;
&lt;li&gt;Whenever I have a &lt;code&gt;not null&lt;/code&gt; constraint, I try to make sure I set a &lt;code&gt;default&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Finally, &lt;code&gt;gen_random_uuid&lt;/code&gt; comes from the &lt;code&gt;pgcrypto&lt;/code&gt; extension&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OK, let’s add some data to our products table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;insert into products(sku, name, price)
values
('imposter-single','The Imposter''s Handbook', 30.00),
('mission-interview','Mission:Interview',49.00);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Great. Now let’s get to the good stuff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 1: Saving Order Data Transactionally
&lt;/h2&gt;

&lt;p&gt;When a new order comes in, I need to create an &lt;code&gt;order&lt;/code&gt; record and then an &lt;code&gt;order_items&lt;/code&gt; record. This &lt;em&gt;must&lt;/em&gt; be done in a transaction or Bad Things will happen. This is simple to do with a CTE, &lt;strong&gt;because CTEs execute within a single transaction&lt;/strong&gt; :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;with new_order as(
  insert into orders(email, total) 
  values ('rob@bigmachine.io',100.00) 
  returning *
), new_items as (
  insert into order_items(order_id, sku, price)
  select new_order.id, 'imposter-single',30.00
  from new_order
  returning *
)
select * from new_items;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;When you insert a new record with PostgreSQL, you can ask for it right back with &lt;code&gt;returning *&lt;/code&gt;. If you just want the &lt;code&gt;id&lt;/code&gt;, you can add &lt;code&gt;returning id&lt;/code&gt;. The first query inserts the new &lt;code&gt;order&lt;/code&gt; and then returns it, which I can then use in the second query. Obviously: hard-coding values isn’t a good idea, but I’ll fix that in a moment.&lt;/p&gt;

&lt;p&gt;The result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id | order_id | sku | price | quantity | discount 
----+----------+-----------------+-------+----------+----------
  1 | 1 | imposter-single | 30.00 | 1 | 0.00
(1 row)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Perfect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 2: Creating Downloads From Our New Order
&lt;/h2&gt;

&lt;p&gt;I’m starting simple, making sure things work as intended, then moving forward. My next step is to create downloads so that users can download what they’ve bought immediately. For that, I can chain a new query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;with new_order as(
  insert into orders(email, total) 
  values ('rob@bigmachine.io',100.00) 
  returning *
), new_items as (
  insert into order_items(order_id, sku, price)
  select new_order.id, 'imposter-single',30.00
  from new_order
  returning *
), new_downloads as (
  insert into downloads(order_id, order_item_id)
  select new_order.id, new_items.id 
  from new_order, new_items
  returning *
)

select * from new_downloads;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I tack on a &lt;code&gt;returning *&lt;/code&gt; from my insert statement for &lt;code&gt;order_items&lt;/code&gt; and then I can use that to generate the downloads in a third query, this time using a &lt;code&gt;select&lt;/code&gt; for the insert.&lt;/p&gt;

&lt;p&gt;The result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id | key | order_id | order_item_id | times_downloaded 
----+--------------------------------------+----------+---------------+------------------
  1 | 1fa7c369-94c4-4220-83ba-78e35cfc7377 | 1 | 1 | 0
(1 row)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Great! The best part of all of this, so far, is that I can feel good about the data going into my database because of my constraints and design, and I can have faith that it’s been added correctly because &lt;strong&gt;a CTE is wrapped in a single transaction&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem 3: Inserting Multiple Order Items
&lt;/h2&gt;

&lt;p&gt;One of the minor drawbacks of a CTE is that you can only execute a single statement with each clause. If you think of this in functional programming terms, it’s a bit like &lt;em&gt;currying&lt;/em&gt; in that you have a single argument (the result of the previous query) and a single function body that returns a single value.&lt;/p&gt;

&lt;p&gt;How, then, would you insert multiple &lt;code&gt;order_items&lt;/code&gt;? This is where things could get a little tricky, especially if you’re not a fan of SQL. I like using it, so what you’re about to see doesn’t freak me out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;with new_order as(
  insert into orders(email, total) 
  values ('rob@bigmachine.io',100.00) 
  returning *
), new_items as (
  insert into order_items(order_id, sku, price)
  (
    select new_order.id, sku,price
    from products, new_order
    where sku in('imposter-single','mission-interview')
  )
  returning *
), new_downloads as (
  insert into downloads(order_id, order_item_id)
  select new_order.id, new_items.id 
  from new_order, new_items
  returning *
)

select * from new_downloads;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;I’m getting around the problem by using a simple &lt;code&gt;select&lt;/code&gt; statement for the insert. It’s going to the &lt;code&gt;products&lt;/code&gt; table to insert whatever SKUs are given to it. Let’s run the query and see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;id | key | order_id | order_item_id | times_downloaded 
----+--------------------------------------+----------+---------------+------------------
  1 | 6e695533-dd8d-407d-bdca-d71f81c666fb | 1 | 1 | 0
  2 | 31f81049-d08a-4f4c-b30c-565169178268 | 1 | 2 | 0
(2 rows)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;It works! Sort of. We have one last problem…&lt;/p&gt;

&lt;h2&gt;
  
  
  Hard Coding, Data Integrity, and Validation?
&lt;/h2&gt;

&lt;p&gt;I’m hard-coding the email address as well as the SKUs, which isn’t a Good Thing, obviously. This is where we brush up against what your ORM wants to do for your vs. what you might want to do as a coder. Put another way: &lt;em&gt;would you really write this SQL in your code?&lt;/em&gt;. &lt;strong&gt;I certainly wouldn’t&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here are some possible solutions to these issues.&lt;/p&gt;

&lt;h3&gt;
  
  
  The SKU Problem
&lt;/h3&gt;

&lt;p&gt;What if a SKU is passed to this SQL that is not in our product’s table? The short answer is non-compelling: &lt;em&gt;nothing&lt;/em&gt;. If a SKU isn’t found in the &lt;code&gt;products&lt;/code&gt; table, it will simply be ignored. This is &lt;em&gt;sub-optimal&lt;/em&gt; because we can end up adding crap data to our system!&lt;/p&gt;

&lt;p&gt;How can we fix this? My first inclination would be to wrap this routine in a function, passing in the email, SKUs and everything else in a &lt;code&gt;jsonb&lt;/code&gt; variable called &lt;code&gt;cart&lt;/code&gt;. In my function, I would make sure the cart totals matched and that the SKUs were present in the database.&lt;/p&gt;

&lt;p&gt;This is where you rip me apart for putting business logic in a database. I can understand why people think that, however I can also understand &lt;strong&gt;why I do it anyway&lt;/strong&gt;. The answer is simple: I’m more likely to change my platform/ORM than I am PostgreSQL. To me, this kind of thing belongs as close to your data as possible. It’s a simple data operation that’s not exactly unique to my business, is it?&lt;/p&gt;

&lt;p&gt;The other solution is to make sure the cart is validated before it gets pushed to this query. If we can trust the inputs, then we’re good to go.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blobs of SQL In Your Code
&lt;/h3&gt;

&lt;p&gt;I think SQL is a beautiful thing, but that’s &lt;em&gt;my&lt;/em&gt; problem. Yours is trying to figure out where to put this stuff! One thing you could do is to store this as a &lt;a href="https://www.postgresql.org/docs/10/static/sql-prepare.html"&gt;prepared statement&lt;/a&gt;, which offers quite a few benefits. Let’s see the code, then we’ll dive into the benefits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prepare new_order(text, decimal(10,2), text[]) as
with new_order as(
  insert into orders(email, total) 
  values ($1,$2) 
  returning *
), new_items as (
  insert into order_items(order_id, sku, price)
  (
    select new_order.id, sku,price
    from products, new_order
    where sku = any($3)
  )
  returning *
), new_downloads as (
  insert into downloads(order_id, order_item_id)
  select new_order.id, new_items.id 
  from new_order, new_items
  returning *
)
select * from new_downloads;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Whenever you write a SQL statement for PostgreSQL, the engine needs to parse the SQL, analyze it, and then optimize/rewrite it for execution. You can skip a number of those steps if you tell PostgreSQL the query you plan on running ahead of time, so it can parse and analyze it &lt;strong&gt;once&lt;/strong&gt;. You can do this with the &lt;code&gt;prepare&lt;/code&gt; statement.&lt;/p&gt;

&lt;p&gt;The downside is that you need to use positional arguments, as you see I’m doing here with &lt;code&gt;$1, $2&lt;/code&gt; etc, which means you lose a little readability. If you can get over that, executing this statement means that you can call it by name using &lt;code&gt;execute&lt;/code&gt; and some parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;execute new_order('rob@bigmachine.io',100.00, '{imposter-single,mission-interview}')
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;You’ll notice the funky &lt;code&gt;{imposter-single}&lt;/code&gt; syntax – that’s how you work with arrays in PostgreSQL. Since I’ve switched to working with arrays, I’ve opted to use the &lt;code&gt;any&lt;/code&gt; keyword, which works like &lt;code&gt;in&lt;/code&gt; but is specifically for array values.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Long post, but I encourage you to explore and see what’s possible with your database, even if it’s not PostgreSQL. The SQL I wrote above would likely replace 100s of (total) lines of ORM code and orchestration, but yes there is a learning curve.&lt;/p&gt;

</description>
      <category>database</category>
      <category>postgres</category>
      <category>syndication</category>
    </item>
  </channel>
</rss>
