【Node.js】Expressで存在しないパスにアクセスした時の404エラーハンドリング【Inversify Express Utils】



## 初めに

Expressのエラーハンドラは存在しないパスにアクセスした時のエラー(404)はハンドリングしてくれません。
じゃあどうしたらいいか？ を調べたのでここにまとめます。


## サンプルファイル
ここにExpressを使ったserver.tsファイルを用意します。
ポート3000でサーバを起動し、/usersというルータのみ用意しています。

```js
import express from "express"
const app: express.Express = express()

// サーバ起動
app.listen(3000, () => {
  console.log("Start on port 3000.")
})

// ユーザ一覧
app.get("/user", (req: express.Request, res: express.Response) => {
  res.send("OK")
})

```

## エラーハンドラを定義してみる
Expressでデフォルトのエラーハンドラをミドルウェアに用意するなら一番下に以下を追記するといいです。
参考: [Express エラー処理](https://expressjs.com/ja/guide/error-handling.html)
```js
app.use((err, req, res, next) => {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})
```
(↑引用なのでjsです)

しかしこれだと、存在しないパスにアクセスしたときの404エラーはキャッチしてくれないようです。
試しにサーバを起動して、適当なパスにアクセスしてみます。
エラーハンドラを通っていれば　「Something broke!」　と表示されるはずですが、結果はそうではありません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284077/cf66cfb2-806d-3c76-bf70-b3380d46ff5f.png)

## 404をハンドリングするには
こういう時は、ルーティングの一番最後になんでも通すルーターを用意して、
そこにアクセスが来たら４０４ページを表示するなどしてハンドリングしたらいいみたいです。

```js
// 404ハンドラー
app.all("*", (req, res) => {
  res.send("404!")
})
```
(↑引用なのでjsです)

もう一度同じページにアクセスしてみると、きちんとハンドリングできたのがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/284077/c28a736f-ffff-61d2-5c7b-c9222fa484aa.png)

## まとめ
まとめると以下のファイルになります。

```js
import express from "express"
const app: express.Express = express()

// サーバ起動
app.listen(3000, () => {
  console.log("Start on port 3000.")
})

// ユーザ一覧
app.get("/user", (req: express.Request, res: express.Response) => {
  res.send("OK")
})

// 404ハンドラー
app.all("*", (req, res) => {
  res.send("404!")
})

// エラーハンドラ
app.use(
  (
    err: Error,
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) => {
    console.error(err.stack)
    res.send("Something broke!")
  }
)

```

これで存在しないpathにアクセスした時の404エラーハンドリングができるようになりました。

## おまけ Inversify Express Utilsを使ったケース

普段はDIライブラリの[Inversify Express Utils](https://github.com/inversify/inversify-express-utils)を使っています。
この場合は上記のやり方でルーティングを定義すると、@controllerで定義したルーティングよりも `app.all()` で定義したルーティングが優先されてしまったりでうまく動きません。
そういう時は[こちらのISSUE](https://github.com/inversify/InversifyJS/issues/782)を参考に以下のように書いてください。

```js
@controller("*")
class NotFoundController {
  @httpGet("/")
  public async index() {
    return Promise.resolve("not found!");
  }
}

@controller("/")
class HomeController {

  @httpGet("/")
  public index() {
    return Promise.resolve("home!");
  }
}

const container = new Container();
const server = new InversifyExpressServer(container);
const app = server.build();

app.listen(3000, () => {
  console.log("Listening on port 3000...");
});
```

ポイントは `@controller("*")` を**一番先頭**に定義することです。
ミドルウェアを使ったときは一番最後に定義しましたが、inversify-express-utilsを使うときは**逆**なのでお気をつけください。




## 参考にさせていただきました
- [Node.js + Express + EJS でエラーページハンドリング](http://dotnsf.blog.jp/archives/1071533178.html)
- [How to Redirect 404 errors to a page in Express.js ?](https://www.geeksforgeeks.org/how-to-redirect-404-errors-to-a-page-in-express-js/)
- [Controller/Route Ordering #782](https://github.com/inversify/InversifyJS/issues/782)