<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Express on blog.iankulin.com</title><link>https://blog.iankulin.com/tags/express/</link><description>Recent content in Express on blog.iankulin.com</description><generator>Hugo</generator><language>en-AU</language><lastBuildDate>Mon, 28 Apr 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.iankulin.com/tags/express/index.xml" rel="self" type="application/rss+xml"/><item><title>Express router for better code organisation</title><link>https://blog.iankulin.com/express-router-for-better-code-organisation/</link><pubDate>Mon, 28 Apr 2025 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/express-router-for-better-code-organisation/</guid><description>&lt;p&gt;A Node/Express app I&amp;rsquo;m working on has been sprouting routes so much that the &lt;code&gt;server.js&lt;/code&gt; file has swollen to 800 lines - way past my 200-250 comfort zone, so it&amp;rsquo;s time to organise the routes into their own files. That seems like a good topic for a beginner blog post, so let&amp;rsquo;s dive in.&lt;/p&gt;
&lt;p&gt;Imagine we&amp;rsquo;ve written this little Node/Express app.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import express from &amp;#34;express&amp;#34;;
import {
 dbCustomersGet,
 dbCustomersGetById,
 dbCustomersDelete,
 dbOrdersGet,
 dbOrdersGetById,
 dbOrdersGetByCustomerId,
 dbOrdersDelete,
} from &amp;#34;./db.js&amp;#34;;

const app = express();
app.set(&amp;#34;view engine&amp;#34;, &amp;#34;ejs&amp;#34;);
const port = 3002;

app.use(express.urlencoded({ extended: true }));

app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 res.redirect(&amp;#34;/customers&amp;#34;);
});

app.get(&amp;#34;/customers&amp;#34;, (req, res) =&amp;gt; {
 const customers = dbCustomersGet();
 res.render(&amp;#34;customers&amp;#34;, { customers });
});

app.get(&amp;#34;/customers/:id&amp;#34;, (req, res) =&amp;gt; {
 const customer = dbCustomersGetById(req.params.id);
 const orders = dbOrdersGetByCustomerId(req.params.id);
 res.render(&amp;#34;customer&amp;#34;, { customer, orders });
});

app.get(&amp;#34;/customers/:id/delete&amp;#34;, (req, res) =&amp;gt; {
 dbCustomersDelete(req.params.id);
 res.redirect(&amp;#34;/customers&amp;#34;);
});

app.get(&amp;#34;/orders&amp;#34;, (req, res) =&amp;gt; {
 const orders = dbOrdersGet();
 res.render(&amp;#34;orders&amp;#34;, { orders });
});

app.get(&amp;#34;/orders/:id&amp;#34;, (req, res) =&amp;gt; {
 const order = dbOrdersGetById(req.params.id);
 const customer = dbCustomersGetById(order.customerId);
 res.render(&amp;#34;order&amp;#34;, { order, customer });
});

app.get(&amp;#34;/orders/:id/delete&amp;#34;, (req, res) =&amp;gt; {
 dbOrdersDelete(req.params.id);
 res.redirect(&amp;#34;/orders&amp;#34;);
});

app.listen(port, () =&amp;gt; {
 console.log(`Listening on http://127.0.0.1:${port}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Although concocted, this would seem familiar to anyone who&amp;rsquo;s built a CRUD business app.&lt;/p&gt;
&lt;p&gt;One thing I&amp;rsquo;ve done better here than in the real app I&amp;rsquo;m fixing is that the routes are carefully named - all the &amp;lsquo;orders&amp;rsquo; routes begin with &lt;code&gt;/orders&lt;/code&gt;, all the &amp;lsquo;customers&amp;rsquo; routes with &lt;code&gt;/customers&lt;/code&gt;. As we&amp;rsquo;ll see, this is going to make separating them out much easier.&lt;/p&gt;
&lt;h3 id="express-router"&gt;Express Router&lt;/h3&gt;
&lt;p&gt;Like almost everything in Express, the router is middleware. Let&amp;rsquo;s look at how our index.js has changed once we&amp;rsquo;ve moved the routes out into a &lt;code&gt;customers.js&lt;/code&gt; and an &lt;code&gt;orders.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import express from &amp;#34;express&amp;#34;;
import customersRouter from &amp;#34;./routes/customers.js&amp;#34;;
import ordersRouter from &amp;#34;./routes/orders.js&amp;#34;;

const app = express();
app.set(&amp;#34;view engine&amp;#34;, &amp;#34;ejs&amp;#34;);
const port = 3002;

app.use(express.urlencoded({ extended: true }));

// routers
app.use(&amp;#34;/customers&amp;#34;, customersRouter);
app.use(&amp;#34;/orders&amp;#34;, ordersRouter);

// root route redirect to customers
app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 res.redirect(&amp;#34;/customers&amp;#34;);
});

app.listen(port, () =&amp;gt; {
 console.log(`Listening on http://127.0.0.1:${port}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;So much neater!&lt;/p&gt;
&lt;p&gt;First of all, the imports for all my database functions are gone - they&amp;rsquo;ll be in the files for our two routes.&lt;/p&gt;
&lt;p&gt;There are a couple of new imports though - our two &amp;lsquo;routers&amp;rsquo;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import customersRouter from &amp;#34;./routes/customers.js&amp;#34;;
import ordersRouter from &amp;#34;./routes/orders.js&amp;#34;;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then further down, they are installed as middleware:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// routers
app.use(&amp;#34;/customers&amp;#34;, customersRouter);
app.use(&amp;#34;/orders&amp;#34;, ordersRouter);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can pretty much see from this code how this works. Any routes that begin with &amp;ldquo;/customers&amp;rdquo; are sent off to the &lt;code&gt;customersRouter&lt;/code&gt; which we&amp;rsquo;ve imported from &lt;code&gt;&amp;quot;./routes/customers.js&amp;quot;&lt;/code&gt;, and the routes for &amp;ldquo;/orders&amp;rdquo; go to the &lt;code&gt;ordersRouter&lt;/code&gt;. Any route requests that don&amp;rsquo;t match those will be sought in the main file where the app is declared.&lt;/p&gt;
&lt;p&gt;You might have noticed how we&amp;rsquo;re organising the routes - there&amp;rsquo;s a &amp;ldquo;routes&amp;rdquo; folder and they&amp;rsquo;re dropped in there. That&amp;rsquo;s not a requirement, but it&amp;rsquo;s a common convention.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/screenshot-2025-04-04-at-20.42.50.png" width="800" alt=""&gt;
&lt;p&gt;Let&amp;rsquo;s have a look at one of the route files:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;import express from &amp;#34;express&amp;#34;;
import {
 dbCustomersGet,
 dbCustomersGetById,
 dbCustomersDelete,
 dbOrdersGetByCustomerId,
} from &amp;#34;../db.js&amp;#34;;

const router = express.Router();

// GET /customers
router.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 const customers = dbCustomersGet();
 res.render(&amp;#34;customers&amp;#34;, { customers });
});

// GET /customers/:id
router.get(&amp;#34;/:id&amp;#34;, (req, res) =&amp;gt; {
 const customer = dbCustomersGetById(req.params.id);
 const orders = dbOrdersGetByCustomerId(req.params.id);
 res.render(&amp;#34;customer&amp;#34;, { customer, orders });
});

// GET /customers/:id/delete
router.get(&amp;#34;/:id/delete&amp;#34;, (req, res) =&amp;gt; {
 dbCustomersDelete(req.params.id);
 res.redirect(&amp;#34;/customers&amp;#34;);
});

export default router;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is nice. We&amp;rsquo;re only importing the customer database functions, and we&amp;rsquo;ve got all the customer routes in one place in an easily comprehensible file.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s really only one gotcha here which we alluded to earlier. You&amp;rsquo;ll notice how I&amp;rsquo;ve added a comment over each route?&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// GET /customers/:id
router.get(&amp;#34;/:id&amp;#34;, (req, res) =&amp;gt; {
 const customer = dbCustomersGetById(req.params.id);
 const orders = dbOrdersGetByCustomerId(req.params.id);
 res.render(&amp;#34;customer&amp;#34;, { customer, orders });
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is because in the process of specifying that this file deals with all the &amp;ldquo;/customers&amp;rdquo; routes, that part of the request URL has been stripped off - so a call to &lt;code&gt;http://127.0.0.1:3002/customers/5&lt;/code&gt; arrives here as &lt;code&gt;/5&lt;/code&gt;. It&amp;rsquo;s another common practice to put the route path in a comment as I&amp;rsquo;ve done here as a reminder to myself. I wish the Express team had just left the requests unaltered.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Really, that&amp;rsquo;s about all there is to using the Express Router to split your routes out into files; it&amp;rsquo;s quite straightforward. A good naming convention for your routes so that logical groups of routes all start with the same specifier will be a great help.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/IanKulin/route-demo"&gt;Code on GitHub&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Uploading files to a web app with Node</title><link>https://blog.iankulin.com/uploading-files-to-a-web-app-with-node/</link><pubDate>Mon, 02 Sep 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/uploading-files-to-a-web-app-with-node/</guid><description>&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-3.09.38-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;My default approach to web apps at the moment is Node/Express SSR. I needed to have users be able to upload files this week, and as usual there&amp;rsquo;s an express middleware that makes it trivial. This post just steps through using &lt;a href="https://github.com/expressjs/multer"&gt;multer&lt;/a&gt; to make it simple to enable file uploads on your website.&lt;/p&gt;
&lt;h3 id="express--middleware"&gt;Express &amp;amp; middleware&lt;/h3&gt;
&lt;p&gt;Before we look at file uploading, it&amp;rsquo;s worth just explaining how it fits with the other tools we&amp;rsquo;re using:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nodejs.org/en"&gt;Node&lt;/a&gt; - A server runtime that executes javascript. It&amp;rsquo;s a good option for writing web apps if you already know JavaScript from frontend. It has an extensive ecosystem of packages that are installed managed with &lt;a href="https://www.npmjs.com/"&gt;NPM&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt; - A node package that encapsulates a lot of functionality around handling web requests to make it much simpler for the developer. In particular it makes setting up routes easier and introduces the concept of middleware.&lt;/li&gt;
&lt;li&gt;Middleware - in Express we can install middleware - packages that intercept web requests and deal with them or pass them on. Commonly, they work to pull parts of requests out and expose them in developer friendly ways, but they can also do things like apply security rules to requests to allow or deny them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Multer is express middleware to handle data from a web request that includes &amp;ldquo;multipart/form-data&amp;rdquo; - which is what we use for file uploads.&lt;/p&gt;
&lt;h3 id="steps"&gt;Steps&lt;/h3&gt;
&lt;p&gt;Since this is quite a small topic, and I&amp;rsquo;ve started by saying what Node is, I&amp;rsquo;ll pitch these explanations for beginners. I am going to assume you&amp;rsquo;ve been able to install VSCode or some other IDE that you know how to use, that you&amp;rsquo;ve installed Node on the machine you&amp;rsquo;re working on, and you&amp;rsquo;ve got some familiarity with JavaScript &amp;amp; HTML.&lt;/p&gt;
&lt;h4 id="project-setup"&gt;Project Setup&lt;/h4&gt;
&lt;p&gt;Create a directory for your project - I&amp;rsquo;m calling mine &amp;lsquo;file-upload&amp;rsquo; which will be the name of this project. Open VSCode in that directory, and run:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;npm install express multer
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;After a few seconds NPM should have created a couple of files (&lt;code&gt;package.json&lt;/code&gt; &amp;amp; &lt;code&gt;package-lock.json&lt;/code&gt;) and a directory called &lt;code&gt;node_modules&lt;/code&gt;. &lt;code&gt;node_modules&lt;/code&gt; contains all the library code we&amp;rsquo;ll be using, and the package files have some versioning information used by NPM.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.00.01-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.00.01-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is going to be a server that responds to web requests, so we better write a skeleton for that. Create a file called &lt;code&gt;server.js&lt;/code&gt; and add this code.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#34;express&amp;#34;);
const app = express();

// handle the default route
app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 res.send(&amp;#34;hello world&amp;#34;);
});

// Start the server
app.listen(3000, () =&amp;gt; {
 console.log(&amp;#34;Listening on http://127.0.0.1:3000&amp;#34;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To start our server, we need to enter this in the terminal:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;node server.js
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.11.02-pm.jpg" alt=""&gt;&lt;/p&gt;
&lt;p&gt;Now if you visit the web address &lt;a href="http://127.0.0.1:3000"&gt;http://127.0.0.1:3000&lt;/a&gt; in your web browser, you should see the message &amp;ldquo;hello world&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.15.28-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.15.28-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;To stop the server hold down control and press &amp;lsquo;C&amp;rsquo; in the terminal window.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.18.49-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.18.49-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="serving-a-html-file"&gt;Serving a HTML file&lt;/h4&gt;
&lt;p&gt;Sending that &amp;lsquo;hello world&amp;rsquo; text is cool and all, but ideally, our web server would serve a web page. Let&amp;rsquo;s alter the default route of our server to do that:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#34;express&amp;#34;);
const app = express();

// handle the default route
app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 res.sendFile(__dirname + &amp;#34;/index.html&amp;#34;);
});

// Start the server
app.listen(3000, () =&amp;gt; {
 console.log(&amp;#34;Listening on http://127.0.0.1:3000&amp;#34;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This will send the file &lt;code&gt;index.html&lt;/code&gt; instead of just the &amp;lsquo;hello world&amp;rsquo; text from before. The &lt;code&gt;__dirname&lt;/code&gt; part is just saying the index.html file will be in the same directory as our app. We better also create an &lt;code&gt;index.html&lt;/code&gt; there so it can be sent.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;
 &amp;lt;head&amp;gt;
 &amp;lt;title&amp;gt;Hello&amp;lt;/title&amp;gt;
 &amp;lt;/head&amp;gt;
 &amp;lt;body&amp;gt;
 Hello world
 &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ah yes. That&amp;rsquo;s much more professional.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.28.36-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-2.28.36-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="multer"&gt;Multer&lt;/h4&gt;
&lt;p&gt;Now it does get a bit more complicated. Multer can use several different types of storage. For example you might want to use an S3 bucket on AWS. We have simpler tastes and just want to store files as files on our host, but the point is the storage engines can be swapped in and out for Multer, so we need to create a Multer storage engine, then a Multer &lt;code&gt;upload&lt;/code&gt; that uses that storage.&lt;/p&gt;
&lt;p&gt;Then the Multer &lt;code&gt;upload&lt;/code&gt; is used in the route for the web request that contains our file. Possibly this explanation is more complicated than the code. Let&amp;rsquo;s have a look:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#34;express&amp;#34;);
const multer = require(&amp;#34;multer&amp;#34;);
const fs = require(&amp;#34;fs&amp;#34;);

const app = express();

// set up storage engine for Multer
const storage = multer.diskStorage({
 destination: function (req, file, cb) {
 cb(null, &amp;#34;data/&amp;#34;);
 },
 filename: function (req, file, cb) {
 cb(null, file.originalname);
 },
});

const upload = multer({ storage: storage });

// create the &amp;#39;data&amp;#39; directory if it doesn&amp;#39;t exist 
if (!fs.existsSync(&amp;#34;./data&amp;#34;)) {
 fs.mkdirSync(&amp;#34;./data&amp;#34;);
}

// handle the default route
app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 res.sendFile(__dirname + &amp;#34;/index.html&amp;#34;);
});

// handle the upload route
app.post(&amp;#34;/upload&amp;#34;, upload.single(&amp;#34;file&amp;#34;), (req, res) =&amp;gt; {
 if (!req.file) {
 return res.status(400).send(&amp;#34;No file uploaded.&amp;#34;);
 }
 res.send(&amp;#34;File uploaded successfully.&amp;#34;);
});

// start the server
app.listen(3000, () =&amp;gt; {
 console.log(&amp;#34;Listening on http://127.0.0.1:3000&amp;#34;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ll look at each of these new fragments one at a time:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const multer = require(&amp;#34;multer&amp;#34;);
const fs = require(&amp;#34;fs&amp;#34;);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This just pulls in two libraries - &lt;code&gt;multer&lt;/code&gt; for handling the uploads, and &lt;code&gt;fs&lt;/code&gt; which just has some file operations that we&amp;rsquo;ll use for creating the directory for our data.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// set up storage engine for Multer
const storage = multer.diskStorage({
 destination: function (req, file, cb) {
 cb(null, &amp;#34;data/&amp;#34;);
 },
 filename: function (req, file, cb) {
 cb(null, file.originalname);
 },
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As discussed before, we need to create a storage engine, and these can be of different types. This is the diskStorage type. We&amp;rsquo;ll save the file to the ./data directory and use the original filename it had on the user&amp;rsquo;s machine.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const upload = multer({ storage: storage });
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This creates the upload handler with that storage engine we created in the previous step.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// create the &amp;#39;data&amp;#39; directory if it doesn&amp;#39;t exist 
if (!fs.existsSync(&amp;#34;./data&amp;#34;)) {
 fs.mkdirSync(&amp;#34;./data&amp;#34;);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;d get an error if multer tries to write to the directory we told it to when we created the storage engine and the directory did not exist. So we check for that here and create it if needed.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// handle the upload route
app.post(&amp;#34;/upload&amp;#34;, upload.single(&amp;#34;file&amp;#34;), (req, res) =&amp;gt; {
 if (!req.file) {
 return res.status(400).send(&amp;#34;No file uploaded.&amp;#34;);
 }
 res.send(&amp;#34;File uploaded successfully.&amp;#34;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&amp;rsquo;s our route handler. Any POST requests to hrrp://127.0.0.1:3000/upload will be sent here. It passes off the file contained in the request to our Multer upload, and if that all works, it sends a message back to the browser.&lt;/p&gt;
&lt;h4 id="html-form"&gt;HTML form&lt;/h4&gt;
&lt;p&gt;We need a way for the /upload route to be hit with the file data, and that&amp;rsquo;s done by submitting a form with the file data. Let&amp;rsquo;s edit out index.html to do that:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;
 &amp;lt;head&amp;gt;
 &amp;lt;meta charset=&amp;#34;UTF-8&amp;#34; /&amp;gt;
 &amp;lt;title&amp;gt;File Upload&amp;lt;/title&amp;gt;
 &amp;lt;/head&amp;gt;
 &amp;lt;body&amp;gt;
 &amp;lt;form action=&amp;#34;/upload&amp;#34; method=&amp;#34;post&amp;#34; enctype=&amp;#34;multipart/form-data&amp;#34;&amp;gt;
 &amp;lt;input type=&amp;#34;file&amp;#34; name=&amp;#34;file&amp;#34; id=&amp;#34;file&amp;#34; /&amp;gt;
 &amp;lt;input type=&amp;#34;submit&amp;#34; value=&amp;#34;Upload&amp;#34; /&amp;gt;
 &amp;lt;/form&amp;gt;
 &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That &lt;code&gt;enctype&lt;/code&gt; of &lt;code&gt;&amp;quot;multipart/form-data&amp;quot;&lt;/code&gt; is important. That&amp;rsquo;s what Multer wants to see. Apart from that, this is just a form with two buttons. The first one lets the user choose a file, and the second &amp;ldquo;Upload&amp;rdquo; button submits it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-3.00.01-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-3.00.01-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Clicking on the &amp;ldquo;Browse&amp;hellip;&amp;rdquo; button will open the file selection dialog for your operating system. Once you&amp;rsquo;ve selected a file, the name will be shown.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-3.03.39-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-3.03.39-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If we press the Upload button, that file will now be sent to the server, and should appear in the &lt;code&gt;data&lt;/code&gt; directory.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-3.07.20-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-18-at-3.07.20-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This is about the simplest I think we can make this. As always there are a heap of other considerations when implementing this in a live app. For example, I feel uncomfortable using the user submitted file name - perhaps they could manipulate this to be something like &lt;code&gt;./../server.js&lt;/code&gt; and overwrite our source code. We should probably sanitize that, or just replace it with a name we generate. We also should be thinking about restricting the size and or type of files the user can upload, and gracefully handle the errors if we run out of space or some other disaster befalls our system.&lt;/p&gt;</description></item><item><title>Authentication basics for Node apps</title><link>https://blog.iankulin.com/authentication-basics-for-node-apps/</link><pubDate>Mon, 19 Aug 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/authentication-basics-for-node-apps/</guid><description>&lt;p&gt;&lt;a href="https://unsplash.com/photos/calahorra-tower-torre-de-la-calahorra-in-cordoba-spain-a-fortified-gate-built-during-the-late-12th-century-by-the-almohads-to-protect-the-nearby-roman-bridge-in-the-historic-center-of-cordoba-andalusia-spain-ECsukeqrDoo"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-08-10-at-8.59.01-pm.jpg" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pretty much every serious web app needs to include a way for users to log in securely and to be served their content. Since there&amp;rsquo;s a lot of complexity in this, it&amp;rsquo;s highly advisable to use good libraries to support this. In a future post we&amp;rsquo;re going to use those libraries, but first I want to explain what&amp;rsquo;s happening at the lower level and tease out some of the concepts as we build a secure system from the ground up.&lt;/p&gt;
&lt;h3 id="http"&gt;HTTP&lt;/h3&gt;
&lt;p&gt;Before we dive into our authentication story, it&amp;rsquo;s worth thinking about how HTTP works and putting some names to things. We often don&amp;rsquo;t think too much about this level because the mechanics are most abstracted away for us by libraries such as express.js.&lt;/p&gt;
&lt;p&gt;A HTTP &lt;em&gt;request&lt;/em&gt; is just a bunch of lines of text arriving at TCP port 80. It&amp;rsquo;s an agreed on &lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-example-message-exchange"&gt;Internet standard&lt;/a&gt; originally written by &lt;a href="https://en.wikipedia.org/wiki/Tim_Berners-Lee"&gt;Tim Berners-Lee&lt;/a&gt;. The request will include the type of request it is (GET, POST etc), the resource being requested (usually a web-page) - these make up the &lt;em&gt;request line&lt;/em&gt;. Then there will be some lines of data called the &lt;em&gt;header&lt;/em&gt; that might include things like the type of browser making the request, and optionally a &lt;em&gt;body&lt;/em&gt; of the request. The body might contain form data being submitted or a JSON description of an object. If there is a body, there will be a blank line separating it from the header.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET /hello.txt HTTP/1.1
User-Agent: curl/7.64.1
Host: www.example.com
Accept-Language: en, mi
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Similarly, the HTTP &lt;em&gt;response&lt;/em&gt; is just some lines of text. A &lt;em&gt;status line&lt;/em&gt; (which includes the famous &lt;em&gt;status code&lt;/em&gt; such as 404), some &lt;em&gt;headers&lt;/em&gt; and the &lt;em&gt;body&lt;/em&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
ETag: &amp;#34;34aa387-d-1568eb00&amp;#34;
Accept-Ranges: bytes
Content-Length: 51
Vary: Accept-Encoding
Content-Type: text/plain

Hello World! My content includes a trailing CRLF.
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="sessions"&gt;Sessions&lt;/h3&gt;
&lt;p&gt;A web app might be serving thousands of users, so we need some way for the server to know which user it is talking to. If our app is a todo list, we don&amp;rsquo;t want to be showing Jane&amp;rsquo;s todo items to Fred - each user only wants to see their own items. A common way of doing this is that the browser making requests to the server could send a bit of text along with each request. These little bits of text are called &amp;lsquo;cookies&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;In a very simple example, the cookie could contain the name of our user - for example &amp;lsquo;Fred&amp;rsquo; or &amp;lsquo;Jane&amp;rsquo;. Then when the server received each request, it could read the cookie to know which user was making the request. Here&amp;rsquo;s our code:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#39;express&amp;#39;);

const app = express();

// Route to handle requests
app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; {
 if (req.headers.cookie &amp;amp;&amp;amp; req.headers.cookie.includes(&amp;#39;name=Fred&amp;#39;)) {
 res.send(&amp;#39;Hello Fred!&amp;#39;);
 } else if (req.headers.cookie &amp;amp;&amp;amp; req.headers.cookie.includes(&amp;#39;name=Jane&amp;#39;)) {
 res.send(&amp;#39;Hello Jane!&amp;#39;);
 } else {
 res.send(&amp;#39;Hello stranger!&amp;#39;);
 }
});

// Start the server
const PORT = 3000;
app.listen(PORT, () =&amp;gt; {
 console.log(`Server is running on port ${PORT}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The cookie is just a line of text included in the header of the request. Perhaps the request looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;GET / HTTP/1.1
Accept: application/json, text/plain, */*
Cookie: name=Fred
User-Agent: axios/1.5.1
Accept-Encoding: gzip, compress, deflate, br
Host: 127.0.0.1:3000
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At the user&amp;rsquo;s end the cookie is probably stored in an sqlite database - this implementation detail is left up to the browser. When the users browser sends the request, it checks to see if it&amp;rsquo;s got a cookie for this host and encodes it into the header of the request.&lt;/p&gt;
&lt;h4 id="testing-this-code"&gt;Testing this code&lt;/h4&gt;
&lt;p&gt;There&amp;rsquo;s no simple way to test the server code above since regular browsers don&amp;rsquo;t allow us to set the cookie values. There are however a number of tools that can send customised requests. Some examples of these API testing tools are &lt;a href="https://www.postman.com/"&gt;Postman&lt;/a&gt; and Insomnia. Since the &lt;a href="https://news.ycombinator.com/item?id=37680126"&gt;Insomnia rug-pull&lt;/a&gt;, I&amp;rsquo;ve been a big fan of &lt;a href="https://www.usebruno.com/"&gt;Bruno&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All of these tools allow you to specify the URL, the type of request, and any header or body to go with it. They can make the call to the server and show the results.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/brunoexample.png" width="1000" alt=""&gt;
&lt;h3 id="setting-a-cookie"&gt;Setting a Cookie&lt;/h3&gt;
&lt;p&gt;Our server as it stands at the moment is not very secure. Any hacker can just change the value of the cookie to see the content intended for Fred or Jane. We&amp;rsquo;ll get to authentication eventually, and when we do, we&amp;rsquo;ll need to be able to &lt;em&gt;set&lt;/em&gt; a cookie in the client. How does that work?&lt;/p&gt;
&lt;p&gt;Again, we&amp;rsquo;ll npm install a little library to assist us. &lt;a href="https://github.com/expressjs/cookie-parser#readme"&gt;cookie-parser&lt;/a&gt; is some middleware that lets us easily work with cookies. For the demonstration we&amp;rsquo;ll just add some routes to set the name to &amp;lsquo;Jane&amp;rsquo; or to clear it. Setting it to &amp;lsquo;Jane&amp;rsquo; will look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Route to set a cookie for &amp;#39;Jane&amp;#39;
app.get(&amp;#34;/setuserjane&amp;#34;, (req, res) =&amp;gt; {
 res.cookie(&amp;#34;name&amp;#34;, &amp;#34;Jane&amp;#34;); // Set a cookie named &amp;#39;name&amp;#39; with value &amp;#39;Jane&amp;#39;
 res.send(&amp;#34;Cookie set for Jane&amp;#34;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And clearing it, like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// Route to clear the &amp;#39;name&amp;#39; cookie
app.get(&amp;#34;/clearuser&amp;#34;, (req, res) =&amp;gt; {
 res.clearCookie(&amp;#34;name&amp;#34;);
 res.send(&amp;#34;Cookie cleared&amp;#34;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And since we&amp;rsquo;re using cookie-parser, we may as well use it for reading the cookie to tidy things up a bit as well&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#34;express&amp;#34;);
const cookieParser = require(&amp;#34;cookie-parser&amp;#34;);

const app = express();

// cookie middleware
app.use(cookieParser());

app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 if (req.cookies.name === &amp;#34;Fred&amp;#34;) {
 res.send(&amp;#34;Hello Fred!&amp;#34;);
 } else if (req.cookies.name === &amp;#34;Jane&amp;#34;) {
 res.send(&amp;#34;Hello Jane!&amp;#34;);
 } else {
 res.send(&amp;#34;Hello stranger!&amp;#34;);
 }
});

// Route to set a cookie for &amp;#39;Jane&amp;#39;
app.get(&amp;#34;/setuserjane&amp;#34;, (req, res) =&amp;gt; {
 res.cookie(&amp;#34;name&amp;#34;, &amp;#34;Jane&amp;#34;);
 res.send(&amp;#34;Cookie set for Jane&amp;#34;);
});

// Route to clear the &amp;#39;name&amp;#39; cookie
app.get(&amp;#34;/clearuser&amp;#34;, (req, res) =&amp;gt; {
 res.clearCookie(&amp;#34;name&amp;#34;);
 res.send(&amp;#34;Cookie cleared&amp;#34;);
});

// Start the server
const PORT = 3000;
app.listen(PORT, () =&amp;gt; {
 console.log(`Server is running on port ${PORT}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With this code, we can just use a regular browser for testing. Visiting &lt;code&gt;127.0.0.1:3000/clearuser&lt;/code&gt; will delete the &lt;code&gt;name&lt;/code&gt; cookie, which we could test by visiting &lt;code&gt;127.0.0.1:3000&lt;/code&gt; and getting the &amp;ldquo;Hello stranger!&amp;rdquo; message. If we then go to &lt;code&gt;127.0.0.1:3000/setuserjane&lt;/code&gt; and back to &lt;code&gt;127.0.0.1:3000&lt;/code&gt; we&amp;rsquo;ll see &amp;ldquo;Hello Jane!&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="session-id"&gt;Session ID&lt;/h3&gt;
&lt;p&gt;Clearly this setup is still insecure since a hacker can easily just include a name cookie to pretend to be any particular user. A better system would be to store a unique ID in the cookie, then match that internally to a particular user. This means we&amp;rsquo;d have to maintain the links between each GUID and user on the server, but it would massively reduce the chance of a hacker being able to pretend to be a particular user since the chance of correctly guessing a GUID would be very low.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s think about what we&amp;rsquo;d need to do to make this work for /setuserjane.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;generate a unique ID&lt;/li&gt;
&lt;li&gt;save that ID along with &amp;lsquo;Jane&amp;rsquo; in the local store&lt;/li&gt;
&lt;li&gt;save the UID to the cookie to go back to the browser&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/setuserjane&amp;#34;, (req, res) =&amp;gt; {
 const sessionId = uuidv4(); // Generate a new GUID
 sessions.push({ sessionId, name: &amp;#34;Jane&amp;#34; });
 res.cookie(&amp;#34;sessionId&amp;#34;, sessionId);
 res.send(&amp;#34;Session set for Jane&amp;#34;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then when we needed to check who the user was at a route, we&amp;rsquo;d need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;extract the session ID from the cookie if there is one&lt;/li&gt;
&lt;li&gt;look it up in the server&amp;rsquo;s session store&lt;/li&gt;
&lt;li&gt;use that to identify the name&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 const sessionId = req.cookies.sessionId;
 const session = sessions.find(s =&amp;gt; s.sessionId === sessionId);

 if (session) {
 res.send(`Hello ${session.name}!`);
 } else {
 res.send(&amp;#34;Hello stranger!&amp;#34;);
 }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&amp;rsquo;s the whole thing. The store of session id:name keypairs is just an array of objects (so it will be wiped on every server restart), and we&amp;rsquo;re using the uuid library to generate globally unique ids.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#34;express&amp;#34;);
const cookieParser = require(&amp;#34;cookie-parser&amp;#34;);
const { v4: uuidv4 } = require(&amp;#34;uuid&amp;#34;);

const app = express();

// cookie middleware
app.use(cookieParser());

// Array to store session objects
const sessions = [];

app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 const sessionId = req.cookies.sessionId;
 const session = sessions.find(s =&amp;gt; s.sessionId === sessionId);

 if (session) {
 res.send(`Hello ${session.name}!`);
 } else {
 res.send(&amp;#34;Hello stranger!&amp;#34;);
 }
});

// Route to set a session for &amp;#39;Jane&amp;#39;
app.get(&amp;#34;/setuserjane&amp;#34;, (req, res) =&amp;gt; {
 const sessionId = uuidv4(); // Generate a new GUID
 sessions.push({ sessionId, name: &amp;#34;Jane&amp;#34; });
 res.cookie(&amp;#34;sessionId&amp;#34;, sessionId);
 res.send(&amp;#34;Session set for Jane&amp;#34;);
});

// Route to clear the session
app.get(&amp;#34;/clearuser&amp;#34;, (req, res) =&amp;gt; {
 const sessionId = req.cookies.sessionId;
 const index = sessions.findIndex(s =&amp;gt; s.sessionId === sessionId);
 if (index !== -1) {
 sessions.splice(index, 1);
 }
 res.clearCookie(&amp;#34;sessionId&amp;#34;);
 res.send(&amp;#34;Session cleared&amp;#34;);
});

// Start the server
const PORT = 3000;
app.listen(PORT, () =&amp;gt; {
 console.log(`Server is running on port ${PORT}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="express-session"&gt;express-session&lt;/h2&gt;
&lt;p&gt;The code above is a great improvement, however in practice, instead of managing session ids ourselves, we&amp;rsquo;d make use of express-session. Although general good practice is to avoid dependencies, when we&amp;rsquo;re working with security related code, it&amp;rsquo;s often advisable to use a trusted library since they will have already dealt with a lot of the edge cases and potential weaknesses.&lt;/p&gt;
&lt;p&gt;This is the case with &lt;code&gt;express-session&lt;/code&gt; which does basically what we have above, but also deals with potential cross-site scripting, regenerates session id&amp;rsquo;s to avoid fixation attacks, and signs the cookies to reduce the chance of session data being tampered with. express-session will also handle the storage for the key value pairs for us.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#34;express&amp;#34;);
const cookieParser = require(&amp;#34;cookie-parser&amp;#34;);
const session = require(&amp;#34;express-session&amp;#34;);

const app = express();

// cookie middleware
app.use(cookieParser());

// session middleware
app.use(
 session({
 secret: &amp;#34;REtKU9xyvahuHGd3&amp;#34;, // Replace with a strong secret key
 resave: false,
 saveUninitialized: true,
 cookie: { secure: false }, // Set to true if using HTTPS
 })
);

app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 if (req.session.name) {
 res.send(`Hello ${req.session.name}!`);
 } else {
 res.send(&amp;#34;Hello stranger!&amp;#34;);
 }
});

// Route to set a session for &amp;#39;Jane&amp;#39;
app.get(&amp;#34;/setuserjane&amp;#34;, (req, res) =&amp;gt; {
 req.session.name = &amp;#34;Jane&amp;#34;;
 res.send(&amp;#34;Session set for Jane&amp;#34;);
});

// Route to clear the session
app.get(&amp;#34;/clearuser&amp;#34;, (req, res) =&amp;gt; {
 req.session.destroy((err) =&amp;gt; {
 if (err) {
 res.send(&amp;#34;Error clearing session&amp;#34;);
 } else {
 res.send(&amp;#34;Session cleared&amp;#34;);
 }
 });
});

// Start the server
const PORT = 3000;
app.listen(PORT, () =&amp;gt; {
 console.log(`Server is running on port ${PORT}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="authentication-flow"&gt;Authentication flow&lt;/h3&gt;
&lt;p&gt;Everyone in the world by now is familiar with having to use a username and password to sign into a web app and use it. If we think about how that is going to work with the session ID, it would be something like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When a user tries to access a route that needs authorisation, we check the session object to see if there&amp;rsquo;s a logged in user attached to it.&lt;/li&gt;
&lt;li&gt;If there is, then the route is served, if not they are redirected to a log in page&lt;/li&gt;
&lt;li&gt;At the log in page, we take a username and password, and check it against an internal store. If they match, we update the session to identify the user&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 if (req.session.name) {
 res.send(`Hello ${req.session.name}!`);
 } else {
 res.render(&amp;#34;login.ejs&amp;#34;);
 }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;m using the EJS templating system for this app because it will be handy for later. I&amp;rsquo;m not going to explain it more here other than to say you can just imagine the above is loading the login form HTML. In fact, it just looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;
 &amp;lt;head&amp;gt;
 &amp;lt;meta charset=&amp;#34;UTF-8&amp;#34; /&amp;gt;
 &amp;lt;meta name=&amp;#34;viewport&amp;#34; content=&amp;#34;width=device-width, initial-scale=1.0&amp;#34; /&amp;gt;
 &amp;lt;title&amp;gt;Login&amp;lt;/title&amp;gt;
 &amp;lt;/head&amp;gt;
 &amp;lt;body&amp;gt;
 &amp;lt;h1&amp;gt;Login&amp;lt;/h1&amp;gt;
 &amp;lt;form action=&amp;#34;/login&amp;#34; method=&amp;#34;post&amp;#34;&amp;gt;
 &amp;lt;div&amp;gt;
 &amp;lt;label for=&amp;#34;username&amp;#34;&amp;gt;Username:&amp;lt;/label&amp;gt;
 &amp;lt;input type=&amp;#34;text&amp;#34; id=&amp;#34;username&amp;#34; name=&amp;#34;username&amp;#34; /&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;div&amp;gt;
 &amp;lt;label for=&amp;#34;password&amp;#34;&amp;gt;Password:&amp;lt;/label&amp;gt;
 &amp;lt;input type=&amp;#34;password&amp;#34; id=&amp;#34;password&amp;#34; name=&amp;#34;password&amp;#34; /&amp;gt;
 &amp;lt;/div&amp;gt;
 &amp;lt;button type=&amp;#34;submit&amp;#34;&amp;gt;Login&amp;lt;/button&amp;gt;
 &amp;lt;/form&amp;gt;
 &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This form posts to the /login route, which looks like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.post(&amp;#34;/login&amp;#34;, (req, res) =&amp;gt; {
 const { username, password } = req.body;
 if (username === &amp;#34;demo&amp;#34; &amp;amp;&amp;amp; password === &amp;#34;password&amp;#34;) {
 req.session.name = username;
 res.send(&amp;#34;Logged in&amp;#34;);
 } else {
 res.send(&amp;#34;Invalid username or password&amp;#34;);
 }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It extracts the user name and password from the body of the request (ie from the form). If they are a match, then it sets &amp;ldquo;name&amp;rdquo; in the session which signifies to the rest of the app that we are validly logged in.&lt;/p&gt;
&lt;p&gt;To log out, we just tell express-session to destroy the session:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/logout&amp;#34;, (req, res) =&amp;gt; {
 req.session.destroy((err) =&amp;gt; {
 if (err) {
 res.send(&amp;#34;Error clearing session&amp;#34;);
 } else {
 res.send(&amp;#34;Session cleared&amp;#34;);
 }
 });
});
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="tidy-up"&gt;Tidy up&lt;/h3&gt;
&lt;p&gt;We just need a bit of refactoring before we move on. Currently our &lt;code&gt;/login&lt;/code&gt; route only allows a single user, and is not great to read, let&amp;rsquo;s change it to:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.post(&amp;#34;/login&amp;#34;, (req, res) =&amp;gt; {
 const { username, password } = req.body;
 if (isValidCredentials(username, password)) {
 req.session.name = username;
 res.send(&amp;#34;Logged in&amp;#34;);
 } else {
 res.send(&amp;#34;Invalid username or password&amp;#34;);
 }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s better, and for the isValidCredentials() we&amp;rsquo;ll check against an array of objects like so:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const validCredentials = [
 { username: &amp;#34;demo&amp;#34;, password: &amp;#34;password&amp;#34; },
 { username: &amp;#34;Jane&amp;#34;, password: &amp;#34;password&amp;#34; },
 { username: &amp;#34;Fred&amp;#34;, password: &amp;#34;password&amp;#34; },
];

function isValidCredentials(username, password) {
 return validCredentials.some(
 (cred) =&amp;gt; cred.username === username &amp;amp;&amp;amp; cred.password === password
 );
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you haven&amp;rsquo;t met the JavaScript &lt;code&gt;.some()&lt;/code&gt; method, it&amp;rsquo;s used to run a callback function against the elements in an array until it returns true or comes to the end of an array.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ve made a few changes, lets revisit the complete server.js code:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// npm install cookie-parser express express-session

const express = require(&amp;#34;express&amp;#34;);
const cookieParser = require(&amp;#34;cookie-parser&amp;#34;);
const session = require(&amp;#34;express-session&amp;#34;);
const bodyParser = require(&amp;#34;body-parser&amp;#34;);

const app = express();

// Set up view engine
app.set(&amp;#34;views&amp;#34;, &amp;#34;views&amp;#34;);
app.set(&amp;#34;view engine&amp;#34;, &amp;#34;ejs&amp;#34;);

app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));

// session middleware
app.use(
 session({
 secret: &amp;#34;REtKU9xyvahuHGd3&amp;#34;, // Replace with a strong secret key
 resave: false,
 saveUninitialized: true,
 cookie: { secure: false }, // Set to true if using HTTPS
 })
);

app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 if (req.session.name) {
 res.send(`Hello ${req.session.name}!`);
 } else {
 res.render(&amp;#34;login.ejs&amp;#34;);
 }
});

// Route to clear the session
app.get(&amp;#34;/logout&amp;#34;, (req, res) =&amp;gt; {
 req.session.destroy((err) =&amp;gt; {
 if (err) {
 res.send(&amp;#34;Error clearing session&amp;#34;);
 } else {
 res.send(&amp;#34;Session cleared&amp;#34;);
 }
 });
});

const validCredentials = [
 { username: &amp;#34;demo&amp;#34;, password: &amp;#34;password&amp;#34; },
 { username: &amp;#34;Jane&amp;#34;, password: &amp;#34;password&amp;#34; },
 { username: &amp;#34;Fred&amp;#34;, password: &amp;#34;password&amp;#34; },
];

function isValidCredentials(username, password) {
 return validCredentials.some(
 (cred) =&amp;gt; cred.username === username &amp;amp;&amp;amp; cred.password === password
 );
}

app.post(&amp;#34;/login&amp;#34;, (req, res) =&amp;gt; {
 const { username, password } = req.body;
 if (isValidCredentials(username, password)) {
 req.session.name = username;
 res.send(&amp;#34;Logged in&amp;#34;);
 } else {
 res.send(&amp;#34;Invalid username or password&amp;#34;);
 }
});

// Start the server
const PORT = 3000;
app.listen(PORT, () =&amp;gt; {
 console.log(`Server is running on port ${PORT}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="plaintext-passwords"&gt;Plaintext passwords&lt;/h4&gt;
&lt;p&gt;It&amp;rsquo;s a bad idea to ever store passwords in plaintext anywhere. A solution for this is to hash the password before storing it, then when we need to test a password a user has entered, we test the hash of the password the user has entered against the hashes we have stored. I&amp;rsquo;m being very casual in my language here - I should probably be saying &lt;em&gt;salting&lt;/em&gt; and &lt;em&gt;hashing&lt;/em&gt;. For the purposes of this discussion the idea is to turn each password into gobbledygook in such a way it&amp;rsquo;s not possible to turn it back into the password.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to use the &lt;a href="https://www.npmjs.com/package/bcrypt"&gt;bcrypt&lt;/a&gt; to do the heavy lifting for us since it&amp;rsquo;s going to be more cryptographically sound than anything we could write.&lt;/p&gt;
&lt;p&gt;The encryption process is resource intensive, so these are going to be async operations.It&amp;rsquo;s a small trade-off for the security we&amp;rsquo;re adding.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const bcrypt = require(&amp;#34;bcrypt&amp;#34;);

const validCredentials = [
 {
 username: &amp;#34;demo&amp;#34;,
 hashedPassword:
 &amp;#34;$2b$10$MYd23sm2O1AuAU1l0sPV7enE.XkJpTYC4fga1Dm8Wx33u/8T.L9HC&amp;#34;,
 },
 {
 username: &amp;#34;Jane&amp;#34;,
 hashedPassword:
 &amp;#34;$2b$10$MYd23sm2O1AuAU1l0sPV7enE.XkJpTYC4fga1Dm8Wx33u/8T.L9HC&amp;#34;,
 },
 {
 username: &amp;#34;Fred&amp;#34;,
 hashedPassword:
 &amp;#34;$2b$10$MYd23sm2O1AuAU1l0sPV7enE.XkJpTYC4fga1Dm8Wx33u/8T.L9HC&amp;#34;,
 },
];

async function isValidCredentials(username, password) {
 const user = validCredentials.find((cred) =&amp;gt; cred.username === username);
 if (!user) return false;
 return await bcrypt.compare(password, user.hashedPassword);
}

app.post(&amp;#34;/login&amp;#34;, async (req, res) =&amp;gt; {
 const { username, password } = req.body;
 if (await isValidCredentials(username, password)) {
 req.session.name = username;
 res.send(&amp;#34;Logged in&amp;#34;);
 } else {
 res.send(&amp;#34;Invalid username or password&amp;#34;);
 }
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Okay, now we have a login system, with safeish password storage and session management so the user doesn&amp;rsquo;t have to log in on every page.&lt;/p&gt;
&lt;h3 id="persisting-sessions"&gt;Persisting sessions&lt;/h3&gt;
&lt;p&gt;One last thing before we wrap up this overly long post. Currently, if Jane is logged in, and the server is rebooted, when she returns, her session will have been eliminated. That&amp;rsquo;s to say, her browser will pass the session id in it&amp;rsquo;s cookie, but the server won&amp;rsquo;t recognise it and will force her to log in again. That&amp;rsquo;s not the end of the world (in fact a future improvement should probably be to expire sessions every now and then) but it would be nicer if the session information survived server reboots.&lt;/p&gt;
&lt;p&gt;By default, &lt;code&gt;express-session&lt;/code&gt; uses a memory store, but this can be swapped out for other types of stores. Frequently, production apps will use a database of some kind to keep the session data, but for a single instance app with a hundred or so users a simpler system is just to use the host file system. Such a thing is built into express-session in the form of &lt;code&gt;session-file-store&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Implementing this is simple, we just need to declare a variable for the class, then include it in our initialisation of the session middleware.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const FileStore = require(&amp;#34;session-file-store&amp;#34;)(session);

const app = express();

// Set up view engine
app.set(&amp;#34;views&amp;#34;, &amp;#34;views&amp;#34;);
app.set(&amp;#34;view engine&amp;#34;, &amp;#34;ejs&amp;#34;);

app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: false }));

// session middleware
app.use(
 session({
 secret: &amp;#34;REtKU9xyvahuHGd3&amp;#34;, // Replace with a strong secret key
 resave: false,
 saveUninitialized: true,
 cookie: { secure: false }, 
 store: new FileStore({logFn: function(){}})
 })
);
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You don&amp;rsquo;t need the business with &lt;code&gt;logFn&lt;/code&gt;, that&amp;rsquo;s just a hack to subdue the logs. Without it, express-session logs an error each time a session id arrives in a cookie and there&amp;rsquo;s no corresponding file for it. That happens all the time when I&amp;rsquo;m developing so I foolishly turn it off.&lt;/p&gt;
&lt;p&gt;Now every time a session is created, it will be stored as a text file of JSON in the sessions directory. When a browser makes a request, the express-session will check for a file matching the session id from the cookie, and load the session data from it if needed.&lt;/p&gt;
&lt;p&gt;Since express-session is now dealing with our cookies, we can eliminate cookie-parser.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s where we&amp;rsquo;re up to:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#34;express&amp;#34;);
const session = require(&amp;#34;express-session&amp;#34;);
const bodyParser = require(&amp;#34;body-parser&amp;#34;);
const bcrypt = require(&amp;#34;bcrypt&amp;#34;);
const FileStore = require(&amp;#34;session-file-store&amp;#34;)(session);

const app = express();

// Set up view engine
app.set(&amp;#34;views&amp;#34;, &amp;#34;views&amp;#34;);
app.set(&amp;#34;view engine&amp;#34;, &amp;#34;ejs&amp;#34;);

app.use(bodyParser.urlencoded({ extended: false }));

// session middleware
app.use(
 session({
 secret: &amp;#34;REtKU9xyvahuHGd3&amp;#34;, // Replace with a strong secret key
 resave: false,
 saveUninitialized: true,
 cookie: { secure: false }, 
 store: new FileStore({logFn: function(){}})
 })
);

app.get(&amp;#34;/&amp;#34;, (req, res) =&amp;gt; {
 if (req.session.name) {
 res.send(`Hello ${req.session.name}!`);
 } else {
 res.render(&amp;#34;login.ejs&amp;#34;);
 }
});

// Route to clear the session
app.get(&amp;#34;/logout&amp;#34;, (req, res) =&amp;gt; {
 req.session.destroy((err) =&amp;gt; {
 if (err) {
 res.send(&amp;#34;Error clearing session&amp;#34;);
 } else {
 res.send(&amp;#34;Session cleared&amp;#34;);
 }
 });
});

const validCredentials = [
 {
 username: &amp;#34;demo&amp;#34;,
 hashedPassword:
 &amp;#34;$2b$10$MYd23sm2O1AuAU1l0sPV7enE.XkJpTYC4fga1Dm8Wx33u/8T.L9HC&amp;#34;,
 },
 {
 username: &amp;#34;Jane&amp;#34;,
 hashedPassword:
 &amp;#34;$2b$10$MYd23sm2O1AuAU1l0sPV7enE.XkJpTYC4fga1Dm8Wx33u/8T.L9HC&amp;#34;,
 },
 {
 username: &amp;#34;Fred&amp;#34;,
 hashedPassword:
 &amp;#34;$2b$10$MYd23sm2O1AuAU1l0sPV7enE.XkJpTYC4fga1Dm8Wx33u/8T.L9HC&amp;#34;,
 },
];

async function isValidCredentials(username, password) {
 const user = validCredentials.find((cred) =&amp;gt; cred.username === username);
 if (!user) return false;
 return await bcrypt.compare(password, user.hashedPassword);
}

app.post(&amp;#34;/login&amp;#34;, async (req, res) =&amp;gt; {
 const { username, password } = req.body;
 if (await isValidCredentials(username, password)) {
 req.session.name = username;
 res.send(&amp;#34;Logged in&amp;#34;);
 } else {
 res.send(&amp;#34;Invalid username or password&amp;#34;);
 }
});

// Start the server
const PORT = 3000;
app.listen(PORT, () =&amp;gt; {
 console.log(`Server is running on port ${PORT}`);
});
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="where-next"&gt;Where next?&lt;/h3&gt;
&lt;p&gt;It&amp;rsquo;s been a bit of a trek to get to this point, so I&amp;rsquo;m winding this up here, and we&amp;rsquo;ll take it to the next level in a future post. Some of the next steps to explore are to move our secrets out of the source file, and to use &lt;a href="https://www.npmjs.com/package/passport"&gt;Passport.js&lt;/a&gt; like the two million other projects who downloaded it this week.&lt;/p&gt;</description></item><item><title>Quick &amp;&amp; Dirty auth with nginx &amp; Node</title><link>https://blog.iankulin.com/quick-dirty-auth-with-nginx-node/</link><pubDate>Fri, 23 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/quick-dirty-auth-with-nginx-node/</guid><description>&lt;p&gt;One of the basic requirements for any serious web app is a proper users/roles/authentication system - but if you&amp;rsquo;re just throwing up a utility of some kind on a public IP for testing, and you don&amp;rsquo;t want it to be abused, then this could be an option. There&amp;rsquo;s a few components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Your app. In this demo it&amp;rsquo;s going to be Node, but it could be Go or whatever your server-side poison is. The app is listening for connections on a non-web port (ie not on 80 or 443), I&amp;rsquo;m going to use the traditional 3000.&lt;/li&gt;
&lt;li&gt;A firewall. That port (in my example 3000) must not be accessible from the internet. It has to be blocked by a firewall.&lt;/li&gt;
&lt;li&gt;A web server (I&amp;rsquo;m using nginx) that enforces basic auth.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I briefly discussed web server basic auth earlier - it&amp;rsquo;s a system built into the web server that requires a log in for a route, and authenticates it against the credentials in a password file (usually named &lt;code&gt;.htpasswrd&lt;/code&gt;) and only serves the content if authenticated.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;re going to complicate that a bit by then inserting the authenticated user name into a header, so that we can access it in our node app. The web server does this as it passes the incoming request to our app in a process called proxy-ing.&lt;/p&gt;
&lt;h3 id="prerequisites"&gt;Prerequisites&lt;/h3&gt;
&lt;p&gt;You&amp;rsquo;re going to need a server, separate to the machine you&amp;rsquo;re using. I&amp;rsquo;m going to use an LXC container on one of my Proxmox servers, but perhaps you&amp;rsquo;re on windows and have a WSL to play with, or you&amp;rsquo;ve perhaps you&amp;rsquo;ve spun up a baby server on Hetzner, Linode or Digital Ocean. What ever floats your boat. You need to be able to set it up and &lt;code&gt;ssh&lt;/code&gt; into it to follow along.&lt;/p&gt;
&lt;p&gt;All my examples are assuming Debian, so that or a Debian based distro like Ubuntu is going to be simplest, but if you&amp;rsquo;re on something with a different package management system, you&amp;rsquo;re probably able to translate things to that.&lt;/p&gt;
&lt;h3 id="install-nginx"&gt;Install nginx&lt;/h3&gt;
&lt;p&gt;To install nginx, we just&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt install nginx
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now if we open the server ip address, we should see the nginx test page:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-3.23.32-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-3.23.32-pm.png" width="800" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re wondering where this page comes from, it&amp;rsquo;s &lt;code&gt;/var/www/html/index.nginx-debian.html&lt;/code&gt;. There&amp;rsquo;s a default nginx site config at &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; that points to it. We&amp;rsquo;ll be playing in there later.&lt;/p&gt;
&lt;h2 id="installing-node"&gt;Installing Node&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt install nodejssudo apt install npm
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is going to install the version of node and npm that are provided by Debian or the Debian related distro you&amp;rsquo;re using, so they won&amp;rsquo;t be the latest and greatest, but they will be stable and bug patched to whatever level your distro maintainers think they should be. You could check with &lt;code&gt;node -v&lt;/code&gt; and &lt;code&gt;npm -v&lt;/code&gt; if you were interested, but we&amp;rsquo;re not using any bleeding edge features here, so whatever you&amp;rsquo;ve got it should be fine. For reverence, I have node v18.19.0, and npm 9.2.0&lt;/p&gt;
&lt;h3 id="the-app"&gt;The App&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re going to create a very basic node/Express server app to run on our server. I&amp;rsquo;m going to remote in with VS Code because that&amp;rsquo;s how I roll this week, but do this however you want. Nano is fine, or maybe you&amp;rsquo;re a vim person. Perhaps for these examples we&amp;rsquo;ll assume you&amp;rsquo;re a sane person near the start of their dev journey and use nano. &lt;code&gt;ssh&lt;/code&gt; to the server, then:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;mkdir appcd appnpm initnpm install expressnano app.js
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, our app code in app.js&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#39;express&amp;#39;);const app = express();const port = 3000;app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; { res.send(&amp;#39;Hello World&amp;#39;);});app.listen(port, () =&amp;gt; { console.log(`Server is listening at http://localhost:${port}`);});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we&amp;rsquo;ve done everything right, once you&amp;rsquo;ve saved that (ctl-O, ctl-X) if we run &lt;code&gt;node app.js&lt;/code&gt; we&amp;rsquo;ll get the message &lt;code&gt;Server is listening at http://localhost:3000&lt;/code&gt; and visiting the IP address of our server with &lt;code&gt;:3000&lt;/code&gt; on the end should get this result:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-3.56.39-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-3.56.39-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="the-firewall"&gt;The Firewall&lt;/h3&gt;
&lt;p&gt;Firewalls are their own big thing that I should write about another time. Suffice to say we&amp;rsquo;re going to make it so outside traffic can&amp;rsquo;t access our app on port 3000 (so we can force them to go through nginx where we authenticate them).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt-get install netfilter-persistentsudo iptables -A INPUT -p tcp --dport 3000 -j DROPsudo netfilter-persistent savesudo netfilter-persistent reload
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now if you start the app again with &lt;code&gt;node app.js&lt;/code&gt; and visit :3000 in the browser, it should eventually just time out because the request is never making it to our app.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.17.42-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.17.42-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="proxy-pass"&gt;Proxy Pass&lt;/h3&gt;
&lt;p&gt;So now that raw access from the network to our app is blocked off, we want to configure nginx to pass any requests to our app. There&amp;rsquo;s a number of good reasons why you should put a web server in front of you apps, but today we&amp;rsquo;re doing it so we can authenticate the users. We&amp;rsquo;ll get to that, but for the moment, we need to edit &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Scroll down till you see the &lt;code&gt;location / {&lt;/code&gt; block. Delete out the contents and replace it with&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;proxy_pass http://localhost:3000;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.49.27-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.49.27-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Then we&amp;rsquo;ll check the configuration is okay, and restart the nginx server.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo nginx -tsudo service nginx restart
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now if our app is running (&lt;code&gt;node app.js&lt;/code&gt;) you should be able to go to the server address (without the :3000) and see the app working again.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.55.54-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-4.55.54-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="credentials"&gt;Credentials&lt;/h3&gt;
&lt;p&gt;Now we need to create a file with our credentials, so nginx can have something to check against. The first web server that I ever used that did this was &lt;a href="https://httpd.apache.org/"&gt;Apache&lt;/a&gt;, and that format has carried forward to be used by nginx. I&amp;rsquo;m mentioning this to explain why I&amp;rsquo;m about to tell you to install some Apache tools.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt install apache2-utilssudo htpasswd -c /etc/nginx/.htpasswd user1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This second command is creating (that&amp;rsquo;s the &lt;code&gt;-c&lt;/code&gt; flag) a text file called &lt;code&gt;.htpasswd&lt;/code&gt; in the &lt;code&gt;/etc/nginx&lt;/code&gt; directory. It doesn&amp;rsquo;t matter that much what it&amp;rsquo;s called or where it is - we&amp;rsquo;re going to specify that later in the nginx conf, but I like to put it somewhere I&amp;rsquo;d probably guess later.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;user1&lt;/code&gt; is just what I&amp;rsquo;ve called this user - it could of course be just about anything. htpasswd will ask you to enter a password for this user, and confirm it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-5.55.14-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-5.55.14-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re curious about how that looks in the file, you can just &lt;code&gt;cat&lt;/code&gt; it out. You won&amp;rsquo;t see the plaintext password, it&amp;rsquo;s been hashed into gooblygook.&lt;/p&gt;
&lt;p&gt;If you want to add more users, go ahead; it&amp;rsquo;s the same command without the &lt;code&gt;-c&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo htpasswd /etc/nginx/.htpasswd ian
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Next, we need to tell nginx to use this. We need to go back to the same spot in the &lt;code&gt;/etc/nginx/sites-available/default&lt;/code&gt; where we added the proxy pass statement. Just &lt;em&gt;above&lt;/em&gt; the proxy statement, add:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;auth_basic &amp;#34;Protected app&amp;#34;;auth_basic_user_file /etc/nginx/.htpasswd;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&amp;ldquo;Protected app&amp;rdquo; is the explanation that should pop up in the modal, and the other directive just tells nginx where to look for the credentials.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-6.09.17-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-6.09.17-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m pretty sure nginx processes these in order, so put the auth_basic directives before the proxy_pass.&lt;/p&gt;
&lt;p&gt;Once that&amp;rsquo;s saved, we&amp;rsquo;ll check the configuration and restart nginx to load it.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ian@ct372-authplay:~$ sudo nginx -tnginx: the configuration file /etc/nginx/nginx.conf syntax is oknginx: configuration file /etc/nginx/nginx.conf test is successfulian@ct372-authplay:~$ sudo service nginx restart
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we go back to the page, it should pop up and ask for the credentials. If you input your credentials it will direct you to the &amp;ldquo;hello world&amp;rdquo; message from our app.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-6.15.55-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-6.15.55-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="accessing-the-user-in-node"&gt;Accessing the user in node&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s all great, but how do we access the authenticated user in our app so we know what content to serve? Nginx knows the username, but our node app does not. To fix that, nginx needs to put it in the header passed to the app. To do this, we need to edit the nginx conf file again to add:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;proxy_set_header X-Username $remote_user;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This takes the user name (in remote_user) and inserts it to the request header.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-7.39.41-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-7.39.41-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;After making this change, we need to restart nginx to pick up the config change again - &lt;code&gt;sudo service nginx restart&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Back in our node app, we need to recover the username from the request header.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; { const username = req.get(&amp;#39;X-Username&amp;#39;); res.send(&amp;#39;Hello &amp;#39;+username);});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-7.51.33-pm.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2024-02-17-at-7.51.33-pm.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In the example above I&amp;rsquo;ve extracted the username in the route - often in my apps I do that in middleware and use it to set some request variables with allowed roles and so on.&lt;/p&gt;
&lt;h3 id="limitations"&gt;Limitations&lt;/h3&gt;
&lt;p&gt;This is not a sophisticated system, here are some shortcomings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The most dangerous thing (although I guess this applies to any auth) is that if you&amp;rsquo;re not securing the web traffic with SSL, the password is transmitted in plaintext across the internet.&lt;/li&gt;
&lt;li&gt;There&amp;rsquo;s no simple way to logout or change the user.&lt;/li&gt;
&lt;li&gt;I entered wrong credentials about twenty times as fast as I could and it never stopped me trying, so a brute force is possible. There are ways of addressing this that I haven&amp;rsquo;t covered here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All in all, this is a handy tool that doesn&amp;rsquo;t require a lot of libraries or setup. It is very simple and doesn&amp;rsquo;t provide any fancy functionality like password resets, but sometimes it&amp;rsquo;s all you need.&lt;/p&gt;
&lt;h4 id="links"&gt;Links&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/"&gt;NGINX basic auth&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><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;pre tabindex="0"&gt;&lt;code&gt;const express = require(&amp;#39;express&amp;#39;);const app = express();const port = 3000;app.get(&amp;#39;/&amp;#39;, (req, res) =&amp;gt; { res.send(&amp;#39;Hello, World!&amp;#39;);});app.listen(port, () =&amp;gt; { console.log(`Server is running at http://localhost:${port}`);});
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;app.use(express.json());
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#39;/adduser&amp;#39;, (req, res) =&amp;gt; { const name = req.query.name; const email = req.query.email;
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;app.post(&amp;#39;/users&amp;#39;, (req, res) =&amp;gt; { const name = req.body.name; const email = req.body.email;
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;const sqlite3 = require(&amp;#39;sqlite3&amp;#39;).verbose();const db = new sqlite3.Database(&amp;#39;db/test.sqlite&amp;#39;);
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&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;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&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;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;// endpoint to add a user record to the users table// expects a name and email in the JSON body of the requestapp.post(&amp;#39;/users&amp;#39;, (req, res) =&amp;gt; { const name = req.body.name; const email = req.body.email; const sql = `INSERT INTO users (name, email) VALUES (&amp;#34;${name}&amp;#34;, &amp;#34;${email}&amp;#34;)`; db.run(sql, function(err) { if (err) { res.status(500).send(err.message); } else { res.status(201).json({ rowid: this.lastID }); } });});
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&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;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;// endpoint to get all users from the users table in the databaseapp.get(&amp;#39;/users&amp;#39;, (req, res) =&amp;gt; { const sql = &amp;#39;SELECT rowid, * FROM users&amp;#39;; db.all(sql, (err, rows) =&amp;gt; { if (err) { res.status(500).send(err.message); } else { res.status(200).send(rows); } });});
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;// endpoint to get a single user from the users table in the database// expects an id in the URLapp.get(&amp;#39;/user/:id&amp;#39;, (req, res) =&amp;gt; { const id = req.params.id; const sql = `SELECT rowid, * FROM users WHERE rowid = ${id}`; db.get(sql, (err, row) =&amp;gt; { if (err) { res.status(500).send(err.message); } else { res.status(200).send(row); } });});
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;// endpoint to update a user record in the users table in the database// expects a name, and email in the JSON body of the request// expects an id in the URLapp.put(&amp;#39;/user/:id&amp;#39;, (req, res) =&amp;gt; { const id = req.params.id; const name = req.body.name; const email = req.body.email; const sql = `UPDATE users SET name = &amp;#34;${name}&amp;#34;, email = &amp;#34;${email}&amp;#34; WHERE rowid = ${id}`; db.run(sql, (err) =&amp;gt; { if (err) { res.status(500).send(err.message); } else { res.status(200).send(&amp;#39;User updated.&amp;#39;); } });});
&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="delete"&gt;Delete&lt;/h4&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;// endpoint to delete a user record from the users table in the database// expects an id in the URL. Doesn&amp;#39;t complain if the id doesn&amp;#39;t exist.app.delete(&amp;#39;/user/:id&amp;#39;, (req, res) =&amp;gt; { const id = req.params.id; const sql = `DELETE FROM users WHERE rowid = ${id}`; db.run(sql, (err) =&amp;gt; { if (err) { res.status(500).send(err.message); } else { res.status(200).send(); } });});
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;// endpoint to add a user record to the users table// expects a name and email in the JSON body of the requestapp.post(&amp;#39;/users&amp;#39;, (req, res) =&amp;gt; { const name = req.body.name; const email = req.body.email; const sql = `INSERT INTO users (name, email) VALUES (?, ?)`; db.run(sql, [name, email], function(err) { if (err) { res.status(500).send(err.message); } else { res.status(201).json({ rowid: this.lastID }); } });});
&lt;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&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;/code&gt;&lt;/pre&gt;&lt;p&gt;Then my original add code:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.post(&amp;#39;/users&amp;#39;, (req, res) =&amp;gt; { const name = req.body.name; const email = req.body.email; const sql = `INSERT INTO users (name, email) VALUES (&amp;#34;${name}&amp;#34;, &amp;#34;${email}&amp;#34;)`; db.run(sql, function(err) { if (err) { res.status(500).send(err.message); } else { res.status(201).json({ rowid: this.lastID }); } });});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;would result in executing this against the database:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&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;/code&gt;&lt;/pre&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;pre tabindex="0"&gt;&lt;code&gt;// endpoint to update a user record in the users table in the database// expects a name, and email in the JSON body of the request// expects an id in the URLapp.put(&amp;#39;/user/:id&amp;#39;, (req, res) =&amp;gt; { const id = req.params.id; const name = req.body.name; const email = req.body.email; const updateSql = `UPDATE users SET name = ?, email = ? WHERE rowid = ?`; db.run(updateSql, [name, email, id], (err) =&amp;gt; { if (err) { res.status(500).send(err.message); } else { const selectSql = `SELECT rowid, * FROM users WHERE rowid = ?`; db.get(selectSql, [id], (err, row) =&amp;gt; { if (err) { res.status(500).send(err.message); } else { res.status(200).json(row); } }); } });});
&lt;/code&gt;&lt;/pre&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>Displaying markdown as HTML</title><link>https://blog.iankulin.com/displaying-markdown-as-html/</link><pubDate>Wed, 08 Nov 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/displaying-markdown-as-html/</guid><description>&lt;p&gt;In the spirit of over-complicating things, when I wanted to collect all the links to the services on my homelab into one place, I decided I needed to write them in markdown, and have them converted on the fly into HTML by a server. Then when I couldn&amp;rsquo;t find exactly what I was after (&lt;a href="http://harpjs.com/"&gt;Harp&lt;/a&gt; was closest) of course, I decided to write it.&lt;/p&gt;
&lt;img src="https://blog.iankulin.com/images/distracted.jpg" width="1000" alt=""&gt;
&lt;h3 id="markdown"&gt;Markdown&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Markdown"&gt;Markdown&lt;/a&gt; has definitely been having it&amp;rsquo;s moment over the last couple of years. It&amp;rsquo;s a simple open format mark-up language that is quite readable in it&amp;rsquo;s source form. Although it&amp;rsquo;s now very fashionable as an input for static site generators, most people will have run in to it when adding simple formatting to forum comments or on instant messaging platforms.&lt;/p&gt;
&lt;p&gt;It supports text formatting such as bold, italic and underlining as well as links, and in some extended versions, tables and so on.&lt;/p&gt;
&lt;h3 id="middleware"&gt;Middleware&lt;/h3&gt;
&lt;p&gt;My plan for tackling this is to have a simple Node.js/Express web server that&amp;rsquo;s serving static files from a public sub-directory. As it receives requests for each file, it checks if it&amp;rsquo;s a markdown file (which normally if served directly to a browser would trigger it to be downloaded instead of displayed). If it is markdown, it&amp;rsquo;s translated into HTML and passed to the browser.&lt;/p&gt;
&lt;p&gt;This is easily accomplished in a simple Express server which has the concept of &amp;lsquo;&lt;a href="https://expressjs.com/en/guide/using-middleware.html"&gt;middleware&lt;/a&gt;&amp;rsquo;. This are just layers of processing that each request goes through. If a layer can deal with a request it does, otherwise it passes it off to the next layer. You&amp;rsquo;ll often see this type of pattern (usually with more layers) in an Express app, where each of the &lt;code&gt;app.use&lt;/code&gt; declarations is another middleware layer:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;const app = express();
app.use(mdParser);
app.use(express.static(publicDirectory));
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this case, &lt;code&gt;mdParser&lt;/code&gt; is my middleware function that checks if a file is markdown, then if it is returns a HTML version of the file to the browser, or if not, just lets the request go through to the next layer. A simple version might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;express&amp;#34;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;path&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;showdown&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;require&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;showdown&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;converter&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;showdown&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Converter&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;publicDirectory&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;public&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;staticRoot&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;join&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;__dirname&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;publicDirectory&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// middleware for processing markdown files
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;function&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mdParser&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;next&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;endsWith&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;.md&amp;#39;&lt;/span&gt;)) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;mdFilePath&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;path&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;join&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;staticRoot&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fs&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;readFile&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;mdFilePath&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;utf8&amp;#39;&lt;/span&gt;, (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;404&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;File not found&amp;#39;&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;htmlContent&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;converter&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;makeHtml&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;send&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;htmlContent&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;next&lt;/span&gt;();
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The actual heavy lifting of converting the markdown into HTML is done in line 20 with a library called &lt;a href="https://showdownjs.com/"&gt;ShowDown&lt;/a&gt;. There are a few of these floating around, I tried &lt;a href="https://marked.js.org/"&gt;Marked&lt;/a&gt; first, but it didn&amp;rsquo;t immediately work how I expected without reading any documentation, so I moved on ¯\_(ツ)_/¯&lt;/p&gt;
&lt;h3 id="templating"&gt;Templating&lt;/h3&gt;
&lt;p&gt;This simple version works - the markdown is correctly displayed in the browser, but there&amp;rsquo;s a couple of things going on that are not great.&lt;/p&gt;
&lt;p&gt;The first is that it&amp;rsquo;s not actually well formed HTML. If we load a markdown file containing this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Test.md

* A sample mark down file
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It looks like this:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://blog.iankulin.com/images/screen-shot-2023-10-22-at-7.42.46-am.png"&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-10-22-at-7.42.46-am.png" width="900" alt=""&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;But if we view the page source, it&amp;rsquo;s this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;h1 id=&amp;#34;testmd&amp;#34;&amp;gt;Test.md&amp;lt;/h1&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;A sample mark down file&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;No &lt;code&gt;DOCTYPE, &amp;lt;html&amp;gt;, &amp;lt;head&amp;gt;&lt;/code&gt; etc. Since forever, browsers have been expected to deal gracefully with malformed HTML, and they generally do, but as someone who still feels bound by the ethics printed on my 1991 &lt;a href="https://www.acs.org.au/content/dam/acs/rules-and-regulations/Code-of-Ethics.pdf"&gt;ACS membership certificate&lt;/a&gt;, I can&amp;rsquo;t accept this low standard.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a second related problem I don&amp;rsquo;t like, that&amp;rsquo;s that the title of this page (displayed in the browser tab, and used if we bookmark the page) is &amp;ldquo;http://127.0.0.1:3000&amp;rdquo; instead of what I would like it to be - probably &amp;ldquo;Test&amp;rdquo;. This is not Showdown&amp;rsquo;s fault, it doesn&amp;rsquo;t really have any way of guessing what we&amp;rsquo;d like for the title.&lt;/p&gt;
&lt;p&gt;As usual, these are a class of problem that&amp;rsquo;s long been solved, in this case with templates. Essentially what I need to do is take the generated (but not correctly formed) HTML output from Showdown, and insert it in the middle of some boilerplate HTML. Perhaps the template could look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;
&amp;lt;head&amp;gt;
 &amp;lt;meta charset=&amp;#34;UTF-8&amp;#34;&amp;gt;
 &amp;lt;title&amp;gt;{{title}}&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
 &amp;lt;main&amp;gt;
 {{content}}
 &amp;lt;/main&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;d put the title I wanted for the page in &lt;code&gt;{{title}}&lt;/code&gt; and the converted markdown into &lt;code&gt;{{content}}&lt;/code&gt;. These double curly braces are a reasonably common convention for templating.&lt;/p&gt;
&lt;p&gt;If I load the template file (which can include all sorts of lovely CSS and JS) into &lt;code&gt;templateData&lt;/code&gt; at start up, I can just use a string replace when I need to serve the file at request time:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;if (useTemplate) {
 // Replace placeholders with title and content 
 const title = path.basename(mdFilePath);
 const templatedHtml = templateData.replace(&amp;#39;{{title}}&amp;#39;,
 title).replace(&amp;#39;{{content}}&amp;#39;,
 htmlContent);
 res.send(templatedHtml);
} else {
 res.send(htmlContent);
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I&amp;rsquo;m just using the file name for the title here, I&amp;rsquo;ll think about &lt;a href="https://github.com/IanKulin/mdserver/issues/1"&gt;how to improve that&lt;/a&gt; in a later installment.&lt;/p&gt;
&lt;p&gt;Now the output looks like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;
&amp;lt;head&amp;gt;
 &amp;lt;meta charset=&amp;#34;UTF-8&amp;#34;&amp;gt;
 &amp;lt;title&amp;gt;test.md&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
 &amp;lt;main&amp;gt;
 &amp;lt;h1 id=&amp;#34;testmd&amp;#34;&amp;gt;Test.md&amp;lt;/h1&amp;gt;
&amp;lt;ul&amp;gt;
&amp;lt;li&amp;gt;A sample mark down file&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
 &amp;lt;/main&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Which, while &lt;a href="https://github.com/IanKulin/mdserver/issues/2"&gt;not well indented&lt;/a&gt;, at least meets the HTML specification.&lt;/p&gt;
&lt;h3 id="done"&gt;Done&lt;/h3&gt;
&lt;p&gt;I really enjoyed making this - it&amp;rsquo;s one of those compact sized projects you can start and finish on a Saturday between house jobs, and although small, it does address a genuine use case - if I&amp;rsquo;d found this when I was searching for something I would have used it as is.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/IanKulin/mdserver/blob/e9a09c4381e9bda373b86701f90cf165ea0d0e7e/server.js"&gt;code&amp;rsquo;s up on github&lt;/a&gt; if you want a look. To make it a finished product it probably needs some hardening. Also, since I need to learn how to build Docker containers, this would be a good project for that, so stand by for a future installment.&lt;/p&gt;</description></item><item><title>Using Node.js to return a static file</title><link>https://blog.iankulin.com/using-node-js-to-return-a-static-file/</link><pubDate>Sun, 02 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.iankulin.com/using-node-js-to-return-a-static-file/</guid><description>&lt;p&gt;As mentioned in the &lt;a href="https://blog.iankulin.com/complicating-the-temperature-api/"&gt;previous post&lt;/a&gt;, stage one is just to return the same static text file, but from the Node server, rather than NGINX. That&amp;rsquo;s non-trivial to a rank beginner since I need to figure out 1) how to serve a static file from Node, and 2) how to configure NGINX to hand off calls to the API to Node. This post will look at both of those, but it&amp;rsquo;s first probably worth just setting out what each of the puzzle pieces are.&lt;/p&gt;
&lt;h3 id="nginx"&gt;NGINX&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.nginx.com/"&gt;NGINX&lt;/a&gt; is a web server - it listens on a port (classically 80 and 443 - http and https) and responds to those requests. Usually by returning some files. However, it can also pass those requests off to something else. This process is called Reverse Proxying. Currently I have NGINX set up to just serve a static text file, but in the change I&amp;rsquo;m proposing, NGINX will pass an API request off to Node.&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/JKxlsvZXG7c?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;h3 id="nodejs"&gt;Node.js&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://nodejs.org/en"&gt;Node&lt;/a&gt; is JavaScript packaged up to run on a server, instead of inside a browser. There&amp;rsquo;s lots of different languages we can write server-side code in, and many have some strengths over Javascript. Part of the motivation for using Node might be that web developers have already invested significantly in learning JavaScript to use on the front-end, so it makes sense to use those same skills on the back end.&lt;/p&gt;
&lt;p&gt;A major difference from some other server-side scripting languages (for example, PHP) is that Node is non-blocking, making use of call-backs to handle events resulting in high performance at scale. It&amp;rsquo;s trivial to write a static web server in Node, but that is to seriously under-use it&amp;rsquo;s capability.&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/jOupHNvDIq8?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;h3 id="expressjs"&gt;Express.js&lt;/h3&gt;
&lt;p&gt;Once you start writing backends in Node, you&amp;rsquo;ll find yourself writing a lot of the same code over and over to achieve some standard things - time for a framework. &lt;a href="https://expressjs.com/"&gt;Express&lt;/a&gt; is one of the most popular web frameworks for writing APIs on Node. Using Express makes that job simpler and leaves you with cleaner, more succinct code. It&amp;rsquo;s can be argued that there are better frameworks, but at around 5 miliion downloads per day, I think we can regard it as a standard approach to the problems it solves.&lt;/p&gt;
&lt;h3 id="serve-a-static-file-from-node"&gt;Serve a static file from Node&lt;/h3&gt;
&lt;p&gt;I said it was trivial. Here&amp;rsquo;s the code, then we&amp;rsquo;ll discuss it:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.iankulin.com/images/screen-shot-2023-06-25-at-8.45.20-am.png" alt="const express = require(&amp;rsquo;express&amp;rsquo;);
const app = express();
const PORT = 3000;
app.get(&amp;quot;/api/gnp_temp.txt&amp;quot;, (req, res) =&amp;gt; {
res.status(200).sendFile(__dirname + &amp;lsquo;/gnp_temp.txt&amp;rsquo;);
});
app.listen(PORT, () =&amp;gt; {console.log(`Listening on port ${PORT}`)});"&gt;&lt;/p&gt;
&lt;p&gt;PORT is the port we&amp;rsquo;re listening on. In this case 3000. So if I open a URL on http://localhost:3000 that request will be handled by this code. The actual work is done in these lines:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;app.get(&amp;#34;/api/gnp_temp.txt&amp;#34;, (req, res) =&amp;gt; {
 res.status(200).sendFile(__dirname + &amp;#39;/gnp_temp.txt&amp;#39;);
});
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;It is only looking for requests to &lt;code&gt;:3000/api/gnp_temp.txt&lt;/code&gt; - everything else is ignored. But if it gets that request, it will return a result status of &lt;code&gt;200&lt;/code&gt; (success) along with the file &lt;code&gt;gnp_temp.txt&lt;/code&gt; from the current directory.&lt;/p&gt;
&lt;p&gt;If you are wondering about setting up the environment to get to the point where you can run and understand this. There are lots of great videos - &lt;a href="https://www.youtube.com/watch?v=SccSCuHhOw0"&gt;Web Dev Simplified&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=pKd0Rpw7O48"&gt;Code with Mosh&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=KNa-wMpry00"&gt;Code with Con&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Now that it Works on My Machine™ I need to figure out how to deploy it.&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>