Express触ってたら例外処理でハマったのでメモを残しておきます。そんなに大した理由ではありませんでした。
先に結論
ドキュメントをちゃんと読む
前提
Node.js 14.15.3
Express 4.17.1
Mongoose 5.11.9
express-async-errors 3.1.1
起こったこと
所謂MERN stackを学習中なのですが、ユーザーを作成してDBに保存するAPIを作成していました。こんな感じのよくチュートリアルにあるようなやつです
constusersRouter=require('express').Router()constUser=require('../models/user')constbcrypt=require('bcrypt')usersRouter.get('/',async(request,response)=>{constusers=awaitUser.find({})response.json(users)})/**
* create new user
*/usersRouter.post('/',async(request,response)=>{constbody=request.bodyconstsaltRounds=10constpasswordHash=awaitbcrypt.hash(body.password,saltRounds)constuser=newUser({username:body.username,name:body.name,password:passwordHash})constsavedUser=awaituser.save()response.json(savedUser)})module.exports=usersRouter
で、usernameのユニークバリデーションを確認するためにちょっとPostmanでPOSTリクエスト送ってみたのですが、全然レスポンスが返ってきませんでした。
確認すると以下のようなエラーが。
(node:32647) UnhandledPromiseRejectionWarning: ValidationError: User validation failed: username: Error, expected `username` to be unique. Value: `ichiro`
at model.Document.invalidate (/Users/xxx/projects/bloglist/node_modules/mongoose/lib/document.js:2693:32)
at /Users/xxx/projects/bloglist/node_modules/mongoose/lib/document.js:2513:17
at /Users/xxx/projects/bloglist/node_modules/mongoose/lib/schematype.js:1241:9
at processTicksAndRejections (internal/process/task_queues.js:75:11)
(node:32647) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:32647) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
try catchしろよという話なのですが問題はそこではなくて、express-async-errorsをインストールしていたのにこのエラーが出たという点です。
express-async-errorはExpressでasync/awaitを使用する際にtry catchを書かなくてもよしなにやってくれるようなライブラリです。
https://www.npmjs.com/package/express-async-errors
意図していた挙動
以下のようなエラーハンドリング用のミドルウェアを定義していたので、バリデーションエラーの場合はステータスコード400でエラーメッセージが返ってくる想定でした。
consterrorHandler=(error,request,response,next)=>{if(error.name==='ValidationError'){returnresponse.status(400).json({error:error.message})}next(error)}module.exports={errorHandler}
で、上記のmiddlewareはapp.jsというファイルでuseしているので適用されるはずです。
constexpress=require('express')constapp=express()constmongoose=require('mongoose')constcors=require('cors')constblogsRouter=require('./controllers/blogs')constusersRouter=require('./controllers/users')require('express-async-errors')//<- express-async-errorsはここでrequireconstconfig=require('./utils/config')constmiddleware=require('./utils/middleware')mongoose.connect(config.MONGODB_URL,{useNewUrlParser:true,useUnifiedTopology:true,useFindAndModify:false,useCreateIndex:true})app.use(cors())app.use(express.json())app.use('/api/blogs',blogsRouter)app.use('/api/users',usersRouter)app.use(middleware.errorHandler)//<-これmodule.exports=app
ちなみにエントリーポイントのindex.jsはこういう感じです。ここは特に問題なさそう
consthttp=require('http')constapp=require('./app')constconfig=require('./utils/config')constserver=http.createServer(app)server.listen(config.PORT,()=>{console.log(`Server running on port ${config.PORT}`)})
app.jsがどうも怪しそうなのですが、ミドルウェアのuseの順番もとくに問題なさそうですし、Expressのドキュメントを色々確認してみてもいまいち原因が掴めないまま時間が過ぎました。
で、そもそもexpress-async-errorsの使い方が何か間違っているのではと思い始め、express-async-errorsのドキュメントを確認しました。すると
Then require this script somewhere before you start using it
修正
constexpress=require('express')require('express-async-errors')constapp=express()constmongoose=require('mongoose')constcors=require('cors')constblogsRouter=require('./controllers/blogs')constusersRouter=require('./controllers/users')constconfig=require('./utils/config')constmiddleware=require('./utils/middleware')mongoose.connect(config.MONGODB_URL,{useNewUrlParser:true,useUnifiedTopology:true,useFindAndModify:false,useCreateIndex:true})app.use(cors())app.use(express.json())app.use('/api/blogs',blogsRouter)app.use('/api/users',usersRouter)app.use(middleware.errorHandler)module.exports=app
ドキュメントをちゃんと読んでない&そもそもNode.jsのrequireの仕組みについて理解が浅いというのが招いたミスでした。