おさらい
データベース
なぜデータベースが必要なのか
- データの永続化をしたい
- データの整合性を保ちたい
- データの検索を効率化したい
別にファイルとして保存すればよくない?
- ファイルだとデータの整合性が取れない(多重書き込みなど)
- DBはトランザクションという排他制御の仕組みを持っている
- データの検索がしにくい
- DBはインデックスキーやハッシュテーブルなど、高速に検索ができる仕組みを持っている
- ファイルだと全てのデータを読み込んでから検索する必要があるため遅い
どんな種類があるの?
- リレーショナルデータベース(RDB)
- ファイルベース
- SQLite
- サーバーベース
- MySQL, PostgreSQL
- ファイルベース
他にも
- Key-Value Store
- Redis
- Cloudflare Workers KV
- Vector DB
- Document Store
...など
どうやって使うの?
- 基本的にSQL(Structured Query Language)という言語を使って操作する
- データベースに接続するためのライブラリを使ってプログラムからSQLをDBに送って操作する
何に使われてるの?
- Webアプリケーション
- ゲームサーバー
- ビックデータ解析
- スマホのデータ保存
少しでも「データ」を扱う場面であれば必ずと言っていいほどデータベースが使われています。 決してWebだけの技術というわけではありませんのでこの機会に慣れておくといいでしょう。
SQL
SQLとは
- データベースを操作するための言語
- データの操作(追加、更新、削除、取得)ができる
- テーブルの作成や削除もできる
なぜSQLが必要なのか
サーバーはJavaScriptのほかに、GoやPythonなどの言語でも書くことができます。 例えば、JavaScriptだけがDBに直接命令をできるとしたら、他の言語で書かれたサーバーからはその命令を使うことはできませんよね? つまり、データベースにアクセスし操作するための共通の中間言語が必要なのです。
今回は紹介しませんが、SQLを書かなくてもうまくデータベースを操作できるライブラリもありますが、裏側では基本的にはSQLが使われています。
SQLの基本
前回の講義で学習したSQL文を復習してみましょう。詳しくは【第8回】データベース入門(1)を参照してください。
テーブル操作
CREATE TABLE
でテーブルを作成DROP TABLE
でテーブルを削除
レコード操作
SELECT
でデータを取得INSERT INTO
でデータを追加UPDATE
でデータを更新DELETE
でデータを削除
レコードの操作である、CREATE、READ、UPDATE、DELETEの頭文字を取ってCRUDと呼ぶことがあります。
SQLが何をするかイメージしてみましょう。次のSQLは何をしそうだと思いますか?
CREATE TABLE users (
id INT PRIMARY KEY,
name TEXT,
age INT
);
CREATE TABLE
でusers
というテーブルを作成し、そのテーブルにはid
、name
、age
というカラムがあるんだなーくらいのイメージが湧いたらOKです。
次はどうでしょう?
SELECT * FROM users;
users
テーブルから全てのデータを取得するんだなーくらいのイメージが湧いたらOKです。
最後にこれは?
INSERT INTO users (id, name, age) VALUES (1, 'Alice', 20);
users
テーブルにid
が1
、name
がAlice
、age
が20
のデータを追加するんだなーくらいのイメージが湧いたらOKです。
プログラム(Node.js)からデータベースを操作する
おさらいの通り、今回はプログラムからデータベースを操作することを学びます。
そのまま自力でデータベースに接続するのは難しいので、better-sqlite3
というライブラリを使ってSQLite3のデータベースをプログラムから操作してみましょう。
better-sqlite3 のインストール
3回前の講義から使い続けているsimple-todo-app
フォルダへ移動してください。
Node.jsのプロジェクトにライブラリを追加する方法はnpm install
コマンドでしたね。これを使ってbetter-sqlite3
をインストールしましょう。
npm install better-sqlite3
もしうまくできてるか不安な場合は、package.json
を確認してみましょう。
cat package.json
をして、dependencies
にbetter-sqlite3
が追加されていればOKです!
もしGit
を使っている場合は
git diff
とすることで前回のコミットとの比較により、package.json
に変更があったか確認するという方法もあります。
gitignore を使おう
これからデータベースファイルを作成しますが、データベースに入っているデータにもし個人情報などが含まれていた場合、パブリックリポジトリならそのデータが全世界に公開されてしまいますし、プライベートリポジトリでもそもそもGitHub社にデータを公開していることになるので問題です。
その後消すコミットをしても、Gitは履歴を保存するソフトウェアですからコミットを辿ればデータにアクセスできてしまいます。
そのような場合は、そもそも最初からデータベースファイルをGitで管理しないようにするために.gitignore
ファイルを使いましょう。
.gitignore
ファイルはGitで管理されるファイルの中で、Gitで管理しないファイルを指定するためのファイルです。
simple-todo-app
フォルダに.gitignore
ファイルを作成して、以下のように書いてください。
node_modules/
*.db
これはnode_modules/
ディレクトリと.db
拡張子のファイルをGitで管理しないようにするための設定です。
node_modules/
ディレクトリはライブラリをインストールしたときに作成されるディレクトリで、ライブラリのインストールは各PCがやるべきなのでGitで管理する必要がないです。
逆にGitで管理してしまうと重すぎて大変になります。
そしたらこれをGitにコミットしておきましょう。
git add .gitignore
git commit -m ".gitignoreを追加"
これでデータベースファイルがGitで管理されないようになりました。
また、前々回の講義でnode_modules
を.gitignore
する指示を出し忘れていたので、こちらのコマンドを実行してGitからnode_modules
を削除しておいてください。
git rm -r --cached node_modules
git push -f
git push -f
はリモートリポジトリを作成していれば (GitHub に上げていれば) 行なってください。
プログラムからDBを作る
control-db.js
というファイルをそのまま今いるフォルダ(simple-todo-app
直下)に作成して、以下のコードを書いてください。
import Database from 'better-sqlite3';
const db = new Database('webken-9.db');
先ほどインストールしたbetter-sqlite3
ライブラリから、Database
クラスをインポートして、webken-9.db
というファイルを作成してデータベースとして扱うためのdb
という変数を作成しています。
JS文法Tips - import
import
はES Modulesという標準的なモジュールシステムを使って他のファイルから関数やクラスを読み込むための文法です。
import
にはよく使う2つの書き方があります。
-
デフォルトインポート
デフォルトエクスポートされた関数やクラスを読み込むときに使います。
some-module.jsconst hello = () => { console.log('Hello'); }; export default hello;
main.jsimport hello from 'some-module'; hello(); // Hello と表示される
-
名前付きインポート
名前付きエクスポートされた関数やクラスを読み込むときに使います。
some-module.jsexport const hello = () => { console.log('Hello'); };
main.jsimport { hello } from 'some-module'; hello(); // Hello と表示される
Maximumは名前付きインポート勢が多いです。パフォーマンス的にも名前付きインポートの方がいい、ただしデフォルトインポートの方が簡潔だし統一性があるというメリットもあります。場合によって使い分けましょう。
詳しく説明すると挙動が全然違うので省略しますが、今回のbetter-sqlite3
ライブラリはデフォルトインポートを使っているので、Database
クラスをそのままimport
しています。
このファイルを実行してみましょう。
node control-db.js
エラーが出なければ成功です。webken-9.db
というファイルが作成されているか確認してみてください。
できてたら次に進みましょう。
プログラムからテーブルを作る
そしたら、前回の講義でやったテーブル作成のSQL文をプログラムから実行してみましょう。
SQL文を実行するには文字列としてSQL文を書いて、db.exec()
メソッドに渡すだけです。
import Database from 'better-sqlite3';
const createTweetTableQuery = `
CREATE TABLE tweets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
created_at TEXT NOT NULL
);
`;
const db = new Database('webken-9.db');
db.exec(createTweetTableQuery);
JS文法Tips - 改行を含む文字列
JSで複数行の文字列を書くには、バッククォート(`
)を使います。これはテンプレートリテラルと呼ばれる文法です。
const str = `
こんにちは
こんばんは
おはよう
`;
これは次のように解釈されます。
const str = "\nこんにちは\nこんばんは\nおはよう\n";
一番最初と最後の改行が邪魔ですね。これを取り除くにはtrim()
メソッドを使います。
発展JS文法Tips - trim()
trim()
はテンプレートリテラル構文のものではなく、文字列に対して使えるメソッドです。
もっと詳しくいうと、String.prototype.trim()
という文字列オブジェクトのプロトタイプに生えているメソッドです。
const str = `
こんにちは
こんばんは
おはよう
`.trim();
これで改行が取り除かれます。
const str = "こんにちは\nこんばんは\nおはよう";
と解釈されることでしょう。
では、このファイルを実行してみましょう。
node control-db.js
エラーが出なければ成功です。
ちゃんとtweets
テーブルが作成されているかSQLite3のコマンドラインツールを使って確認してみましょう。
sqlite3 webken-9.db
.tables
とすると、tweets
テーブルが作成されていることが確認できるはずです。
プログラムからデータを挿入する
悪い例を紹介するのでこれを書く必要はありません
次に、前回の講義でやったデータの挿入をプログラムから行ってみましょう。
データの挿入はINSERT INTO
文でしたね。これも文字列として書いて、db.exec()
メソッドに渡すだけです。
INSERT INTO tweets (content, created_at) VALUES ('今日はいい天気ですね', '2024-07-01 12:00:00');
INSERT INTO tweets (content, created_at) VALUES ('おなかがすいたなー', '2024-07-01 12:30:00');
ただ、これをINSERT
毎に書いていたら10000件とか挿入したくなったときに大変ですよね。
ということでこのINSERT
文を作る関数を作る工夫をしてみます。
具体的にはこういう関数です。(まだ書く必要はありません)
const insertTweet = (content, createdAt) => {
return `INSERT INTO tweets (content, created_at) VALUES ('${content}', '${createdAt}');`
};
こうすれば
console.log(insertTweet('今日はいい天気ですね', '2024-07-01 12:00:00'));
// INSERT INTO tweets (content, created_at) VALUES ('今日はいい天気ですね', '2024-07-01 12:00:00');
というように引数に好きな値を渡すだけでINSERT
文を作成して実行できます。
SQLインジェクションとプレイスホルダ
ちょっと待って!
このままだと、SQLインジェクションという脆弱性が発生してしまいます。
SQLインジェクションとは、ユーザーが入力した値をそのままSQL文に埋め込んでしまうことで、意図しないSQL文が実行されてしまう脆弱性のことです。
試しに、content
に'); DROP TABLE tweets; --
という文字列を入れてみましょう。
insertTweet("'); DROP TABLE tweets; --", '2024-07-01 12:00:00');
これを実行すると...
INSERT INTO tweets (content, created_at) VALUES(''); DROP TABLE tweets; --', '2024-07-01 12:00:00');
なんとtweets
テーブルが削除されてしまいます!
INSERT文の第一引数に'); DROP TABLE tweets; --
という文字列を渡すことで、INSERTが終わった後にDROP TABLE
を実行するクエリを作れてしまいました。(--
はそれ以降コメントアウトの記号です)
任意の値を埋め込めるということは、任意のSQL文を実行できてしまうということなのです。
この脆弱性発生を防ぐために色々な対策がありますが、その中でも一番簡単で効果的なのがプレイスホルダです。
プレイスホルダとは、SQL文の中に?
という記号を使って、その後に渡す値を埋め込むという仕組みです。
この話は一応SQLite3もといSQLの話なのですが、前回紹介されていなかったのでここで紹介します。
better-sqlite3
では、db.prepare()
メソッドを使ってプレイスホルダを使ったSQL文を実行できます。
const insertTweetQuery = db.prepare(`
INSERT INTO tweets (content, created_at) VALUES (?, ?);
`);
insertTweetQuery.run('今日はいい天気ですね', '2024-07-01 12:00:00');
このように、?
を使ってSQL文を書いて、run()
メソッドに渡すことで、その?
に対応する値を埋め込んで実行してくれます。
こうすれば、ユーザーが入力した値をそのままSQL文に埋め込むことがなくなるので、SQLインジェクションの脆弱性を防ぐことができます。
基本的にdb.prepare()
メソッドを使ってSQL文を書きましょう。
(なるべくdb.exec()
は使わないようにしましょう。わざと危険性を認識してもらうために使いました)
プログラムから色々操作を試してみる
後の操作はほぼ同じなので、一気に動かしてみましょう。
import Database from 'better-sqlite3';
const db = new Database('webken-9.db');
const createTweetTableQuery = db.prepare(`
CREATE TABLE tweets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
created_at TEXT NOT NULL
);
`);
createTweetTableQuery.run();
const insertTweetQuery = db.prepare(`
INSERT INTO tweets (content, created_at) VALUES (?, ?);
`);
const getTweetsQuery = db.prepare(`
SELECT * FROM tweets;
`);
const deleteTweetQuery = db.prepare(`
DELETE FROM tweets WHERE id = ?;
`);
insertTweetQuery.run('今日はいい天気ですね', '2024-07-01 12:00:00');
insertTweetQuery.run('おなかがすいたなー', '2024-07-01 12:30:00');
console.log(getTweetsQuery.all());
deleteTweetQuery.run(1);
console.log(getTweetsQuery.all());
かなりファイルに変更があるのでコピーして全て上書きすることをおすすめします。
大変分かりづらくて申し訳ないんですが、INSERT
文などをprepare
するとき、操作先のテーブル(今回はtweets
テーブル)が存在しないとエラーが出ます。
createTweetTableQuery.run()
はなるべく最初に実行するようにしましょう!
このファイルを実行してみましょう。
node control-db.js
エラーが出なければ成功です。もしSqliteError: table tweets already exists
的なエラーが出た場合は、webken-9.db
を削除してから再度実行してみてください。
tweets
テーブルにデータが挿入されているか、SELECT
文で取得できているか、DELETE
文で削除できているか確認してみてください。
流れとしては
tweets
テーブルを作成- データを挿入 (
id
がAUTOINCREMENTなので1
と2
が付与される) - データを取得 (
id
が1
と2
のデータが取得される) id
が1
のデータを削除- データを取得 (
id
が2
のデータだけが取得される)
という流れになります。
つまり
sqlite3 webken-9.db
SELECT * FROM tweets;
を実行して、id
が2
のデータだけが取得できていればOKです。
このファイルcontrol-db.js
はデモ用に作ったファイルですので今後使いません、消していただいても結構です。もし取っておきたい場合はGitにコミットしておくといいでしょう。
git add control-db.js
git commit -m "Add control-db.js"
simple-todo-app にデータベースを導入する
それでは、ついにsimple-todo-app
にデータベースを導入していきましょう。
移行コードが長いので一部しか載せていません。最後に全て置き換えたコードを載せておくのでわからなくなったらそっちを見てください。
データベースファイルを作成する
上の方でまずDBを初期化しましょう。今回はtodo.db
というファイル名でDBを作成します。
import Database from 'better-sqlite3';
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { cors } from "hono/cors";
const app = new Hono();
const db = new Database('todo.db');
app.use(cors());
const todoList = [
{ title: "JavaScriptを勉強する", completed: false },
server.js
の冒頭にbetter-sqlite3
をインポートして、todo.db
というファイルを作成してデータベースとして扱うためのdb
という変数を作成しています。
テーブルを作成する
次に、todo
テーブルを作成するSQL文を書いて、db.exec()
メソッドで実行します。
import Database from 'better-sqlite3';
import { serve } from "@hono/node-server";
import { Hono } from "hono";
const app = new Hono();
const db = new Database('todo.db');
// todoテーブルが存在しなければ作成するSQL
const createTodoTableQuery = db.prepare(`
CREATE TABLE IF NOT EXISTS todo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed INTEGER NOT NULL
);
`);
// todoテーブルが存在しなければ作成
createTodoTableQuery.run();
const todoList = [
{ title: "JavaScriptを勉強する", completed: false },
ポイントはCREATE TABLE
文の中でIF NOT EXISTS
を使っているところです。
これを書くことで、2回目以降のサーバー起動などでもしすでにtodo
テーブルが存在していた場合でもエラー(SqliteError: table todo already exists
)が出なくなります。
completed
カラムはtrue
かfalse
を表すため、BOOLEAN
としたいところですが、SQLite3にはBOOLEAN
型がないのでINTEGER
型で0
か1
で表現します。
データを取得する
次に、データを取得する部分を書いてみましょう。
app.get()
メソッドの中で、todoList
をDBから取得する部分を書き換えます。
// `todo`テーブルから全てのデータを取得するSQL
const getTodoListQuery = db.prepare(`
SELECT * FROM todo;
`);
app.get("/", async (c) => {
// `todo`テーブルから全てのデータを取得
const todos = getTodoListQuery.all();
return c.json(todos, 200);
});
サーバーを起動して、curl
コマンドでデータが取得できるか確認してみましょう。
npm run start &
curl http://localhost:8000
もちろんまだ何もデータが入っていないので、空の配列が返ってくるはずです。
一度サーバーを停止します。
fg
を実行してから、Ctrl + C
でサーバーを停止してください。
データを挿入する
次にデータ挿入部分を書いてみましょう。
app.post()
メソッドの中で、todoList
にデータを追加する部分を書き換えます。
const insertTodoQuery = db.prepare(`
INSERT INTO todo (title, completed) VALUES (?, ?);
`);
app.post("/", async (c) => {
const param = await c.req.json();
if (!param.title) {
throw new HttpException(400, { message: "Title must be provided" });
}
const newTodo = {
completed: param.completed ? 1 : 0,
title: param.title,
};
// リクエストに渡されたデータをDBに挿入
insertTodoQuery.run(newTodo.title, newTodo.completed);
return c.json({ message: "Successfully created" }, 200);
});
insertTodoQuery
という変数にINSERT INTO
文をprepare()
メソッドで準備して、run()
メソッドで実行しています。
サーバーを起動して、curl
コマンドでデータが挿入できるか確認してみましょう。
サーバーを再度起動してください。
npm run start &
データを挿入するリクエストを送ってみましょう。
curl -X POST -H "Content-Type: application/json" -d '{"title": "次回のWeb研に出席する"}' http://localhost:8000
これでデータが挿入されているはずです。
さっき作ったGET
リクエストを送って、データが取得できるか確認してみましょう。
curl http://localhost:8000
title
が"次回のWeb研に出席する"
のデータが取得できていれば成功です。
一度サーバーを停止します。
fg
を実行してから、Ctrl + C
でサーバーを停止してください。
ここらへんで一旦コミットしておきましょう。
git add server.js
git commit -m "DBを使ってtodoデータを読み書きできるようにした"
データを編集する機能をDBを使って実装する
次に、データを編集する機能をDBを使って実装してみましょう。
app.put()
メソッドの中で、todoList
のデータを更新する部分を書き換えます。
// 指定されたIDのtodoを取得するSQL
const getTodoQuery = db.prepare(`
SELECT * FROM todo WHERE id = ?;
`);
// 指定されたIDのtodoのtitleとcompletedを更新するSQL
const updateTodoQuery = db.prepare(`
UPDATE todo SET title = ?, completed = ? WHERE id = ?;
`);
app.put("/:id", async (c) => {
const param = await c.req.json();
const id = c.req.param("id");
if (!param.title && param.completed === undefined) {
throw new HTTPException(400, { message: "Either title or completed must be provided" });
}
// 指定されたIDのtodoを取得
const todo = getTodoQuery.get(id);
if (!todo) {
throw new HTTPException(400, { message: "Failed to update task title" });
}
if (param.title) {
todo.title = param.title;
}
if (param.completed !== undefined) {
todo.completed = param.completed ? 1 : 0;
}
// リクエストに渡されたデータをDBに更新
updateTodoQuery.run(todo.title, todo.completed, id);
return c.json({ message: "Task updated" }, 200);
});
getTodoQuery
とupdateTodoQuery
という変数にそれぞれSELECT
文とUPDATE
文をprepare()
メソッドで準備します。
getTodoQuery.get(id)
で指定されたIDの更新前データを取得し、新たなデータになるようにtodo
オブジェクトを更新して、updateTodoQuery.run()
でDBを更新します。
サーバーを起動して、curl
コマンドでデータが更新できるか確認してみましょう。
npm run start &
データを更新するリクエストを送ってみましょう。
curl -X PUT -H "Content-Type: application/json" -d '{"completed": true}' http://localhost:8000/1
さっき作ったGET
リクエストを送って、データが更新されているか確認してみましょう。
curl http://localhost:8000
"completed": true
になっていれば成功です。
一度サーバーを停止します。
fg
を実行してから、Ctrl + C
でサーバーを停止してください。
データを削除する機能をDBを使って実装する
最後に、データを削除する機能をDBを使って実装してみましょう。
app.delete()
メソッドの中で、todoList
のデータを削除する部分を書き換えます。
// 指定されたIDのtodoを削除するSQL
const deleteTodoQuery = db.prepare(`
DELETE FROM todo WHERE id = ?;
`);
app.delete("/:id", async (c) => {
const id = c.req.param("id");
// 指定されたIDのtodoを取得
const todo = getTodoQuery.get(id);
// もし指定されたIDのtodoが存在しない場合はエラーを返す
if (!todo) {
throw new HTTPException(400, { message: "Failed to delete task" });
}
// 指定されたIDのtodoを削除
deleteTodoQuery.run(id);
return c.json({ message: "Task deleted" }, 200);
});
deleteTodoQuery
という変数にDELETE
文をprepare()
メソッドで準備します。
サーバーを起動して、curl
コマンドでデータが削除できるか確認してみましょう。
npm run start &
データを削除するリクエストを送ってみましょう。
curl -X DELETE http://localhost:8000/1
さっき作ったGET
リクエストを送って、データが削除されているか確認してみましょう。
curl http://localhost:8000
"id": 1
のデータが削除されていれば成功です。
一度サーバーを停止します。
fg
を実行してから、Ctrl + C
でサーバーを停止してください。
コミットしましょう。
git add server.js
git commit -m "DBを使ってtodoデータの更新と削除ができるようにした"
これでsimple-todo-app
にデータベースを導入することができました!
リファクタリング
最後に、要らなくなったtodoList
とcurrentId
を削除して終わりです。最終的にはこうなりました。
import Database from 'better-sqlite3';
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { cors } from "hono/cors";
const app = new Hono();
const db = new Database('todo.db');
app.use(cors());
// todoテーブルが存在しなければ作成するSQL
const createTodoTableQuery = db.prepare(`
CREATE TABLE IF NOT EXISTS todo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed INTEGER NOT NULL
);
`);
// todoテーブルが存在しなければ作成
createTodoTableQuery.run();
// `todo`テーブルから全てのデータを取得するSQL
const getTodoListQuery = db.prepare(`
SELECT * FROM todo;
`);
app.get("/", async (c) => {
// `todo`テーブルから全てのデータを取得
const todos = getTodoListQuery.all();
return c.json(todos, 200);
});
// `todo`テーブルにデータを挿入するSQL
const insertTodoQuery = db.prepare(`
INSERT INTO todo (title, completed) VALUES (?, ?);
`);
app.post("/", async (c) => {
const param = await c.req.json();
if (!param.title) {
throw new HttpException(400, { message: "Title must be provided" });
}
const newTodo = {
completed: param.completed ? 1 : 0,
title: param.title,
};
// リクエストに渡されたデータをDBに挿入
insertTodoQuery.run(newTodo.title, newTodo.completed);
return c.json({ message: "Successfully created" }, 200);
});
// 指定されたIDのtodoを取得するSQL
const getTodoQuery = db.prepare(`
SELECT * FROM todo WHERE id = ?;
`);
// 指定されたIDのtodoのtitleとcompletedを更新するSQL
const updateTodoQuery = db.prepare(`
UPDATE todo SET title = ?, completed = ? WHERE id = ?;
`);
app.put("/:id", async (c) => {
const param = await c.req.json();
const id = c.req.param("id");
if (!param.title && param.completed === undefined) {
throw new HTTPException(400, { message: "Either title or completed must be provided" });
}
// 指定されたIDのtodoを取得
const todo = getTodoQuery.get(id);
if (!todo) {
throw new HTTPException(400, { message: "Failed to update task title" });
}
if (param.title) {
todo.title = param.title;
}
if (param.completed !== undefined) {
todo.completed = param.completed ? 1 : 0;
}
// リクエストに渡されたデータをDBに更新
updateTodoQuery.run(todo.title, todo.completed, id);
return c.json({ message: "Task updated" }, 200);
});
// 指定されたIDのtodoを削除するSQL
const deleteTodoQuery = db.prepare(`
DELETE FROM todo WHERE id = ?;
`);
app.delete("/:id", async (c) => {
const id = c.req.param("id");
// 指定されたIDのtodoを取得
const todo = getTodoQuery.get(id);
// もし指定されたIDのtodoが存在しない場合はエラーを返す
if (!todo) {
throw new HTTPException(400, { message: "Failed to delete task" });
}
// 指定されたIDのtodoを削除
deleteTodoQuery.run(id);
return c.json({ message: "Task deleted" }, 200);
});
app.onError((err, c) => {
return c.json({ message: err.message }, 400);
});
serve({
fetch: app.fetch,
port: 8000,
});
DBを使うことでサーバーを止めてもデータが消えることがなくなりました。
また、実はデータの検索が ( はレコード数) でできるようになっていたり、AUTOINCREMENT
を使うことでid
を自動で付与できるようになっていたりします。
これでsimple-todo-app
は完成です!お疲れ様でした!