🎉

experss+mongoDBでCRUDを実装してみる

2022/06/26に公開

概要

node.js+expressで今度はmongoDBとの連携でよくある新規登録や削除などCRUDを実装してみようかと思う。
データベースはmongoDBが有名?なのかよく出てくるので選んでみた。
今回は、いろいろファイルを分割してどうこうすると分かりづらくなりそうなので、1ファイルのみで作成しておく。
expressなどの説明は以前したので、行わない。

https://zenn.dev/kiriyama/articles/94e273bda8a591

技術スタック

今回は、node.js/express/mongoose/ejs で。

データを表示させるための、テンプレートエンジンとして、EJSがある。
ただ、あまり使われてる??イメージもなく最終的にはmongoDBと連携してReactなど使うつもりなので、あまり深く語らない。イメージ的にはWordpressのようにタイトルを変数などで動的に変更したりできるイメージ。

ベースファイル

今回長くなる為、部分的にソースコードを乗せていく。基本的には下記のベースファイルをもとに追加していくイメージ。

index.js
//expressを読み込む
const express = require("express");
const app = express();

//POSTを受け取れるようにする
app.use(express.urlencoded({ extended: true }));

//ポート番号
const PORT = process.env.PORT || 5000;
//サーバーに接続
app.listen(PORT, () => {
  console.log("listening server");
});

mongoDBの設定

まずはmongoDBのアカウントを取得する必要がある。
https://www.mongodb.com/ja-jp

アカウントを取得した後はデータベースを作成する。流れは以下の参考サイト。
作成すると3つの接続の選択で真ん中の「Connect your application」を選択する。
これが実際にjsファイルにてmongoDBと接続の際に必要となるデータベースのURLが載っている。

https://reffect.co.jp/node-js/mongodb-cloud

mongooseのインストール

mongoDBと連携するにはmongooseというライブラリを使ったほう簡単らしいのでこちらをインストールする。

npm install mongoose

データベースと接続設定

インストールが完了したら、実際にmongoDBとの接続する。
一応、expressの記述も記載しておく。

index.js
//mongooseを読み込む
const mongoose = require("mongoose");

//connectメソッドで接続する
mongoose.connect("mongodb + srv://monotein:<password>@cluster0.ilzpy.mongodb.net/myFirstDatabase?retryWrites=true&w=majority").then(() => {
    console.log("seccess : connetcted to mongoDB");
  })
  .catch((err) => {
    console.log("erro conencted database");
  });

上記のようにrequire()でmongooseを読み込んだら、connect()メソッドで接続をするが、引数として、データベースへのURLを渡す。
これは、mongoDBのアカウントを取得してデータベースを作成した際に発行されるので便宜変更する必要がある。

書き込むデータのスキーマを作成

実際に接続できても情報の追加などは、mongooseを利用する場合にMongooseのSchemaを定義してどんな情報を書き込むが設定する。
例としてブログの記事とする。

index.js
const mongoose = require("mongoose");

//スキーマの設定
const Schema = mongoose.Schema;
const BlogShema = new Schema({
    title: {
	type:String,
	required: true,
    },
    summary: String,
    image: String,
    textbody: String,
  });

const BlogModel=mongoose.model("Blog",BlogShema)

上から順にタイトル(title)、概要(summary)、画像(image)、本文(textbody)とし、文字列であるString型を指定してスキーマを作成。
必須項目など追加したい場合はタイトル(title)のようにrequired:trueを追加する。

その後、mongoose.modelにmongoose.modelでモデル名とスキーマを指定して、BlogModelというブログモデルを作成しておく。このBlogModelを使って、mongoDBにデータを追加・削除などを行う。

https://zenn.dev/bluepost/articles/74ac51dfa484d1

EJS(テンプレートエンジンについて)

今回CRUDの前に登録フォームの画面や実際に受け取った記事データなどを表示させるための画面などはEJSというテンプレートエンジンを使うので簡単に設定しておく。

インストールしていない場合はまずインストールが必要なのでnmpでインストールする。

npm install ejs

実際にEJSを使う場合は宣言する必要があるので、expressのオブジェクト(app)のssetメソッドで宣言する。
下記の設定によってViewsフォルダ以下の「.ejs」という拡張子のファイルが使用可能となる。

index.js
//ejsを使うための宣言
app.set("view engine","ejs");

EJSファイルを呼び出すには

では記事詳細ページを表示させるために、「blog/:id」というルートで呼び出す場合は下記となる。

index.js
app.get("/blog/:id", async (req, res) => {
  const singleBlog = await BlogModel.findById(req.params.id);
  res.render("blogRead",{singleBlog: singleBlog});
});

上記のres.renderのrenderメソッドで「singleBlog.ejs」というファイルを呼びだせる。
また第2引数に「singleBlog」というプロパティ名でデータを渡せる。

EJSファイル

「views/blogRead.ejs」というブログの記事を表示させたいEJSファイルがあったとして表示させるには、以下のようにする。「singleBlog」というプロパティをJSファイルから受け取ってるものとする。

views/blogRead.ejs
<div>
   <h1><%= singleBlog.title  %></h1>
   <img src="/public/image<%= singleBlog.image %>" alt="photo">
   <p><%= singleBlog.textbody  %></p>
</div>

受け取った値を表示させるには「<%= %>」という表記を使い、if文などの場合は「 <% if(){ %>」と「=」がない表記となる。

以上で、簡単にEJSについて記載したが、今回の記事の本質ではないのでEJSについては下記の参考サイトは参考。

https://zumilog.org/ejs-howto/
https://dekikotu.com/webtech/html-template-ejs/

CRUDのやってみる

ここまででデータベースの接続ができていれば、次はCRUDをやっていく。

Create(登録)

まずは登録の部分。

jsファイルでは以下のようになる。

index.js
 //登録フォームを表示
app.get("/blog/create",(req, res) => {
    res.render("blogCreate")
});

 //入力したデータを登録する
app.post("/blog/create", (req, res) => {
    //データを追加
    BlogModel.create(req.body, (err, saveData) => {
      if (err) {
        res.render("error", { message: "登録できませんでした。" });
      } else {
        //書き込みが成功したらトップへ移動
        res.redirect("/");
      }
    });
    //
});
views/blogCreate.ejs
<form action="/blog/create" method="POST">
    <label for="title">タイトル</label>
    <input type="text" name="title" required><br>
    <label for="summary">要約</label>
    <input type="text" name="summary" required><br>
    <label for="image">イメージ</label>
    <input type="text" name="image" placeholder="/img.jpg" required><br>
    <label for="textbody">本文</label>
    <textarea name="textbody" rows="15" required></textarea><br>
    <button type="submit">投稿</button>
</form>

フォームを表示させるのは res.render("blogCreate")でEJSファイルを読み込むだけ。
実際に受け取ったデータをPOSTで受けとってBlogModel.create()メソッドで登録する。

受け取ったデータ(リクエスト)はreq.bodyに全て入ってるので、そのまま第1引数に渡してあげればよい。第二引数の関数はエラー(err)と登録したデータ(saveData)が返される。
エラーはそのままエラーだがsaveDataには登録したデータが確認できる。

Read(参照)

次はデータを参照する。

記事一覧を読み込む

ブログ記事一覧の場合はトップページなどで表示させる。

index.js
 //一覧を表示
app.get("/",async (req, res) => {
  const allBlogs = await BlogModel.find();
  res.render("index", { allBlogs: allBlogs});
});

記事全体を取ってくるにはBlogModel.find()で取得してallBlogsにデータを保存。
その後、index.ejsのテンプレートを呼び出し、index.ejsにallBlgosというプロパティ名でデータを渡してる。

注意点として、BlogModel.find()でデータを受け取る前に次の処理に行くと困るので、awaitを指定しておく。asyncの指定もお忘れなく。

views/index.ejs
<div>
  <% allBlogs.forEach((blog)=>{ %>
    <div>
     <a href="blog/<%= blog._id %>">
       <img src="/public/image<%= blog.image %>" alt="photo">
       <div>
         <h2><%= blog.title %></h2>
          <p><%= blog.summary %></p>
          </div>
     </a>
   </div>
 <% }) %>
</div>

JSからデータを受け取ったら、あとはallBlogs.froEach()で中身を取り出す。
取り出した「blog._id」の_idはmongoDBに登録した際に自動で付与されるID番号となる。
このID番号はユニークとなるため、URLをして利用する。

記事詳細ページ

ブログ記事一覧からクリックしたら記事の詳細ページを表示するには。

index.js
 //一覧を表示
app.get("/blog/:id",async (req, res) => {
  const singleData = await BlogModel.findById(req.params.id);
  res.render("blogRead", {singleData: singleData});
};
);

記事詳細のデータを取ってくるにはBlogModel.findById()で取得できるのですが、そのIDは「req.params.id」でとってこれる。
もしとれなかったら、console.log(req)で中身が確認できるので確認してみるといい。

記事一覧と同様にBlogModel.findById()でデータを受け取る前に次の処理に行くと困るので、awaitを指定しておく。

views/blogRead.ejs
<div>
  <h1><%= singleData.title  %></h1>
  <img src="/public/image<%= singleData.image %>" alt="photo">
  <p><%= singleData.textbody  %></p>
</div>

記事詳細ページなので、mongooseで作成したスキーマ(Schema)で設定したタイトル(title)、画像のパス(image)、本文(textbody)を指定して出力すれば表示される。

Update(更新)

次は更新の部分。

jsファイルでは以下のようになる

index.js
 //更新する記事データを表示する
 app.get("/blog/update/:id",async (req, res) => {
  const singleData = await BlogModel.findById(req.params.id);
  res.render("blogUpdate", { singleData });
});

記事の更新の場合は、登録されてるIDから検索する必要があるので、findById()を使ってreq.params.idから更新対象の記事データを取ってきてsingleDataに入れる。
それをblogUpdate.ejsにrender()メソッドでsingleDataを渡してあげる。

views/blogUpdate.ejs
<h1>記事を更新する</h1>
<form action="/blog/update/<%= singleBlog._id %>" method="POST">
<input type="text" name="title" value="<%= singleBlog.title %>">
<input type="text" name="summary" value="<%= singleBlog.summary %>">
<input type="text" name="image" placeholder="/img.jpg" value="<%= singleBlog.image %>">
<textarea name="textbody" rows="15" required><%= singleBlog.textbody %></textarea><button type="submit">変更を反映する</button> </form>

app.getから受け取った記事のデータ(singleBlog)をフォームのactionに記事のIDをもとにURLを設定して、フォームのvalue属性に取ってきたデータをそれぞれ設定する。

すると登録されてりるデータがフォームに設定されてるので、修正したら「変更を反映する」ボタンをクリックしてapp.postに送る。

index.js
 //修正したデータで受け取って更新する
app.post("/blog/update/:id", (req, res) => {
    BlogModel.updateOne({ _id: req.params.id }, req.body).exec((error) => {
      if (error) {
        res.render("error", { message: "エラー" });
      } else {
        res.redirect("/");
      }
    });
    res.send("記事の編集画面が完了");
  });

更新するデータの記事IDが必要なのでfindByIdかと思うが、ちゃんとupdateOneというメソッドがある。
第1引数に分割代入で{ _id: req.params.id }としてmongoDBのIDを指定して渡してあげて、第2引数にデータ全て(req.body)を渡してあげる。これえで更新できるがexec()で繋げて、エラー時にエラーを出して、無ければトップページへリダイレクト(res.redirect())する。

execに関しては、createの時と違ってこっちを使う??
下記のサイトを参考。

https://ponzmild.hatenablog.com/entry/2018/01/08/183654

https://www.web-dev-qa-db-ja.com/ja/javascript/mongooseexec関数は何をしますか?/1054742733/

Delete(削除)

最後は削除。
ここは更新と似ている。

jsファイルでは以下のようになる

index.js
 //更新する記事データを表示する
 app.get("/blog/delete/:id",async (req, res) => {
  const singleData = await BlogModel.findById(req.params.id);
  res.render("blogDelete", { singleData });
};);

記事の削除の場合は、更新と同様に削除したい記事のIDをfindById(req.params.id)で取得してblogDelete.ejsのテンプレートにsingleDataとして渡してあげる。

views/blogDelete.ejs
 <h1>記事を削除する</h1>
<form action="/blog/delete/<%= singleBlog._id %>" method="POST">
  <h1><%= singleBlog.title %></h1>
  <p><%= singleBlog.textbody%>
  </p> <img src="/public/image/<%= singleBlog.image %>" alt="photo">
  <button type="submit">上記を削除する</button>
</form>

テンプレート側では、記事のIDさえ分かればいいので記事のIDをactionのURLに設定してPOSTとして送信する。
他の記事のタイトルや記事の本文などはユーザーにどのデータを削除するか分かるようにするために表示している。

index.js
 //削除すうる
app.post("/blog/delete/:id", async (req, res) => {
    BlogModel.deleteOne({ _id: req.params.id }).exec((error) => {
      if (error) {
        res.render("error", { message: "user/deleteのエラー" });
      } else {
        res.redirect("/");
      }
    });
  });

削除したい記事IDをdeleteOneというメソッドで、第1引数に分割代入で{ _id: req.params.id }としてmongoDBのIDを指定して渡してあげる。削除なので更新のように第2引数にデータを指定する必要はない。
その後、更新同様にexec()で繋げて、エラー時にエラーを出して、無ければトップページへリダイレクト(res.redirect())する。

まとめ

これで駆け足で、CRUDについてメモ。
mongooseでデータを取得する場合は、asyncawaitは必須だなと感じた。
これをベースとしていろいろ試していきたいと思う。

ざっと記載してみたが、おいおいはReactやNextjsと連携させていろいろやってみたい。

https://reffect.co.jp/node-js/express-jsnode-js-mongodb#MongoDB
https://reffect.co.jp/node-js/mongodb-cloud
https://qiita.com/azukiazusa/items/b2bffb0ade3434e9cd6d#ルーティングを設定する

Discussion