FullStackOpen Part3-c Saving data to MongoDB メモ

Debugging Node applications

結局console.log
VSCodeのデバッガを使ったり、Chromeの開発ツールで以下のコマンド
node --inspect index.js

MongoDB

ドキュメントデータベースであるMongoDBを使用

ドキュメントとは
MongoDBではBSON(JSONのバイナリ表現)で保存される
データは以下のようにフィールドと値のセットで保存
オブジェクトや配列なども入力できる
{
 name: "sue",
 age: 25,
 status: "A",
 groups: [ "news", "sports"]
}
デフォルトで_idキーを持っている

コレクション
ドキュメントの集まり

MongoDB Atlasを使用

以下のコマンドでインストール
npm install mongoose

以下のような形で接続とスキーマを作成する

const mongoose = require('mongoose')

if (process.argv.length<3) {
  console.log('give password as argument')
  process.exit(1)
}

const password = process.argv[2]
const databaseName = 'noteApp'

const url =
  `mongodb+srv://fullstack:${password}@cluster0.o1opl.mongodb.net/${databaseName}$?retryWrites=true&w=majority`

mongoose.set('strictQuery',false)
mongoose.connect(url)

const noteSchema = new mongoose.Schema({
  content: String,
  important: Boolean,
})

const Note = mongoose.model('Note', noteSchema)

const note = new Note({
  content: 'HTML is Easy',
  important: true,
})

note.save().then(result => {
  console.log('note saved!')
  mongoose.connection.close()
})

Schema

MongoDBに接続した後はスキーマとモデルを定義する

const noteSchema = new mongoose.Schema({
  content: String,
  important: Boolean,
})

const Note = mongoose.model('Note', noteSchema)

スキーマ(noteSchema) -> モデル(Note) -> インスタンス(note)

接続が終わったらmongoose.connection.close()で切れる

Fetching objects from the database

findを使う。thenと組み合わせられる

Note.find({}).then(result => {
  result.forEach(note => {
    console.log(note)
  })
  mongoose.connection.close()
})

クエリ条件はオブジェクトでfindに渡す
find({important: true}).then()など

Connecting the backend to a database

スキーマに対しset関数を実行し、Noteオブジェクトに対し_idをStringとして返させたり、__vを非表示にすることが可能。
'toJSON'をキーとして、transformに変更する内容を記述

noteSchema.set('toJSON', {
    transform: (document, returnedObject) => {
        returnedObject.id = returnedObject._id.toString()
        delete returnedObject._id
        delete returnedObject.__v
    }
})

Database configuration into its own modules

MongoDBに接続し、Noteモジュールを作成するところまでリファクタリングする

const mongoose = require('mongoose')
require('dotenv').config()

mongoose.set('strictQuery', false)

const url = process.env.MONGODB_URI
console.log('connecting to the mongo db...')

mongoose.connect(url)
    .then(result => {
        console.log('connected!')
    })
    .catch((error) => {
        console.log('error connecting to db', error.messasge)
    })

const noteSchema = new mongoose.Schema({
    content: String,
    important: Boolean,
})

noteSchema.set('toJSON', {
    transform: (document, returnedObject) => {
        returnedObject.id = returnedObject._id.toString()
        delete returnedObject._id
        delete returnedObject.__v
    }
})

module.exports = mongoose.model('Note', noteSchema)

module.exportsを使用することで、note.js内の他の変数へのアクセスを制限し、モデルのみを提供している
module.exports = mongoose.model('Note', noteSchema)

アプリケーション内の環境変数を.envファイル内で管理
npm install dotenv

環境変数へのアクセスは以下を記述
require('dotenv').config()
const url = process.env.MONGODB_URI

.envファイルは以下のような記法

MONGODB_URI=mongodb+srv://fullstack:<password>@cluster0.o1opl.mongodb.net/noteApp?retryWrites=true&w=majority

PORT=3001

.envファイルはGithub上に上げないこと!(gitignoredに入れる)

Important note to Fly.io users

Fly.ioでのパスワード管理は"fly secrets set"を使う
fly secrets set MONGODB_URI="…"

.dockerignoreにも.envを追加しておく

Using database in route handlers

Note.findById関数でid検索をする。
request.params.idを引数にとる

Error Handling

存在しないIDのnoteを見つけようとしたときの処理
存在しないnoteだと404をthen内で返す
IDのフォーマットがおかしい場合はcatchで400を返す

app.get('/api/notes/:id', (request, response) => {
  Note.findById(request.params.id)
    .then(note => {
      if (note) {
        response.json(note)
      } else {
        response.status(404).end()
      }
    })
    .catch(error => {
      console.log(error)
      response.status(400).send({error: 'Malformatted id'})
    })})

Moving error handling into middleware

Expressでは以下のような形でエラーハンドリングをミドルウェアに任せる
ただしapp.use(errorHandler)は最後に記述する(app.listenの前くらい)

const errorHandler = (error, request, response, next) => {
  console.error(error.message)

  if (error.name === 'CastError') {
    return response.status(400).send({ error: 'malformatted id' })
  } 

  next(error)
}

// this has to be the last loaded middleware.
app.use(errorHandler)

api/notes/:idはこんな感じに変更

app.get('/api/notes/:id', (request, response, next) => {  Note.findById(request.params.id)
    .then(note => {
      if (note) {
        response.json(note)
      } else {
        response.status(404).end()
      }
    })
    .catch(error => next(error))})

The order of middleware loading

正しい順序はこんな感じ
unknownEndpointやerrorHandlerはエンドポイントのget, postの後で

app.use(express.static('build'))
app.use(express.json())
app.use(requestLogger)

app.post('/api/notes', (request, response) => {
  const body = request.body
  // ...
})

const unknownEndpoint = (request, response) => {
  response.status(404).send({ error: 'unknown endpoint' })
}

// handler of requests with unknown endpoint
app.use(unknownEndpoint)

const errorHandler = (error, request, response, next) => {
  // ...
}

// handler of requests with result to errors
app.use(errorHandler)

Other Operations

IDを指定して削除するのはfindByIdAndRemove (or findByIdAndDelete)
statusは204 no contentを指定

app.delete('/api/notes/:id', (request, response, next) => {
  Note.findByIdAndRemove(request.params.id)
    .then(result => {
      response.status(204).end()
    })
    .catch(error => next(error))
})

更新findByIdAndUpdate
findByIdAndUpdateの引数にとるnoteオブジェクトはJavascriptの通常のオブジェクトであり、Noteモデルから作成されたものではないことに注意!
また{new: true}を渡すことでthenイベントハンドラー中で使用できるupdatedNoteが更新後のものになる(デフォルトでは更新前のもの)

app.put('/api/notes/:id', (request, response, next) => {
  const body = request.body

  const note = {
    content: body.content,
    important: body.important,
  }

  Note.findByIdAndUpdate(request.params.id, note, { new: true })
    .then(updatedNote => {
      response.json(updatedNote)
    })
    .catch(error => next(error))
})

A true full stack developer's oath

I will keep an eye on the database: does the backend save data there in the right format


この記事が気に入ったらサポートをしてみませんか?