node.js + express + sqlite3でWebAPI（CRUD）を作る


node.js + express + sqlite3でWebAPIを作るニーズがあったのでメモ。

## 準備

場所の作成と必要モジュールインストール。実装ファイル生成。

```bash
cd
mkdir node-api-test
cd node-api-test

npm init -y

npm install express
npm install sqlite3

touch index.js
```

## 実装

淡々と実装。

### 知っておいた方がいいこと

sqlite3ではinsertしたりupdateしたIDをthis.lastIDやthis.changesで取得することができるが、this.*という構文はラムダ関数の中では利用できません。幸いなことに？const stmt = db.prepare();を利用すると、stmt.lastID, stmt.changesで取得できるようになるので、冗長とはなりますが、stmt.prepare()を利用しています。

>最適な書き方は引き続き模索中。今後変えるかも。

```js:index.js
const express = require("express");
const app = express();

//POSTできたりするように（おまじない）
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

//sqlite3関連設定
const sqlite3 = require("sqlite3");
const db = new sqlite3.Database("./test.db", (err) => {
    if (err) {
        console.error("database error: " + err.message);
    } else {
        db.serialize(() => {
            //都度table削除（あれば）
            db.run("drop table if exists members");
            //table生成（無ければ）
            db.run("create table if not exists members( \
                id integer primary key autoincrement, \
                name nverchar(32), \
                age integer \
            )", (err) => {
                if (err) {
                    console.error("table error: " + err.message);
                } else {
                    //初期データinsert
                    db.run("insert into members(name,age) values(?,?)", "hoge", 11);
                    db.run("insert into members(name,age) values(?,?)", "foo", 22);
                    db.run("insert into members(name,age) values(?,?)", "bar", 33);
                }
            });
        });
    }
});

//リッスン開始
app.listen(3000, () => {
    console.log("Start server on port 3000.");
});

app.get("/", (req, res) => {
    res.send("welcome");
});

//create
app.post("/members", (req, res) => {
    const reqBody = req.body;
    const stmt = db.prepare("insert into members(name,age) values(?,?)"); //lastID取得のため
    stmt.run(reqBody.name, reqBody.age, (err, result) => { //lambda式を使うとthis.lastIDでは取得できない
        if (err) {
            res.status(400).json({
                "status": "error",
                "message": err.message
            });
            return;
        } else {
            res.status(201).json({
                "status": "OK",
                "lastID": stmt.lastID
            });
        }
    });
});

//get members
app.get("/members", (req, res) => {
    db.all("select * from members", [], (err, rows) => {
        if (err) {
            res.status(400).json({
                "status": "error",
                "message": err.message
            });
            return;
        } else {
            res.status(200).json({
                "status": "OK",
                "members": rows
            });
        }
    });
});

//get member
app.get("/members/:id", (req, res) => {
    const id = req.params.id;
    db.get("select * from members where id = ?", id, (err, row) => {
        if (err) {
            res.status(400).json({
                "status": "error",
                "message": err.message
            });
            return;
        } else {
            res.status(200).json({
                "status": "OK",
                "members": row
            });
        }
    })
})

//update member
app.patch("/members", (req, res) => {
    const reqBody = req.body;
    const stmt = db.prepare("update members set name = ?, age = ? where id = ?");
    stmt.run(reqBody.name, reqBody.age, reqBody.id, (err, result) => {
        if (err) {
            res.status(400).json({
                "status": "error",
                "message": err.message
            });
            return;
        } else {
            res.status(200).json({
                "status": "OK",
                "updatedID": stmt.changes
            });
        }
    })
})

//delete member
app.delete("/members/:id", (req, res) => {
    const id = req.params.id;
    const stmt = db.prepare("delete from members where id = ?");
    stmt.run(id, (err, result) => {
        if (err) {
            res.status(400).json({
                "status": "error",
                "message": err.message
            });
            return;
        } else {
            res.status(200).json({
                "status": "OK",
                "deletedID": stmt.changes
            });
        }
    })
})
```

## 動作確認

では動作確認をしていきます。APIのテストにはPastmanやVSCodeのプラグインが利用できますが、ここではcurlを利用します。

### 実行

いつもの。

```bash
node index.js
```

### CREATE

挿入。

```bash
curl -s -X POST -H "Content-Type: application/json" -d '{"name":"user1","age":"44"}' http://localhost:3000/members
```

### READ(ALL/単体）

取得は全件と単体。全件に関しては実務ではページネーション機能の実装が必要でしょう。

#### 全てを取得

```bash
curl -s -X GET http://localhost:3000/members
```

#### IDを指定して取得

```bash
curl -s -X GET http://localhost:3000/members/1
```

### UPDATE

URLパラメータでIDを指定する方法もあるかと思いますが、ここではmemberオブジェクト全部を投げる。

```bash
curl -s -X PATCH -H "Content-Type: application/json" -d '{"id":"1","name":"hoge","age":"99"}' http://localhost:3000/members
```

### DELETE

削除。

```bash
curl -s -X DELETE http://localhost:3000/members/1
```

>存在しないIDを投げるとエラーではなく、changesとして0が戻る。

## 本番環境では

下記が最低限必要です（まあ、そもそもsqlite3を本番で使うことないですが）

* CORS対応
* 受け取った値のバリデーション