Expressでエラーハンドリングを実施する。

#アジェンダ
標題通り、Expressのエラーハンドリングの実施方法。githubに挙がっていたソースコードを元にエラーハンドリングを実施

#ソースコード

```javascript:ディレクトリ構成
src
├── error-handler
│   ├── error-code.ts
│   ├── error-exception.ts
│   ├── error-handler.ts
│   └── error-model.ts
└── server.ts
```

```javascript:error-code.ts
import express, { NextFunction } from 'express';
import { Request, Response } from 'express';
import { ErrorCode } from './error-handler/error-code';
import { ErrorException } from './error-handler/error-exception';
import { errorHandler } from './error-handler/error-handler';

const app = express();

app.get('/', (req: Request, res: Response) => {
  res.send('Application works!');
});

app.get('/throw-unauthenticated', (req: Request, res: Response, next: NextFunction) => {
  throw new ErrorException(ErrorCode.Unauthenticated);
  // or
  // next(new ErrorException(ErrorCode.Unauthenticated))
});
app.get('/throw-maximum-allowed-grade', (req: Request, res: Response, next: NextFunction) => {
  throw new ErrorException(ErrorCode.MaximumAllowedGrade, { grade: Math.random() });
  // or
  // next(new ErrorException(ErrorCode.MaximumAllowedGrade, { grade: Math.random() }))
});
app.get('/throw-unknown-error', (req: Request, res: Response, next: NextFunction) => {
  const num: any = null;
  // Node.js will throw an error because there is no length property inside num variable
  console.log(num.length);
});

const someOtherFunction = () => {
  const myPromise = new Promise((resolve, reject) => {
    setTimeout(() => {
      reject(new ErrorException(ErrorCode.AsyncError));
    }, 1000);
  });
  return myPromise;
};
app.get('/throw-async-await-error', async (req: Request, res: Response, next: NextFunction) => {
  // express 4
  try {
    await someOtherFunction();
  } catch (err) {
    next(err);
    // next line will not work as expected
    // throw err
  }
  // express 5
  // await someOtherFunction();
});

app.use(errorHandler); // registration of handler

app.listen(3000, () => {
  console.log('Application started on port 3000!');
});
```

```javascript:error-code.ts
export class ErrorCode {
  public static readonly Unauthenticated = 'Unauthenticated';
  public static readonly NotFound = 'NotFound';
  public static readonly MaximumAllowedGrade = 'MaximumAllowedGrade';
  public static readonly AsyncError = 'AsyncError';
  public static readonly UnknownError = 'UnknownError';
}
```

```javascript:error-exception.ts
import { ErrorCode } from './error-code';

export class ErrorException extends Error {
  public status: number = null;
  public metaData: any = null;
  constructor(code: string = ErrorCode.UnknownError, metaData: any = null) {
    super(code);
    Object.setPrototypeOf(this, new.target.prototype);
    this.name = code;
    this.status = 500;
    this.metaData = metaData;
    switch (code) {
      case ErrorCode.Unauthenticated:
        this.status = 401;
        break;
      case ErrorCode.MaximumAllowedGrade:
        this.status = 400;
        break;
      case ErrorCode.AsyncError:
        this.status = 400;
        break;
      case ErrorCode.NotFound:
        this.status = 404;
        break;
      default:
        this.status = 500;
        break;
    }
  }
}
```

```javascript:error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { ErrorCode } from './error-code';
import { ErrorException } from './error-exception';
import { ErrorModel } from './error-model';

export const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
  console.log('Error handling middleware called.');
  console.log('Path:', req.path);
  console.error('Error occured:', err);
  if (err instanceof ErrorException) {
    console.log('Error is known.');
    res.status(err.status).send(err);
  } else {
    // For unhandled errors.
    res.status(500).send({ code: ErrorCode.UnknownError, status: 500 } as ErrorModel);
  }
};
```

```javascript:error-model.ts
export class ErrorModel {
  /**
   * Unique error code which identifies the error.
   */
  public code: string;
  /**
   * Status code of the error.
   */
  public status: number;
  /**
   * Any additional data that is required for translation.
   */
  public metaData?: any;
}
```


#実行結果
![スクリーンショット 2021-12-10 13.49.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/599049/24231c6c-9fa3-23d8-e01b-e1277b83a235.png)

#所感
　エラーハンドリングをライブラリで管理できるのは便利。ただ、改善ポイントが複数あるので、個別カスタマイズか、別のライブラリを探してみるのもアリかと。

■改善箇所。
　・HTTPステータスとcodeが一致しているので、冗長。
　・障害発生時、一意に確認できるコードを付与すべき({errorCode:'ERR001'})
　・エラーエクセプションのswitch文が冗長。エラーコードとステータスは一緒に定義した方が管理しやすい。

#参照ソースコード
https://github.com/nisicadmir/nodejs-typescript