【Node.js】TODOリストを管理するAPIを作ってみた（初学者向け）

# はじめに

私はAPIを作成したことがなく、サーバーサイドの知見が少ないです。APIを理解するために、Node.jsを用いてローカルでTODOリストを管理するAPIを作成します。初学者の私がAPIを作成することを目的としており、GET POST　メソッドなどの基本的な部分に着目して記載しています。そのため、今回はデータの永続化などは考慮していません。

# セットアップ
まずは、新規プロジェクトを作成します。

新しいディレクトリを作成
```
mkdir todo-api
```

ディレクトリ内へ
```
cd todo-api
```

プロジェクトをセットアップ
```
npm init -y
```

出力結果)　package.jsonが生成される
```
Wrote to /Users/（自身のPCのディレクトリ）/ReactNativeProject/todo-api/package.json:

{
  "name": "todo-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

Express.jsをインストール

https://expressjs.com/ja/

```
npm install express
```

出力結果) 問題なくインストール完了
```
added 62 packages, and audited 63 packages in 2s

11 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
```

# 実装

app.jsファイルを新規作成して、実装していく。

Expressのインポート　
```js
const express = require('express');
const app = express();
const port = 3000;
```
- `require('express')`はNode.jsのモジュールシステムを使用して、'express'を読み込んでいます
- `app`で`express`をインスタンス生成します
- `port`でポート番号の値を保持しています

### ToDoリストのデータ格納先
```js 
const todos = [
  { id: 1, title: 'タスク1', completed: false },
  { id: 2, title: 'タスク2', completed: true },
];
```
- 今回はデータの永続化を図らずに、`todos`でデータを保持することとする。そのため、ローカルサーバー起動時はデータを保持するが、落とすと初期値に戻ってしまいます

```js
app.use(express.json());
```
- `app.use()` は、Expressアプリケーションにミドルウェアを追加するためのメソッドです。ミドルウェアはリクエストとレスポンスの間に挿入され、特定の処理を行うために使用されます。ミドルウェアとは、`GET`　`POST`メソッドなどを実行する前に呼び出される処理だと理解すれば良いと思います
詳しくは以下のリンクを御覧ください。

https://expressjs.com/ja/guide/writing-middleware.html

- APIメソッドを実行する前に、クライアントからのHTTPリクエストのボディに含まれるJSONデータを変換し、JavaScriptオブジェクトに変換する実装をしています

### ToDoリストの取得
```js
app.get('/todos', (req, res) => {
  res.json(todos);
});
```
- `app.get('/todos', (req, res) => { ... );` は、ExpressでGETリクエストが '/todos' パスに対して届いた時の処理を定義しています

- `(req, res) => { ... }` は、req はリクエストで、クライアントからのリクエストに関する情報を含んでいます。res はレスポンスで、クライアントに返すためのレスポンスを操作するためのメソッドを提供します

- `res.json(todos);` は、レスポンスをJSON形式で送信するコードです。これがクライアントに返されます

### ToDoリストの作成
```js
app.post('/todos', (req, res) => {
  const newTodo = req.body;
  todos.push(newTodo);
  res.status(201).json(newTodo);
});
```
- `app.post('/todos', (req, res) => { ... });` は、ExpressでPOSTリクエストが '/todos' パスに対して届いた時の処理を定義しています

- `(req, res) => { ... }` は、GETと同様です

- `const newTodo = req.body`; は、クライアントが送信した新しいToDoデータを保持しています

- `todos.push(newTodo);` は、`todos`配列に新しいToDoデータ `newTodo` を追加します

- `res.status(201).json(newTodo);` は、レスポンスを設定しています。まず、HTTPステータスコードを201に設定し、`newTodo` をJSON形式でクライアントに返信します

### ToDoリストの更新
```js
app.put('/todos/:id', (req, res) => {
  const todoId = parseInt(req.params.id);
  const updatedTodo = req.body;
  const todoIndex = todos.findIndex(todo => todo.id === todoId);
  if (todoIndex !== -1) {
    todos[todoIndex] = { ...todos[todoIndex], ...updatedTodo };
    res.json(todos[todoIndex]);
  } else {
    res.status(404).json({ message: '指定されたToDoが見つかりません' });
  }
});
```
- `app.put();` は、ExpressでPUTリクエストが `/todos/:id` パスに対しての処理を定義しています。`:id` は動的なパラメータとして、識別子（ID）を受け取るために設定しています

- `const todoId = parseInt(req.params.id);` は、リクエストの**パスパラメータ**である `:id` を整数に変換して、`todoId`という変数に格納しています。これにより、IDを取得します

パラメーターを設定する際に、パスパラメーターとクエリパラメーターがあります。両者の違いに関しては参考になったリンク貼っておきます。

https://qiita.com/Shokorep/items/b7697a146cbb1c3e9f0b#%E3%82%AF%E3%82%A8%E3%83%AA%E3%83%91%E3%83%A9%E3%83%A1%E3%83%BC%E3%82%BF

- `const updatedTodo = req.body;` は、クライアントからのリクエストボディ（JSONデータ）を取得し、updatedTodo という変数に格納しています

- `const todoIndex = todos.findIndex(todo => todo.id === todoId);` は、 todos 配列内で指定されたToDoのIDを持つToDoを検索し、そのインデックスを todoIndex 変数に格納しています

`findIndex`は、値が見つからなかった場合に-1を返すメソッドです。以下リンクです。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex#%E9%85%8D%E5%88%97%E5%86%85%E3%81%AE%E7%B4%A0%E6%95%B0%E3%81%AE%E4%BD%8D%E7%BD%AE%E3%82%92%E6%A4%9C%E7%B4%A2%E3%81%99%E3%82%8B

- `todoIndex` の値に応じて処理が分岐されます。もし `todoIndex` が -1 でない場合、指定されたToDoが見つかったことを意味し、ToDoデータが更新され、更新後のToDoデータがクライアントにJSON形式で返されます

- もし `todoIndex` が -1 の場合、指定されたToDoが見つからなかったことを意味し、HTTPステータスコード404が設定され、エラーメッセージを含むJSONレスポンスがクライアントに返されます





### ToDoリストの削除
```js
app.delete('/todos/:id', (req, res) => {
  const todoId = parseInt(req.params.id);
  const todoIndex = todos.findIndex(todo => todo.id === todoId);
  if (todoIndex !== -1) {
    const deletedTodo = todos.splice(todoIndex, 1)[0];
    res.json(deletedTodo);
  } else {
    res.status(404).json({ message: '指定されたToDoが見つかりません' });
  }
});
```
- `app.delete('/todos/:id', (req, res) => { ... });` は、ExpressアプリケーションでDELETEリクエストが '/todos/:id' パスに対して受け付けられた場合の処理を定義しています。':id' は動的なパラメータとして、ToDoの一意の識別子（ID）を受け取るために使用されます

- 識別子の取得方法や、該当の識別子が存在するかの確認、条件分岐に関しては、PUTの項目と同様なので省略します

- `splice(todoIndex, 1)` は、`todos` 配列から `todoIndex` で指定された位置から1つの要素を削除します。このメソッドは、**元の配列**　を変更し、**削除された要素**　を含む　**新しい配列を返します**

筆者はJavaScriptを書いたことがなかったので、上記の太字の部分は理解に戸惑った部分でした。

- `[0]` は、`splice` メソッドによって返される新しい配列から最初の要素（削除されたToDo）を取得し、それを`res`で返しています

### サーバーを起動
```js
app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});
```

- `app.listen(port, () => { ... });` は、`port`で指定したポート番号でクライアントからのリクエストを待ち受けるためのコードです。この行のコードはアプリケーションのメインエントリポイントの最後に配置され、サーバーを起動する役割を果たします

- `port` は、ポート番号を指定するための変数です。最初の方の実装で、`const port = 3000;` というように設定しているので、サーバーはポート3000でアクセス可能です

- `console.log(Server is running on port ${port});`は、サーバーが正常に起動したことをコンソールにログ出力するコードです。ログメッセージにはサーバーがどのポートでリッスンしているかが表示されます

## APIの挙動確認

ローカルサーバーを起動
```
node app.js
```

`GET`　初期のTODOリストの値を確認する。
```
curl http://localhost:3000/todos

[
{"id":1,"title":"タスク1","completed":false},　←　OK
{"id":2,"title":"タスク2","completed":true}　　←　OK
]
```

`POST`　新規のTODOリストの値を追加する。
```
curl -X POST -H "Content-Type: application/json" -d '{"title": "新しいTo-Doのタイトル", "completed": false}' http://localhost:3000/todos

{"title":"新しいTo-Doのタイトル","completed":false}%  
```

追加されたか確認
```
curl http://localhost:3000/todos
[
{"id":1,"title":"タスク1","completed":false},
{"id":2,"title":"タスク2","completed":true},
{"title":"新しいTo-Doのタイトル","completed":false}　←　追加されている
]
```

`PUT`　すでに存在する値を更新する
```
curl -X PUT -H "Content-Type: application/json" -d '{"title": "更新後のTo-Doのタ イトル", "completed": true}' http://localhost:3000/todos/1

{"id":1,"title":"更新後のTo-Doのタイトル","completed":true}
```

更新されたか確認
```
curl http://localhost:3000/todos

[
→　{"id":1,"title":"更新後のTo-Doのタイトル","completed":true},←　更新されている
{"id":2,"title":"タスク2","completed":true},
{"title":"新しいTo-Doのタイトル","completed":false}
]
```

`DELETE`　すでに存在する値を削除する
```
curl -X DELETE http://localhost:3000/todos/1

{"id":1,"title":"更新後のTo-Doのタイトル","completed":true}%  
```

削除されたか確認
```
curl http://localhost:3000/todos

[
                                            ←　削除されている
{"id":2,"title":"タスク2","completed":true},
{"title":"新しいTo-Doのタイトル","completed":false}
]
```

上記の検証により、正しく挙動が行えていることを確認できた。

## まとめ
基本的なAPIの構成を、一行一行コードを追いながら学習しました。今回はDBに保存しておらず、一時的に変数の中に保存しているのみであるため、次回以降ではFirestoreやRealmなどを用いてデータの永続化も考慮したAPIを作成してみようかと思いました。

## 最後に
他にも良い方法があれば、コメントいただけると大変うれしいです。
良かったと思ったら、いいねやTwitterのフォローよろしくお願いいたします！

https://sites.google.com/view/muranakar

個人でアプリを作成しているので、良かったら覗いてみてください！