2
0

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

Last updated at Posted at 2024-01-24

はじめに

私は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をインストール

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のインポート 

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

ToDoリストのデータ格納先

const todos = [
  { id: 1, title: 'タスク1', completed: false },
  { id: 2, title: 'タスク2', completed: true },
];
  • 今回はデータの永続化を図らずに、todosでデータを保持することとする。そのため、ローカルサーバー起動時はデータを保持するが、落とすと初期値に戻ってしまいます
app.use(express.json());
  • app.use() は、Expressアプリケーションにミドルウェアを追加するためのメソッドです。ミドルウェアはリクエストとレスポンスの間に挿入され、特定の処理を行うために使用されます。ミドルウェアとは、GET POSTメソッドなどを実行する前に呼び出される処理だと理解すれば良いと思います
    詳しくは以下のリンクを御覧ください。

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

ToDoリストの取得

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リストの作成

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リストの更新

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を取得します

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

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

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

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

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

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

ToDoリストの削除

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で返しています

サーバーを起動

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のフォローよろしくお願いいたします!

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

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0