Express から MySQL に接続する

# はじめに
この記事では、ブログアプリの実装を通して、Express から MySQL に接続し、CRUD処理を行う手順について学んでいきます。

# 前提情報
開発環境は以下の通りです。
- Windows11
- VSCode
- Talend API Tester
- TypeScript 5.0.4
- Node.js 18.14.0
- Express 4.18.2
- MySQL 8.0.3

また、事前にExpressでRoutingの実装とWindowsへのMySQLのインストールし、blogsテーブルを作成しておきます。
それぞれ手順は以下の記事に記載があります。

https://qiita.com/Yasushi-Mo/items/532d575624136cdc44ff

https://qiita.com/Yasushi-Mo/items/fe67cae83a71840460ea

https://qiita.com/Yasushi-Mo/items/082ea7395bf1c9f004bd

# Express からMySQL に接続
まず、ExpressからMySQLに接続します。

## mysqlモジュールのインストール
Expressからデータベースを操作するためにmysql2モジュールをインストールします。

https://github.com/sidorares/node-mysql2

```
yarn add mysql2
```

```
yarn add --dev @types/mysql
```

## データベース接続情報の定義
mysql2モジュールを読み込み、テータベースの接続情報を定義します。

```index.ts
import mysql from "mysql2";

const connection = mysql.createConnection({
  host: "localhost",
  user: "root",
  password: "[rootユーザーのパスワード]",
  database: "blog_app",
});
```

## データベースへの接続確認
先ほど定義したテータベースの接続情報を利用してデータベースへの接続を確認するコードを追加します。

```index.ts
connection.connect((error) => {
  if (error) {
    console.error("Error connecting to MySQL: ", error);
    return;
  }

  console.log("Success connecting to MySQL");
});
```

ローカルサーバー起動してMySQLへの接続が成功すると、接続が成功したログが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/3d8eded7-0db4-185a-5e9c-186c86fad12f.png)

# ブログの CRUD
次はExpressからMySQLを操作して、ブログのCRUD機能が実現できるようにします。
CRUD機能は、query() メソッドを利用して以下のように実装します。
```index.ts
connection.query(
  [SQL],
  (error, results) => {
    [SQL実行後の処理]
  }
);
```

## ブログをデータベースに保存する
上記の形式に沿って、データベースへの保存処理を実装します。

```index.ts
const query = "INSERT INTO blogs (title, body) VALUES (?, ?)";
connection.query(
  query,
  [`${req.body.title}`, `${req.body.body}`],
  (error, results) => {
    if (error) {
      console.error("Error insert values:", error);
      res.status(500).json(error);
      return;
    }
    res.status(201).json({ message: "Blog created successfully" });
  }
);
```
こちらの保存処理を Create のルーティング処理に追加します。
また、保存処理の前にデータベースへの接続処理も追加します。
```index.ts
const connectToDatabase = () => {
  connection.connect((error) => {
    if (error) {
      console.error("Error connecting to MySQL: ", error);
      return;
    }

    console.log("Success connecting to MySQL");
  });
};

// Create a new blog
app.post("/blogs", (req: Request, res: Response) => {
  connectToDatabase();

  const query = "INSERT INTO blogs (title, body) VALUES (?, ?)";
  connection.query(
    query,
    [`${req.body.title}`, `${req.body.body}`],
    (error, results) => {
      if (error) {
        console.error("Error insert values:", error);
        res.status(500).json(error);
        return;
      }
      res.status(201).json({ message: "Blog created successfully" });
    }
  );
});
```
動作確認をします。Talend API Tester でブログのタイトルと本文を追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/507b0fa4-8e68-904d-6584-56bf734d45df.png)
データベースに先ほどのタイトルと本文が保存されているか確認します。
```
select * from blogs;
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/03237dd0-2f69-40dc-13c1-1c5ab80045be.png)
保存されていることが確認できました。

## データベースからブログ一覧を取得する
ブログ一覧を取得するルーティングにデータベースからブログ一覧を取得する処理を追加します。

```index.ts
// Get all blogs
app.get("/blogs", (req: Request, res: Response) => {
  const query = "SELECT * FROM blogs";
  connection.query(query, (error, results) => {
    if (error) {
      console.error("Error select values:", error);
      res.status(500).json(error);
      return;
    }
    res.status(200).json(results);
  });
});
```
動作確認をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/f0f3b4f8-635a-cb21-1b08-8fb3e32007b6.png)
ブログ一覧を取得できることを確認できました。

## ブログを更新する
まず、ブログを更新するルーティングに更新対象のIDが含まれているか確認する処理を追加します。

データベースからブログ一覧を取得し、その中に更新対象のIDが含まれているか確認します。
含まれていない場合、404を返します。

```index.ts
// Update a blog
app.put("/blogs/:id", (req: Request, res: Response) => {
  const id = req.params.id;
  const { title, body } = req.body;

  const checkQuery = "SELECT * FROM blogs WHERE id = ?";
  connection.query(checkQuery, [id], (error, results) => {
    if (error) {
      console.error("Error checking ID existence:", error);
      res.status(500).json(error);
      return;
    }

    const isExists = Array.isArray(results) && results.length > 0;
    if (!isExists) {
      console.log("Blog ID not found");
      res.status(404).json({ message: "Blog ID not found" });
      return;
    }
  });
});
```

含まれている場合、更新処理を行います。

```index.ts
// Update a blog
app.put("/blogs/:id", (req: Request, res: Response) => {
  const id = req.params.id;
  const { title, body } = req.body;

  const checkQuery = "SELECT * FROM blogs WHERE id = ?";
  connection.query(checkQuery, [id], (error, results) => {
    if (error) {
      console.error("Error checking ID existence:", error);
      res.status(500).json(error);
      return;
    }

    const isExists = Array.isArray(results) && results.length > 0;
    if (!isExists) {
      console.log("Blog ID not found");
      res.status(404).json({ message: "Blog ID not found" });
      return;
    }

    const updateQuery = "UPDATE blogs SET title = ?, body = ? WHERE id = ?";
    connection.query(updateQuery, [title, body, id], (error, results) => {
      if (error) {
        console.error("Error update values:", error);
        res.status(500).json(error);
        return;
      }

      res.status(200).json({ message: "Blog updated successfully" });
    });
  });
});
```
動作確認をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/7547d525-e6df-1e3e-130d-7fe85f86283e.png)

更新されていることを確認できました。

## ブログを削除する
まず、更新処理と同じように削除対象のIDが含まれているか確認する処理を追加します。
含まれていない場合、404を返し、含まれている場合、削除処理を行います。

```index.ts
// Delete a blog
app.delete("/blogs/:id", (req: Request, res: Response) => {
  const id = req.params.id;

  const checkQuery = "SELECT * FROM blogs WHERE id = ?";
  connection.query(checkQuery, [id], (error, results) => {
    if (error) {
      console.error("Error checking ID existence:", error);
      res.status(500).json(error);
      return;
    }

    const isExists = Array.isArray(results) && results.length > 0;
    if (!isExists) {
      console.log("Blog ID not found");
      res.status(404).json({ message: "Blog ID not found" });
      return;
    }

    const deleteQuery = "DELETE FROM blogs WHERE id = ?";
    connection.query(deleteQuery, [id], (error, results) => {
      if (error) {
        console.error("Error update values:", error);
        res.status(500).json(error);
        return;
      }

      res.status(200).json({ message: "Blog deleted successfully" });
    });
  });
});
```

動作確認をします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318188/407de763-5d0f-f504-80ea-03eb538cc306.png)
削除されているこを確認できました。

# 最後に
この記事では、ブログアプリの実装を通して、Express から MySQL に接続し、CRUD処理を行う手順について学びました。今後は別の技術を使って同じ実装をしたり、もう少し複雑な処理を実装してみようと思います。

# 参考
- [MySQLの使い方](https://www.javadrive.jp/mysql/)
- [node-mysql2](https://github.com/sidorares/node-mysql2)