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