Learn how to use the SQLite database to persist data for your Node apps
mkdir learn-database
cd learn-database
npm init -ynpm install better-sqlite3const Database = require("better-sqlite3");
const db = new Database();
console.log(db);node database/db.jsconst db = new Database("db.sqlite");const db = new Database(process.env.DB_FILE);DB_FILE=db.sqlite node database/db.jsconst select_date = db.prepare("SELECT DATE()");
console.log(select_date);const select_date = db.prepare("SELECT DATE()");
const result = select_date.get();
console.log(result);{ "DATE()": "2022-09-13" }BEGIN;
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
COMMIT;const { readFileSync } = require("node:fs");
const { join } = require("node:path");
const Database = require("better-sqlite3");
const db = new Database(process.env.DB_FILE);
const schemaPath = join("database", "schema.sql");
const schema = readFileSync(schemaPath, "utf-8");
db.exec(schema);const select_table = db.prepare("SELECT name FROM sqlite_schema");
const result = select_table.all();
console.log(result);[ { name: 'tasks' }, ... ]const { readFileSync } = require("node:fs");
const { join } = require("node:path");
const Database = require("better-sqlite3");
/**
* If we do not set DB_FILE env var creates an in-memory temp DB.
* Otherwise connect to the DB contained in the file we specified (if it exists).
* If it does not exist create a new DB file and connect to it.
*/
const db = new Database(process.env.DB_FILE);
/**
* Make sure DB has the right structure by running schema.sql
* This is responsible for creating the tables and columns we need
* It should be safe to run every time
*/
const schemaPath = join("database", "schema.sql");
const schema = readFileSync(schemaPath, "utf-8");
db.exec(schema);
/**
* Export the DB for use in other files
*/
module.exports = db;const db = require("../database/db.js");
const insert_task = db.prepare("INSERT INTO tasks (content) VALUES (?)");
function createTask(content) {
insert_task.run(content);
}
module.exports = { createTask };// ...
createTask("Eat a banana");
const tasks = db.prepare("SELECT * FROM tasks").all();
console.log(tasks);[{ "id": 1, "content": "Eat a banana", "created_at": "2022-09-14 08:40:41" }]const insert_task = db.prepare(`
INSERT INTO tasks (content)
VALUES (?)
RETURNING id, content, created_at
`);
function createTask(content) {
return insert_task.get(content);
}const result = createTask("Send mum flowers");
console.log(result);{ "id": 2, "content": "Send mum flowers", "created_at": "2022-09-14 08:52:30" }BEGIN;
INSERT INTO tasks VALUES
(1, 'Create my first todo', '2022-09-16 01:01:01'),
(2, 'Buy milk', '2022-09-16 11:10:07'),
(3, 'Become a 10x developer', '2022-09-16 23:59:59')
ON CONFLICT(id) DO NOTHING;
COMMIT;const { readFileSync } = require("node:fs");
const { join } = require("node:path");
const db = require("./db.js");
const seedPath = join("database", "seed.sql");
const seed = readFileSync(seedPath, "utf-8");
db.exec(seed);
console.log("DB seeded with example data");{
"scripts": {
"seed": "DB_FILE=db.sqlite node database/seed.js"
}
}CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
complete INTEGER DEFAULT 0 CHECK(complete IN (0, 1))
);INSERT INTO tasks VALUES
(1, 'Create my first todo', '2022-09-16 01:01:01', 1),
(2, 'Buy milk', '2022-09-16 11:10:07', 0),
(3, 'Become a 10x developer', '2022-09-16 23:59:59', 1)
ON CONFLICT(id) DO NOTHING;const insert_task = db.prepare(`
INSERT INTO tasks (content, complete)
VALUES (?, ?)
RETURNING id, content, created_at
`);
function createTask(content, complete) {
return insert_task.get(content, complete);
}const insert_task = db.prepare(`
INSERT INTO tasks (content, complete)
VALUES ($content, $complete)
RETURNING id, content, created_at
`);
function createTask(task) {
return insert_task.get(task);
}const select_tasks = db.prepare(/*sql*/ `
SELECT id, content, created_at, complete FROM tasks
`);
function listTasks() {
return select_tasks.all();
}const select_tasks = db.prepare(/*sql*/ `
SELECT
id,
content,
TIME(created_at),
complete
FROM tasks
`);[
{
"id": 2,
"content": "Send mum flowers",
"TIME(created_at)": "08:52:30",
"complete": 0
}
]const select_tasks = db.prepare(/*sql*/ `
SELECT
id,
content,
TIME(created_at) AS created_at,
complete
FROM tasks
`);[
{
"id": 2,
"content": "Send mum flowers",
"created_at": "08:52:30",
"complete": 0
}
]const delete_task = db.prepare(/*sql*/ `
DELETE FROM tasks WHERE id = ?
`);
function removeTask(id) {
delete_task.run(id);
}const test = require("node:test");
const assert = require("node:assert");
const model = require("../model/tasks.js");
const db = require("../database/db.js");
// Delete all tasks and reset ID counter
function reset() {
db.exec(/*sql*/ `
DELETE FROM tasks;
DELETE FROM sqlite_sequence WHERE name='tasks';
`);
}
test("can create, remove & list tasks", () => {
reset();
const task = model.createTask({ content: "test task", complete: 0 });
assert.equal(task.id, 1);
assert.equal(task.content, "test task");
model.removeTask(task.id);
const tasks = model.listTasks();
assert.equal(tasks.length, 0);
});DB_FILE=test.sqlite node test/tasks.test.jsDB_FILE=test.sqlite node -r ./database/seed.js test/tasks.test.jsconst update_content = db.prepare(/*sql*/ `
UPDATE tasks
SET content = $content
WHERE id = $id
RETURNING id, content, created_at, complete
`);
function editTask(task) {
return update_content.get(task);
}test("can update a task", () => {
reset();
const task = model.createTask({ content: "test task", complete: 0 });
const updated = model.editTask({ id: 1, content: "this is updated" });
assert.equal(updated.id, 1);
assert.equal(updated.content, "this is updated");
});const update_complete = db.prepare(/*sql*/ `
UPDATE tasks
SET complete = NOT complete
WHERE id = ?
RETURNING id, content, created_at, complete
`);
function toggleTask(id) {
return update_complete.get(id);
}test("can complete a task", () => {
reset();
const task = model.createTask({ content: "test task", complete: 0 });
const updated = model.toggleTask(1);
assert.equal(updated.complete, 1);
});npm install expressconst express = require("express");
const server = express();
server.get("/", (req, res) => {
res.send("hello world");
});
module.exports = server;const server = require("./server.js");
const PORT = process.env.PORT || 3333;
server.listen(PORT, () => console.log(`Listening on http://localhost:${PORT}`));server.get("/", (req, res) => {
const body = /*html*/ `
<!doctype html>
<form method="POST">
<input id="content" name="content" aria-label="New task" required>
<button>Add task +</button>
</form>
`;
res.send(body);
});const model = require("./model/tasks.js");
server.post("/", express.urlencoded({ extended: false }), (req, res) => {
const task = {
content: req.body.content,
complete: 0,
};
model.createTask(task);
res.redirect("/");
});DB_FILE=db.sqlite node index.js{
"scripts": {
"dev": "DB_FILE=db.sqlite node index.js"
}
}server.get("/", (req, res) => {
const tasks = model.listTasks();
const body = /*html*/ `
<!doctype html>
<form method="POST">
<input id="content" name="content" aria-label="New task" required>
<button>Add task +</button>
</form>
<ul>${tasks.map((t) => `<li>${t.content}</li>`).join("")}</ul>
`;
res.send(body);
});server.post("/update", express.urlencoded({ extended: false }), (req, res) => {
const { action, id } = req.body;
if (action === "remove") model.removeTask(id);
if (action === "toggle") model.toggleTask(id);
res.redirect("/");
});function Task(task) {
return /*html*/ `
<li>
<form method="POST" action="/update">
<input type="hidden" name="id" value="${task.id}">
<button name="action" value="toggle" aria-label="Toggle complete">
${task.complete ? "☑︎" : "☐"}
</button>
<span style="${task.complete ? "text-decoration: line-through" : ""}">
${task.content}
</span>
<button name="action" value="remove">×</button>
</form>
</li>
`;
}
server.get("/", (req, res) => {
const tasks = model.listTasks();
const list = tasks.map(Task);
const body = /*html*/ `
<!doctype html>
<form method="POST">
<input id="content" name="content" aria-label="New task" required>
<button>Add task +</button>
</form>
<ul>${list.join("")}</ul>
`;
res.send(body);
});id=1&action=toggleid=1&action=remove