<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Rest on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/rest/</link><description>Recent content in Rest on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Thu, 28 Dec 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/rest/index.xml" rel="self" type="application/rss+xml"/><item><title>Simple SQLite in Express</title><link>https://blog.iankulin.com/simple-sqlite-in-express/</link><pubDate>Thu, 28 Dec 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/simple-sqlite-in-express/</guid><description>&lt;p&gt;I don&amp;rsquo;t have experience with &lt;a href="https://www.sqlite.org/index.html"&gt;SQLite&lt;/a&gt; and want to shift one of my apps over from Mongoose since apparently SQLite is &lt;a href="https://www.sqlite.org/whentouse.html"&gt;much more capable&lt;/a&gt; than I imagined. My usual tactic when trying something new is to try and get a minimal project working on it, so what follows is the simplest possible node/express REST API to demo SQLite.&lt;/p&gt;
&lt;p&gt;The simplest possible Express app is going to look something like this. Of course we would have gone to the terminal with &lt;code&gt;npm i express&lt;/code&gt; first so this could run.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; express &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; require&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;express&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; app &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; express&lt;span style="color:#eceff4"&gt;();&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; port &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#b48ead"&gt;3000&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;Hello, World!&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);});&lt;/span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;listen&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;port&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;()&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; console&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;log&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;Server is running at http&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt;localhost&lt;span style="color:#eceff4"&gt;:&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;port&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The only thing to add to this for the moment is some middleware to allow Express to parse JSON body payloads.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app.use(express.json());
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="body-v-query"&gt;Body v Query&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;ll just take you on a short detour here if you&amp;rsquo;re not familiar with HTTP requests. There&amp;rsquo;s a couple of common ways to send some data along with a request. The oldest one is to shove it all in the URL. You will have seen these sorts of things:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;http://localhost:3000/adduser?name=Fred&amp;amp;email=fred@example.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If you want all of your data to fit in a link, these are great. They can be bookmarked and so on. You see them all the time - especially in links with heaps of tracking data. The &amp;lsquo;?&amp;rsquo; denotes it as a query.&lt;/p&gt;
&lt;p&gt;The code to process the GET request above would look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/adduser&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;query&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;query&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;email&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note that I&amp;rsquo;ve used a GET request here, when semantically a POST would make more sense. That&amp;rsquo;s because you can only use query strings for GETs.&lt;/p&gt;
&lt;p&gt;Often the data you want to pass is going to be more complex, or you don&amp;rsquo;t want it in the URL for other reasons (for example, you wouldn&amp;rsquo;t want a user to be able to bookmark a record delete request). In that case you use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Request/body"&gt;body&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can stuff all sorts of text data in the body. Most times you are going to want JSON. I do for this demo, so that&amp;rsquo;s why I&amp;rsquo;ve added the &lt;code&gt;expresss.json()&lt;/code&gt; middleware - all the hard work will be done for me and I can just do this to access the information that&amp;rsquo;s passed as part of the request:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/users&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;email&lt;span style="color:#eceff4"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="adding-sqlite"&gt;Adding SQLite&lt;/h3&gt;
&lt;p&gt;Once you&amp;rsquo;ve run &lt;code&gt;npm i sqlite3&lt;/code&gt; at the terminal to install the package, at the top of our app somewhere - probably where we&amp;rsquo;re requiring the other packages - we&amp;rsquo;ll need this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; sqlite3 &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; require&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;sqlite3&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;verbose&lt;span style="color:#eceff4"&gt;();&lt;/span&gt;&lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; db &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; new sqlite3&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;Database&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;db/test.sqlite&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is just requiring the package, and giving us &lt;code&gt;db&lt;/code&gt; as the variable for the SQLite database connection. The database is actually a file in the &lt;code&gt;db/&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;On the first run, obviously this will be empty, so somewhere before the listen command, we need to create a table.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// if the &amp;#39;users&amp;#39; table doesn&amp;#39;t exist, // create it with &amp;#39;name&amp;#39; and &amp;#39;email&amp;#39; columnsdb.run(&amp;#39;CREATE TABLE IF NOT EXISTS users (name TEXT, email TEXT)&amp;#39;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you&amp;rsquo;ve never encountered SQL before, and was expecting a bunch of methods being passing in structs to do this, this is going to be alarming. But no - in SQL we do things by sending the engine a string. This has created a massive &lt;a href="https://en.wikipedia.org/wiki/SQL_injection"&gt;attack surface&lt;/a&gt;, but it&amp;rsquo;s also a convenient and very readable convention.&lt;/p&gt;
&lt;p&gt;For our simple purposes today, that could be enough infrastructure for SQLite, but because we are good programmers, we&amp;rsquo;ll correctly close the database when the app undergoes an orderly shutdown by adding this before the &lt;code&gt;app.listen&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;// close the database connection when the app is shutting downprocess.on(&amp;#39;SIGINT&amp;#39;, () =&amp;gt; { db.close((err) =&amp;gt; { if (err) { console.error(&amp;#39;Error closing SQLite database:&amp;#39;, err.message); } else { process.exit(0); } });});
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="working-with-sqlite"&gt;Working with SQLite&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s the infrastructure out of the way. Now, onto our CRUD (create, read, update, delete) operations to manipulate our stored data. Since this is just a demo, the simplest way to show these is just to have endpoints for each one. To exercise these endpoints you could use the development tools in your browser, but most people will use an API testing tool like Postman, or Insomnia. I much prefer &lt;a href="https://blog.iankulin.com/we-need-to-talk-about-bruno/"&gt;Bruno&lt;/a&gt; for this job, so I&amp;rsquo;ll use that (and suggest you do too, get it &lt;a href="https://www.usebruno.com/"&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;h4 id="create-add"&gt;Create (add)&lt;/h4&gt;
&lt;p&gt;We already started on that earlier, and explained the concept of passing in data via the &amp;lsquo;body&amp;rsquo; here&amp;rsquo;s the complete thing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint to add a user record to the users table&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; expects a name &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; email &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the JSON body of the requestapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/users&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;email&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; sql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;INSERT INTO users &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; email&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; VALUES &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;${name}&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;${email}&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;run&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;sql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; function&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;201&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;json&lt;span style="color:#eceff4"&gt;({&lt;/span&gt; rowid&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; this&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;lastID &lt;span style="color:#eceff4"&gt;});&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So this code processes a request to our server - something like &lt;code&gt;http://localhost:3000/users&lt;/code&gt; and expects the body payload to contain some JSON with a name and email. It could look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{ &amp;#34;name&amp;#34;: &amp;#34;John Doe&amp;#34;, &amp;#34;email&amp;#34;: &amp;#34;john.doe@example.com&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And when run in Bruno:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-10.24.54-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-10.24.54-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="read"&gt;Read&lt;/h4&gt;
&lt;p&gt;There&amp;rsquo;s a couple of reads we can do, one where all the data is returned, and one where only a specific record is. Let&amp;rsquo;s do the big one first, since we&amp;rsquo;ll use it a lot while we&amp;rsquo;re writing the rest!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint to get all users from the users table &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the databaseapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/users&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; sql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#39;SELECT rowid, * FROM users&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;all&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;sql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; rows&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;rows&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which looks like this in Bruno:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-10.35.56-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-10.35.56-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Or if we just want one in particular, we&amp;rsquo;ll pass the id in the URL.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint to get a single user from the users table &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the database&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; expects an id &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the URLapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/user/:id&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; id &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;params&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; sql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;SELECT rowid&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;*&lt;/span&gt; FROM users WHERE rowid &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;sql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; row&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;row&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-10.55.25-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-12-16-at-10.55.25-am.png" width="1000" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="update"&gt;Update&lt;/h4&gt;
&lt;p&gt;You&amp;rsquo;re probably getting the hang of this now.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint to update a user record &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the users table &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the database&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; expects a name&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; email &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the JSON body of the request&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; expects an id &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the URLapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;put&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/user/:id&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; id &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;params&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;email&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; sql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;UPDATE users SET name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;${name}&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;${email}&amp;#34;&lt;/span&gt; WHERE rowid &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;run&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;sql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;User updated.&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="delete"&gt;Delete&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint to delete a user record from the users table &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the database&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; expects an id &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the URL&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt; Doesn&lt;span style="color:#a3be8c"&gt;&amp;#39;t complain if the id doesn&amp;#39;&lt;/span&gt;t exist&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;delete&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/user/:id&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; id &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;params&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; sql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;DELETE FROM users WHERE rowid &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;$&lt;/span&gt;&lt;span style="color:#eceff4"&gt;{&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;}&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;run&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;sql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;();&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="hardening"&gt;Hardening&lt;/h3&gt;
&lt;p&gt;To keep things simple (since I was just trying to show basic examples of using sqlite) I used string interpolation when making the SQL to run against the database. That&amp;rsquo;s not a great technique because of the danger of SQL injection; so we should routinely use parameterized queries instead. Here&amp;rsquo;s how adding a user looks if we use parameterized queries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint to add a user record to the users table&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; expects a name &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; email &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the JSON body of the requestapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/users&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;email&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; sql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;INSERT INTO users &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; email&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; VALUES &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#bf616a"&gt;?&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#bf616a"&gt;?&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;run&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;sql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; email&lt;span style="color:#eceff4"&gt;],&lt;/span&gt; function&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;201&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;json&lt;span style="color:#eceff4"&gt;({&lt;/span&gt; rowid&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; this&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;lastID &lt;span style="color:#eceff4"&gt;});&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With parameterized queries, whatever the user passes in ends up in the database rather than being executed as part of the query string.&lt;/p&gt;
&lt;p&gt;For example, imagine if an API user tried to add a user with this body:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{ &amp;#34;name&amp;#34;: &amp;#34;John&amp;#34;, &amp;#34;email&amp;#34;: &amp;#34;john@example.com\&amp;#34;); DROP TABLE users; --&amp;#34;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then my original add code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;app&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;post&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/users&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;email&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; sql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;INSERT INTO users &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; email&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; VALUES &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#34;${name}&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#a3be8c"&gt;&amp;#34;${email}&amp;#34;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#bf616a"&gt;`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;run&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;sql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; function&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;201&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;json&lt;span style="color:#eceff4"&gt;({&lt;/span&gt; rowid&lt;span style="color:#eceff4"&gt;:&lt;/span&gt; this&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;lastID &lt;span style="color:#eceff4"&gt;});&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;would result in executing this against the database:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;INSERT INTO users (name, email) VALUES (&amp;#34;John&amp;#34;, &amp;#34;john@example.com&amp;#34;); DROP TABLE users; --&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;which would delete the entire users table from the database. This is &lt;a href="https://xkcd.com/327/"&gt;widely known as the &amp;ldquo;Bobby Tables&amp;rdquo; problem&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the parameterized version, you just end up with an ugly user record.&lt;/p&gt;
&lt;p&gt;Changing all of these doesn&amp;rsquo;t add much code, but does make it a little bit harder to follow, hence showing you the old version first.&lt;/p&gt;
&lt;h3 id="rest-api-conventions"&gt;REST API conventions&lt;/h3&gt;
&lt;p&gt;You may have noticed in this code I&amp;rsquo;ve used a variety of HTTP request types - GET, POST, PUT, DELETE etc. There&amp;rsquo;s no rules for these things, but if someone else (including future you) is going to have to maintain or use your API, it&amp;rsquo;s a good idea to follow the conventions.&lt;/p&gt;
&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Situation&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Request&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;URL&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Return&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Add a record&lt;/td&gt;&lt;td&gt;POST&lt;/td&gt;&lt;td&gt;/users&lt;/td&gt;&lt;td&gt;record id&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Replace a whole record&lt;/td&gt;&lt;td&gt;PUT&lt;/td&gt;&lt;td&gt;/users/:id&lt;/td&gt;&lt;td&gt;the whole record&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Replace part of a record&lt;/td&gt;&lt;td&gt;PATCH&lt;/td&gt;&lt;td&gt;/users/:id&lt;/td&gt;&lt;td&gt;the whole record&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Get all the records&lt;/td&gt;&lt;td&gt;GET&lt;/td&gt;&lt;td&gt;/users&lt;/td&gt;&lt;td&gt;all the records&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Get a particular record&lt;/td&gt;&lt;td&gt;GET&lt;/td&gt;&lt;td&gt;/users/:id&lt;/td&gt;&lt;td&gt;that record&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Delete a record&lt;/td&gt;&lt;td&gt;DELETE&lt;/td&gt;&lt;td&gt;/users/:id&lt;/td&gt;&lt;td&gt;nothing&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;You might have noticed that I haven&amp;rsquo;t done the PATCH - the difference between that and the PUT is that with the PATCH we don&amp;rsquo;t supply the whole record, just the fields we want to change. I&amp;rsquo;m not going to worry about that for this API since our record is so small.&lt;/p&gt;
&lt;p&gt;But I also don&amp;rsquo;t return the whole record after a PUT. Unfortunately, it means a second request - but there&amp;rsquo;s probably not much of a performance hit since it will be in the cache.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; endpoint to update a user record &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the users table &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the database&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; expects a name&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;and&lt;/span&gt; email &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the JSON body of the request&lt;span style="color:#81a1c1"&gt;//&lt;/span&gt; expects an id &lt;span style="color:#81a1c1;font-weight:bold"&gt;in&lt;/span&gt; the URLapp&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;put&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#a3be8c"&gt;&amp;#39;/user/:id&amp;#39;&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;req&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; res&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; id &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;params&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; req&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;body&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;email&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; updateSql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;UPDATE users SET name &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;?&lt;/span&gt;&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; email &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;?&lt;/span&gt; WHERE rowid &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;?`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;run&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;updateSql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;name&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; email&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; id&lt;span style="color:#eceff4"&gt;],&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;const&lt;/span&gt; selectSql &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;`&lt;/span&gt;SELECT rowid&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;*&lt;/span&gt; FROM users WHERE rowid &lt;span style="color:#81a1c1"&gt;=&lt;/span&gt; &lt;span style="color:#bf616a"&gt;?`&lt;/span&gt;&lt;span style="color:#eceff4"&gt;;&lt;/span&gt; db&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;get&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;selectSql&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; &lt;span style="color:#eceff4"&gt;[&lt;/span&gt;id&lt;span style="color:#eceff4"&gt;],&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;,&lt;/span&gt; row&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#81a1c1"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;if&lt;/span&gt; &lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#eceff4"&gt;)&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;500&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;send&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;err&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;message&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#81a1c1;font-weight:bold"&gt;else&lt;/span&gt; &lt;span style="color:#eceff4"&gt;{&lt;/span&gt; res&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;status&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;&lt;span style="color:#b48ead"&gt;200&lt;/span&gt;&lt;span style="color:#eceff4"&gt;)&lt;/span&gt;&lt;span style="color:#81a1c1"&gt;.&lt;/span&gt;json&lt;span style="color:#eceff4"&gt;(&lt;/span&gt;row&lt;span style="color:#eceff4"&gt;);&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});&lt;/span&gt; &lt;span style="color:#eceff4"&gt;}&lt;/span&gt; &lt;span style="color:#eceff4"&gt;});});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://github.com/IanKulin/sqlite-rest-demo/blob/main/app.js"&gt;Link to the completed project on Github&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>We need to talk about Bruno</title><link>https://blog.iankulin.com/we-need-to-talk-about-bruno/</link><pubDate>Fri, 27 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/we-need-to-talk-about-bruno/</guid><description>&lt;p&gt;&lt;a href="https://www.usebruno.com/"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-6.01.17-pm.png" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://blog.iankulin.com/how-to-deploy-a-node-js-app/"&gt;mentioned before&lt;/a&gt; that I was using Insomnia as a tool to check my REST APIs as I was developing them, and that I was avoiding Postman (which I guess is more widely used since it&amp;rsquo;s worth &lt;a href="https://techcrunch.com/2021/08/18/api-platform-postman-valued-at-5-6-billion-in-225-million-fundraise/"&gt;USD5.6 billion&lt;/a&gt;) because&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The only reason I&amp;rsquo;m using Insomnia instead of Postman is that when I tried Postman, it straight away wanted some of my data to make it work. Insomnia hasn&amp;rsquo;t forced me to do that yet.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sadly that was prophetic. I saw &lt;a href="https://vmst.io/@wtpisaac"&gt;@wtpisaac@vmst.io&lt;/a&gt; &lt;a href="https://vmst.io/@wtpisaac/111150369449470670"&gt;mention this exact thing&lt;/a&gt; had happened.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-5.43.52-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;The following day when I opened up Insomnia, it asked to upgrade and I said no, and was able to write and save a handful of new calls to test the API I was building. Sadly, I closed it, and of course on the next run, it demanded an account be created. I skipped that, and it presented me with a &amp;ldquo;sandbox&amp;rdquo; and all my saved requests were gone.&lt;/p&gt;
&lt;p&gt;Luckily, Isaac also suggests a solution - &lt;a href="https://www.usebruno.com/"&gt;Bruno&lt;/a&gt;. This is a 1K star FOSS project by &lt;a href="https://github.com/helloanoop"&gt;helloanoop&lt;/a&gt;. There are Mac, Windows and Linux clients, as well as a CLI and VSCode plugins. One of it&amp;rsquo;s selling points (apart from it&amp;rsquo;s not Postman or Insomnia) is that the collections of requests are saved in very simple human readable text (a language called &lt;em&gt;bru&lt;/em&gt;) so it&amp;rsquo;s straightforward and sensible to commit them to source control along with your code.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-6.14.09-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-01-at-6.14.09-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This video from Anoop (with a very clickbait-y title) does a good job of explaining his project.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/b_ctmKlEOXg?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;I&amp;rsquo;ve only played around with Bruno for an afternoon, but I&amp;rsquo;m loving it so far. Seems like it will do everything I need, and the diffable files for the requests are a bonus. This is a project that deserves to be better known.&lt;/p&gt;</description></item><item><title>Simple API endpoint in Go</title><link>https://blog.iankulin.com/simple-api-endpoint-in-go/</link><pubDate>Wed, 27 Sep 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/simple-api-endpoint-in-go/</guid><description>&lt;img src="https://blog.iankulin.com/images/gopher.png" width="219" alt=""&gt;
&lt;p&gt;I&amp;rsquo;d like a small, quick, low load endpoint on all my nodes and VM&amp;rsquo;s that exposes a text keyword indicating if that machine is okay for RAM and disk space. I&amp;rsquo;m currently using &lt;a href="https://blog.iankulin.com/tags/uptime-kuma/"&gt;Uptime Kuma&lt;/a&gt; to monitor if these machines are pingable, but I&amp;rsquo;d love a tiny bit more information from them so I&amp;rsquo;d get a &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;Ntfy&lt;/a&gt; buzz on my phone if a machine is in trouble.&lt;/p&gt;
&lt;p&gt;I mentioned a couple of weeks ago that the benefit of doing it in C rather than Node.js was probably not worth the trouble, but then being a fickle developer, decided to write it in Go.&lt;/p&gt;
&lt;p&gt;This was a pretty sweet experience, it&amp;rsquo;s a nice language and the ecosystem is good. When writing such a small utility, you don&amp;rsquo;t really get a full appreciation for a language, but there is a couple of nice things going on - one I appreciated was that unused code - for example an import that&amp;rsquo;s not used, or a variable declared but not accessed is a compiler error and flagged by the intellisense as you type.&lt;/p&gt;
&lt;p&gt;In terms of the language as written, it&amp;rsquo;s fair to say C-like - there&amp;rsquo;s no weirdness like the formatting being semantic. It&amp;rsquo;s statically typed, but has good inference.&lt;/p&gt;
&lt;p&gt;The code is up on &lt;a href="https://github.com/IanKulin/vitals-glimpse"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s hard-coded to port 10321 and the route is &lt;code&gt;/vitals.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-15-at-9.37.44-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-15-at-9.37.44-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You get back this JSON. In my Uptime Kuma system, I search for the keywords &lt;code&gt;mem_okay&lt;/code&gt; and &lt;code&gt;disk_okay&lt;/code&gt; - no need to parse the JSON, it&amp;rsquo;s just an on/off status check that will show up in red on the page if there&amp;rsquo;s trouble, and ping my phone using &lt;a href="https://blog.iankulin.com/uptime-kuma-nfty/"&gt;ntfy.sh&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In Uptime Kuma, there&amp;rsquo;s an option when setting up a new monitor for &lt;code&gt;Http(s) Keyword&lt;/code&gt;. How this works is that it will scrape that web address and look to see if a particular keyword exists. If the keyword is present on the page, that site is marked as up, if not, it&amp;rsquo;s marked as down.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-08-15-at-7.47.44-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-08-15-at-7.47.44-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Testing the memory threshold for the screenshot above was fun:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#d8dee9;background-color:#2e3440;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stress-ng --vm-bytes $(awk &amp;#39;/MemAvailable/{printf &amp;#34;%d\n&amp;#34;, $2 * 0.9;}&amp;#39; &amp;lt; /proc/meminfo)k --vm-keep -m 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>How to deploy a Node.js app</title><link>https://blog.iankulin.com/how-to-deploy-a-node-js-app/</link><pubDate>Wed, 05 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/how-to-deploy-a-node-js-app/</guid><description>&lt;p&gt;This is one of those things that is simple once you know it. I had my &lt;a href="https://blog.iankulin.com/using-node-js-to-return-a-static-file/"&gt;tiny Node service working&lt;/a&gt; on my MacBook, but how do I run it on the server?&lt;/p&gt;
&lt;h3 id="native-or-container"&gt;Native or Container&lt;/h3&gt;
&lt;p&gt;Obviously I need Node.js installed on the server, should I have it in a Docker container, or native on the machine. There&amp;rsquo;s no clear answer here - in a container set up with Docker Compose might be more in line with my ideology of treating machines as disposable, but a native install is simpler, and I probably want to make life simpler at this stage when I&amp;rsquo;m learning everything.&lt;/p&gt;
&lt;h3 id="installing-node"&gt;Installing Node&lt;/h3&gt;
&lt;p&gt;This took me down a bigger rabbit hole than I was expecting. My VPS is Unbuntu LTS 22.04.2, so I spun one of those up in a VM on the homelab to try things out.&lt;/p&gt;
&lt;p&gt;A quick google search suggested the &lt;a href="https://github.com/nodesource/distributions"&gt;NodeSource binary distributions&lt;/a&gt;. That involves curling a big script (when I pasted the script into ChatGPT it said it wasn&amp;rsquo;t malicious). I could chose the Node version, so I grabbed 20.x That was as painless as you&amp;rsquo;d expect.&lt;/p&gt;
&lt;p&gt;Then I started wondering why I couldn&amp;rsquo;t just &lt;code&gt;apt install&lt;/code&gt; it. If I could do that, it would reduce the chance of a supply chain attack since I&amp;rsquo;d have the power of Canonical on my side. So I rolled the previous install back (thank you Proxmox backups of VMs), and tried:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt install nodejs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That worked fine - Node is in the Ubuntu packages, but the version is &lt;a href="https://nodejs.dev/en/about/releases/"&gt;quite old&lt;/a&gt; - v12.22.9. This is on the current Ubuntu LTS 22.04.2. I don&amp;rsquo;t think it will matter for my purposes, but it explains why you&amp;rsquo;d do something other than just this.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also going to need &lt;a href="https://www.npmjs.com/"&gt;npm&lt;/a&gt;, so lets get that with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt install npm&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That seemed to download a heap more stuff that the node install.&lt;/p&gt;
&lt;h3 id="deploying-your-project"&gt;Deploying your project&lt;/h3&gt;
&lt;p&gt;Again, the first search result was more complicated than I needed. The advice was to clone my repository onto the server where I wanted to deploy. This is such a minor project, I hadn&amp;rsquo;t pushed it up to GitHub. So that seemed excessive. You know, not everything has to be DevOps CI/CD! I mean, we ain&amp;rsquo;t talking about a very complicated project here:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-06-26-at-8.34.20-pm.png" alt=""&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve got this tiny source file, and the text file I want to serve. All the dependencies (just Express) are in the &lt;code&gt;package.json&lt;/code&gt;, so presumably that&amp;rsquo;s all I need on the server to get going.&lt;/p&gt;
&lt;p&gt;I &lt;code&gt;scp&lt;/code&gt;&amp;rsquo;d those from my laptop to a directory on the folder:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-06-26-at-8.41.19-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Once they are there, I need to install the packages from the package.json, so we do that with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm install&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;That installed 59 packages (presumably Express plus 58 of it&amp;rsquo;s dependencies). Then I started the app with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;node .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and it worked!&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-06-26-at-8.55.34-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;h3 id="insomnia"&gt;Insomnia&lt;/h3&gt;
&lt;p&gt;I should probably explain what you&amp;rsquo;re looking at above. I could have tested this little node server by going to the api address in a browser and checked that I got back the text file I was expecting. And in Chrome (and I assume Firefox) there are developer tools that would show the return code etc. However, most of the REST API videos I&amp;rsquo;ve watched use a better tool - mostly &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt;. These sort of tools give you a heap of other capabilities, none of which I really need for this simple project, but will be very handy for more complex APIs where there is a body to the request.&lt;/p&gt;
&lt;p&gt;The only reason I&amp;rsquo;m using &lt;a href="https://insomnia.rest/"&gt;Insomnia&lt;/a&gt; instead of Postman is that when I tried Postman, it straightaway wanted some of my data to make it work. Insomnia hasn&amp;rsquo;t forced me to do that yet.&lt;/p&gt;</description></item><item><title>Complicating the Temperature API</title><link>https://blog.iankulin.com/complicating-the-temperature-api/</link><pubDate>Wed, 28 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/complicating-the-temperature-api/</guid><description>&lt;p&gt;I&amp;rsquo;ve been slammed with other work, so my web dev learning has fallen well behind. Luckily, the YouTube procrastination algorithm noticed this and suggested I watch a video from &lt;a href="https://www.youtube.com/@codewithcon"&gt;CodeWithCon&lt;/a&gt; titled &lt;a href="https://www.youtube.com/watch?v=KNa-wMpry00&amp;amp;list=PLkJHe6eU_tzeoe7vKUEa4MrS74CpVEwdI&amp;amp;index=3&amp;amp;t=305s"&gt;Learn Backend in 10 MINUTES&lt;/a&gt;.&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 &lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/KNa-wMpry00?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;
 &lt;/div&gt;

&lt;p&gt;Since I was watching a video of a guy learning to land a C152 at St Baths (a skill I do &lt;em&gt;not&lt;/em&gt; need) at the time, it was hard to argue with myself that I didn&amp;rsquo;t have ten minutes to learn all of backend programming.&lt;/p&gt;
&lt;p&gt;I mean, &lt;em&gt;all&lt;/em&gt; of backend programming in 10 minutes is a big claim, but the video did do a surprising good job of simple REST APIs in &lt;a href="https://nodejs.org/en"&gt;Node&lt;/a&gt; using the &lt;a href="http://expressjs.com/"&gt;Express&lt;/a&gt; framework.&lt;/p&gt;
&lt;p&gt;I abandoned iOS programming a year ago when I started to think about the sort of applications I wanted to develop, and saw they would need to run against cloud databases, and so I was going to have to learn backend web dev at some stage anyway, and if so, learning that, then writing the front-ends for web seemed like a lower friction, and wider audience approach.&lt;/p&gt;
&lt;p&gt;I have &lt;em&gt;sort&lt;/em&gt; of created an API to solve my &lt;a href="https://blog.iankulin.com/outside-temperature-from-an-api-in-a-shell-script/"&gt;temperature logging problem&lt;/a&gt;. A Python script runs as a cron job every 5 minutes on a VPS, calls a weather API, parses the json and drops the values I want into a text file on an NGINX server which can be called with a straightforward GET.&lt;/p&gt;
&lt;p&gt;While that was great to learn a bit of Python, it&amp;rsquo;s not pretty, or standard. It does solve the problem I intended (I wanted that weather data for three servers running at home, but didn&amp;rsquo;t want to hammer the weather API I was using for free) it has a few other problems. As the cron job on the VPS runs each five minutes, the data there can be up to five minutes behind the API, and since the cron jobs on my servers are running on the same five minute intervals, and the call to the Australian VPS is quicker than the API call to the US based API, I&amp;rsquo;m always returning the VPS data from five minutes ago - so now my data is up to ten minutes old.&lt;/p&gt;
&lt;p&gt;Does that matter for this application? No, but the whole exercise was for learning, and this is a good enough reason to improve it my making it even more unnecessarily complicated.&lt;/p&gt;
&lt;p&gt;I think my new system will be that the homelab servers will still poll the VPS, but the VPS will be a Node.js endpoint. When it receives a GET from one of the servers, it will check the age of it&amp;rsquo;s current weather data. If it&amp;rsquo;s less than a minute, it will return that, if it&amp;rsquo;s older than a minute, it will call the weather API, store that and return it.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/20230624-weather.drawio-1.png" width="512" alt=""&gt;
&lt;p&gt;Apart from reducing the latency of the outside temperature data, this has a couple of other benefits. The first is that my VPS won&amp;rsquo;t go on for ever requesting the weather API data after I&amp;rsquo;ve reloaded the operating system on the home servers and completely forgotten about this project. The second is that the temperatures in the data I&amp;rsquo;m getting back look like they only change every 20 minutes, so probably they are stale before I ever get them from Open Weather. There are live weather station web pages that I could scrape for better data, so doing things in node on the VPS leaves a good option open for that future improvement.&lt;/p&gt;
&lt;p&gt;To chunk the project down to really small bite sizes, I&amp;rsquo;ll to it in two parts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first will just be to replicate the current system - return a text file when receiving a GET - in Node. That way I will have dealt with the issue of running Node behind NGINX on the VPS.&lt;/li&gt;
&lt;li&gt;The second part will be to expand that to call the weather API from inside the Node program when it&amp;rsquo;s needed.&lt;/li&gt;
&lt;li&gt;A possible third part would be to convert it all to JSON instead of text, and then deal with that in the Python scripts running on the servers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s the plan.&lt;/p&gt;</description></item></channel></rss>