プログラミングの理解が遅すぎる初心者がJavaScript、Node.jsで投票型掲示板を作ろうとしてます

トップページでは記事の順番がごちゃごちゃなので、記事もくじをご覧いただければと思います。

投票ページの作成(Node.js)⑥サーバーへの保存Ⅰ

前回分はこちら。

html-css-javascript.hatenadiary.com

ここまではサーバーに保存されていたデータをフロントに送り、それを表示させるところまでやりました。

ということで今回はフロントから送られてきたデータをサーバーに保存するところをやります。

まずいきなり全部を送るのではなく、IDだけを送って、送られたかどうかを確認します。

データの送信の確認

順番としては以下の通り。

①何を受け取るかを決める。 ②サーバーで受け取る処理を書く。 ③保存処理を書く。 ④結果を返して表示する。

んでは一個ずつやっていきます。

①何を受け取るかを決める

上述の通り、最初に送るのはIDだけです。

送られるIDは、ユーザーが投票ボタンを押したときに、どのIDの候補者のボタンを押したかで決まります。

で、postはデータを送って処理をさせるリクエストですが、今回はデータを送るだけなので、以下のようになります。

app.post('/api/vote', (req, res) => {

  const id = req.body.id;
  console.log("受け取ったID:", id);

あと、メッセージを受け付けたことをフロントに返すことも書いておきます。

res.json({ message: "投票を受け付けました" });

resはオブジェクトです。

その関数jsonを呼び出しています。

これでオブジェクト{ message: "投票を受け付けました" }をJSON形式でフロントにHTTPレスポンスとして送信します。

で、私、{ message: "投票を受け付けました" }ってどこで定義されたオブジェクトだよ、という疑問もありましたが、オブジェクトの生成ってその場で使うときはそのまま書いて構わないことが判明しました。

つまりやってることは

const data ={
  message: "投票を受け付けました",
}
res.json(data);

と同じです。

すぐ渡す時とか、一度しか使わないときとかはこれで十分です。

これを表示させるには当然フロントでfetchを定義する必要があるので、そっちは後回しにしてまずはコンソールだけ確認します。

確認するにはコンソールでJavaScriptでIDを送るコードを書きます。

fetch('http://localhost:3000/api/vote', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({ id: "test123" })
});

idの値は数字でも文字列でも問題ありません。

というわけで早速書いてみます。

結果。

VM57:1 POST http://localhost:3000/api/vote 404 (Not Found)

なんで。

エラーを見てみました。

「Programing/project/frontend/style.css net::ERR_FILE_NOT_FOUND」

CSSファイルがないって言われてます。

んあ??

通信エラーじゃないって?

さっきの404はどこに行った?

まあいいや。

とりあえずCSSがないってのならそっちから直しましょう。

そういえばSCSSファイルを開いて保存していなかった気がします。

なので開いて保存して、CSSファイルを作成しました。

結果は「Promise」だけが出ました。

これ、約束オブジェクトですね。

あとからデータが来るという内容ですので、通信自体は問題なく行われているようです。

来たら、thenで中身を見られます。

404、ほんとになんだったんだろ。

いずれにしても直ってしまったので、ここは良しとします。

となると、いったい何が?

もしかしてサーバーが動いてない?

と思ってターミナルを見たら。

サーバーがポート3000で稼働中

受け取ったID: test123

受け取ったID: test123

受け取ったID: test123

めっちゃ送られてました。

いったいなんで私はフロント側のコンソールを見てたんでしょう。

いずれにしてもこれでデータが送られて来たことは確認できました。

ではここからサーバーに保存することをやります。

サーバーに保存する

次に投票数を一つ増やすという処理をPOSTリクエストで送って、その結果をサーバーに保存します。

IDを送ったので、そのIDに合っ他候補を配列探しますのでfindを使います。

const candidate = candidates.find(c => c.id === id);

findについては以下の記事を参照。 html-css-javascript.hatenadiary.com

これでcandidateにtrueが来たら投票数に1を足します。

if (candidate) {
  candidate.vote += 1;
}

これで処理は終わりました。

保存します。

保存の仕方がわからないので、先生に聞きました。

保存の仕方

  fs.writeFileSync(
    "candidates.json",
    JSON.stringify(candidates, null, 2),
    "utf8"

一個ずつ観ていきます。

writeFileSync

fsの後に書いてあるのでファイルシステムにあるプロパティということになりそうです。

で、writeって書いてあるから書き込みですね。

で、Syncですから同期。

つまり書き終わるまで先に進まないということですね。

後はわからないので素直に先生に聞きました。

第一引数はデータを保存するパスとファイル名です。

「data/candidates.json」のように書けば、dataというディレクトリにcandidates.jsonが保存されます。

なにも書かなければsever.jsと同じディレクトリに保存されます。

特に今は同じディレクトリで問題ないのでこのまま進めます。

JSON.stringify

これは見たらJSON形式の文字列にするということはわかります。

問題は引数です。

先生に聞いてみます。

第一引数は普通にどのデータかを指し示すことはわかります。

今回は候補者全員分のデータ(配列)、candidatesです。

問題は第二引数です。

nullになってます。

なんじゃこりゃ。

と思って先生に聞きました。

これは「フィルター」です。

配列の要素のプロパティのうち、ここに書かれていないものは弾かれます。

たとえば以下のような場合

const obj = {
  id: "a1",
  vote: 3,
  secret: "xxx"
};

このとき

JSON.stringify(obj, ["id", "vote"]);

と書くと、id、voteしか通さないので、secretであるxxxは弾かれてしまいます。

結果、保存内容はidとvoteだけになります。

{"id":"a1","vote":3}

で、今回は(というか大抵の場合)フィルターにかけるものはないのでnullになっています。

さて、最後。

第三引数。

これは保存データを表示するとき、プロパティの前にいくつスペースを空けるかを決めます。

まずここに数値を入れている時点で、データ同士が改行されます。

そんでプロパティの前に

{"id":"a1","vote":3}
//コレが
{
  "id": "a1",
  "vote": 3
}
//こうなる
//プロパティの前には2スペース入っている

で、ネストになった場合は2スペースずつ勝手に増えていくようになります。

{
  "a": {
    "b": {
      "c": 1
    }
  }
}

まあそういうのを決めるとこなので、2か4以外入ることはなさそうです。

1だったらぎゅうぎゅうだし、4以上だったら無駄に広がりますし。

実行してみる

さて、ここまで来たので、ちゃんとサーバーに保存されるかを見てみたいとおもうのですが、考えて見たらこの時点ではvoteというキー自体がサーバーの中に入っていません。

なのでプラス1をしてくれと言ったところで「いや、そんなんないけど」と返されるだけになります。

なので最初に「voteがなければ作れ」と指示しておく必要がありますね。

単純に投票数に+1と書いていたところを以下のように書きます。

もうこの書き方は何度もやってきたのでぱっと出てきますね。

 candidate.vote = (candidate.vote || 0) + 1;

あとフロントの方でnameを表示するようになっています。

これ、サーバーにnameがなかった場合、表示がundefinedになります。

なのでフロントに以下のように書いておきましょう。

const name = candidate.name || "名無し";

これでvoteだけプラス1しても、名前はデフォルトで「名無し」になります。

入れるのは以下です。

data.forEach(candidate => {
      const li = document.createElement('li');
       const name = candidate.name || "名無し";//ここ
      li.textContent = `${candidate.name}:${candidate.vote}票`;
      candidateList.appendChild(li);
    });

forEachで一つずつ見ていったときに表示データを決めることになりますのでここが適切です。

上で「IDが一致した場合、投票数を1増やす」とやっている以上、IDを書いたデータをデフォルトとして前もって保存しておく必要があります。

ちゅうか、それが普通みたいです。

なのでcandidates.jsonというファイルを作って、先に書いておくことにします。

ついでですし名前も書いておきましょう。

やり方は普通にcandidates.jsonというファイルを作って、それをVS Codeで開いて書き込むだけです。

[
  { "id": "a1", "name": "デンジ", "vote": 0 },
  { "id": "a2", "name": "マキマ", "vote": 0 },
  { "id": "a3", "name": "パワー", "vote": 0 },
  { "id": "a4", "name": "アキ", "vote": 0 }
]

さて、これでやっと実行できます。

(上で書いた名無しにするためのコードとかは一体なんだったんだ。)

今度こそ実行してみる

長くなったんで次回やります。