Skip to content

Commit

Permalink
project
Browse files Browse the repository at this point in the history
  • Loading branch information
btholt committed Aug 12, 2024
1 parent ca799f2 commit 0696fe5
Show file tree
Hide file tree
Showing 7 changed files with 126 additions and 1 deletion.
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
2 changes: 1 addition & 1 deletion lessons/04-intermediate-sql/D-aggregation.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,4 +109,4 @@ HAVING
COUNT(Track.GenreId) > 300;
```

Using HAVING we can filter on the aggregated set.
Using HAVING we can filter on the aggregated set. Keep that mind if you ever need to do that.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
> 🚨 Go clone the example [app repo from GitHub][app]. Once cloned, run `npm install` to install all the necessary files and then run `npm run dev` to start the development server. Please use Node.js version 20+.
We are going to build an app frontend to the Chinook database. Since SQLite is file-based, I went ahead and included a fresh copy for you to use.

This app uses [Fastify][fastify], [HTMX][htmx], and [Handlebars][handlebars] to make an app to render invoices for users. You won't need to touch the HTMX nor Handlebars at all, and you'll only need to write a minimal amount of Fastify.

The only file you'll be working on is invoice.js. This has a bare minimal amount of Fastify code to get you started working on the route. You'll be able to try the frontend code at [http://localhost:8080](http://localhost:8080) and to hit the route directly at [http://localhost:8080/invoice?id=1](http://localhost:8080/invoice?id=1). You do not need to modify any other files.

I have also included my code at invoice-complete.js. You can see the result of that function call at [http://localhost:8080/invoice-complete?id=1](http://localhost:8080/invoice-complete?id=1).

> I would strongly suggest you attempt to write the code yourself first before you look how I did it. I know it can be a struggle but I find that I learn the most in those moments of struggle where I know my destination and I'm unsure of how to navigate it and I have to chart the course myself.
[app]: https://github.com/btholt/sqlite-app
[fastify]: https://fastify.dev/
[htmx]: https://htmx.org/
[handlebars]: https://handlebarsjs.com/
94 changes: 94 additions & 0 deletions lessons/05-build-a-project-with-nodejs-and-sqlite/B-sqlite3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
title: "sqlite3"
---

There are so many libraries for connecting to SQLite. It's a very ubiquitious piece of software and so lots of people have opinions on how to build with it.

I am going to show you the standard one, the first one, the one that everyone knows. I will say that when I build stuff with SQLite, it is _not_ the one I normally choose. However I think it's good for you to get exposure to the OG and then you can branch out and choose other libraries once you know what you're getting away from.

[Click here][sqlite3] to go to the sqlite3 docs.

> Why is it sqlite3? It's because it's for version 3 of SQLite, the third major version.
## Connecting to the database

Connecting to a SQLite database from Node.js is a snap. Import the the sqlite3 library and create a `new` database.

```javascript
import sqlite3 from "sqlite3";

const db = new sqlite3.Database("./my-database.db");
```

Again, keep in mind that SQLite databases are files. You need to give it a file path to be able to open the database and to start writing to it.

## Querying the database

Once you have your `db` Database instance, you can then start running queries. There's a few functions to be aware of. We'll talk about the parameters to the functions after this.

- `db.run(sql, params, callback)` – This will run a query and not care about the results. You generally use this to run `UPDATE` and `DELETE` commands as you're just updating and not necessarily caring about what the database has to say back to you.
- `db.get(sql, params, callback)` – Runs a query and only gives you back the first one. Some times all you need is one result (like when you're querying a unique ID). This simplifies your code a bit because you don't need an array of one result.
- `db.all(sql, params, callback)` – Like get, but it gives you all results back that match the query instead of just one. It always returns an array. If you got no results, you get an empty array
- `db.each(sql, params, callback, complete)` – Like all, but instead of one big array of results, your callback will get called once for each row in the set. Then the complete function will be called to let you know it's done. This is nice if you have some action you want to take on each row as it's basically a `.map()` of the result set instead of an array.

## Node.js style callbacks (a.k.a. "Nodebacks")

Some of you whippersnappers may be too young to remember writing JavaScript before promises, async-await, generators, etc. Before the magical time of ES2015 / ES6, we only had one way to deal with asynchronous code, callbacks. These are just functions that are invoked when something async completes. JS has since long moved past this (nearly a decade as of writing) and it's to the point that it feels weird to write callbacks as we've been writing promises so long.

sqlite3 never updated to use promises so you still have to use Node.js-style callbacks (or Nodebacks, as they were sometimes called.) They are Node.js callbacks as they always have the signature of `function myCallback(error, data)`. If that `error` is populated with anything, it means that something went wrong. Otherwise it succeeded. That's it. That's what makes a "Nodeback".

So with `.all()`, your code will look like this

```javascript
db.rows(
`SELECT * FROM Track WHERE name LIKE '%live%'`,
[], // we'll talk about this array in a sec
function (err, rows) {
if (err) {
// do error stuff here
return;
}

// do stuff here with rows
}
);
```

## Parameters and SQL Injection

Let's take this example:

```javascript
const id = getIdFromUser();
db.get(`SELECT * FROM Track WHERE TrackId=${id}`, [], function (err, row) {
// do stuff
});
```

What's wrong with this? There's a **major** problem with this. If that id is coming from a user, it means they can put _anything_ there, right? So they could in theory put valid SQL in there. What if they put `15; DROP TABLE Customer`? Then your SQL statement would be `SELECT * FROM Track WHERE id=15; DROP TABLE Customer`. Uh oh, good bye to all your customer data. They could also do even more nefarious things like steal your data. This is called SQL injection. This is why you **never, ever, ever, ever** put user input directly into a SQL statement. You _always_ let the library do it for you. No exceptions.

So how do we fix this? sqlite3 gives us that array to handle just that.

```javascript
const id = getIdFromUser();
db.get(`SELECT * FROM Track WHERE TrackId=?`, [id], function (err, row) {
// do stuff
});
```

Done! By replacing it with the question mark and providing the user input in the array, sqlite3 will guarantee you that it's safe to use that no matter where it came from. You can also use an object notation.

```javascript
const id = getIdFromUser();
db.get(
`SELECT * FROM Track WHERE TrackId=$id`,
{ $id: id },
function (err, row) {
// do stuff
}
);
```

Both are fine. Question marks rely on order, objection notation relies on names matching.

This should be enough of an intro for you to write your sample app.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
title: "Alternatives to sqlite3"
---
4 changes: 4 additions & 0 deletions lessons/05-build-a-project-with-nodejs-and-sqlite/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"title": "Build a Project with Node.js and SQLite",
"icon": "circle-nodes"
}

0 comments on commit 0696fe5

Please sign in to comment.