サーバーサイド環境構築(Node.js + Typescript)

## ss環境構築

##### まっさらな状態からcurlを叩いて「Hello World」を表示する

## src > app.ts

    import express from "express"

    const app = express()

    app.use(express.json())

    app.get("/", async (_req, res) => {
      res.end("Hello World")
    })

    const port = 8080
    app.listen(port, () => {
      // eslintでconsoleのエラーが出ないよう下記を追加
      // eslint-disable-next-line no-console
      console.log(`Listening on port ${port}`)
    })

## dependencies

#### コマンド
`yarn add express`：サーバーサイド Javascript の実行環境
`yarn add dotenv`：見せれない情報を.env ファイルで管理することで公開させない
`yarn add winston`：複数のトランスポート（出力先）をサポートするロギングライブラリ

## devDependencies

#### コマンド
`yarn add -D @types/express`：expressの開発にtsを利用する
`yarn add -D @types/node`：ts を使う時、node_modules の型定義ファイル
`yarn add -D cspell`：スペルチェック → cspell.json 手書き、cspell.txt 作成
`yarn add -D eslint`：単純な構文エラーやプロジェクト固有のコーディング規約を定義することができる
→ 自由にルールを設定できる、rules で上書きすることでルールを緩くできる(.eslintrc.js)
`yarn add -D eslint-config-prettier`：Prettierと競合する可能性のあるルールをすべてオフにする
`yarn add -D fixpack`：package.json内を並び替え
`yarn add -D prettier`：ソースコードを整形してくれるツール(コードフォーマッター)
　　　　　　　　　　　　　　→ eslint では整形できないコードを整形できる
`yarn add -D ts-node`：ts を毎回 tsc コマンドを叩いて js にコンパイルことをしなくてもよくするやつ
`yarn add -D ts-node-dev`：ts ファイルを js ファイルにコンパイルすることなく、起動することができ、 さらに監視モードで素早く再起動が使用できる
`yarn add -D typescript`：Typescriptの使用

## tsconfig.json
`yarn run tsc --init`でjsonを作成

    {
      "compilerOptions": {
        "target": "ES2021",
        "lib": ["ES2021"],
        "module": "commonjs",
        "moduleResolution": "node",
        "resolveJsonModule": true,
        "outDir": "./dist",
        "noEmit": true,
        "downlevelIteration": true,
        "isolatedModules": true,
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "noImplicitAny": true,
        "strictNullChecks": true,
        "strictFunctionTypes": true,
        "strictBindCallApply": true,
        "strictPropertyInitialization": true,
        "noImplicitThis": true,
        "useUnknownInCatchVariables": true,
        "alwaysStrict": true,
        "noUnusedLocals": true,
        "noUnusedParameters": true,
        "exactOptionalPropertyTypes": true,
        "noImplicitReturns": true,
        "noFallthroughCasesInSwitch": true,
        "noUncheckedIndexedAccess": true,
        "noImplicitOverride": true,
        "noPropertyAccessFromIndexSignature": true,
        "allowUnusedLabels": true,
        "allowUnreachableCode": true
      },
      "exclude": ["node_modules"],
      "include": ["**/*.ts"]
    }

## cspell.json
    {
      "version": "0.2",
      "language": "en",
      "dictionaries": ["en", "typescript"],
      "dictionaryDefinitions": [
        {
          "name": "en",
          "path": "./cspell.txt"
        }
      ],
      "ignorePaths": ["node_modules/**"]
    }

## .eslintrc.js

    const OFF = 0; // eslint-disable-line no-unused-vars
    const WARN = 1; // eslint-disable-line no-unused-vars
    const ERROR = 2; // eslint-disable-line no-unused-vars

    module.exports = {
      root: true,
      extends: [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier",
      ],
      env: {
        node: true,
        es2021: true,
      },
      rules: {
        camelcase: OFF,
        "no-console": ERROR,
        "import/prefer-default-export": OFF,
        "no-underscore-dangle": OFF,
        "@typescript-eslint/camelcase": OFF,
      },
      settings: {
        "import/resolver": {
          node: {
            extensions: [".ts", ".js"],
          },
        },
      },
    };

## .eslintignore

    .eslintrc.js
    /node_modules
    .cache/*
    dist/*

## .fixpackrc

    {
      "sortToTop": [
        "name",
        "version",
        "author",
        "private",
        "main",
        "scripts",
        "dependencies",
        "devDependencies",
        "browser"
      ]
    }

## prettier.config.js

    module.exports = {
      printWidth: 120,
      semi: false,
      trailingComma: "all",
    };


## .gitignore

gitで管理するためignore入れてます。

    .env
    /node_modules
    .DS_Store
    credential.json
    /.vscode

## ロガーのディレクトリ名 > index.ts, type.ts

##### index.ts

    import { createLogger, format, transports } from "winston"
    import { CreateCustomLoggerData, CustomInfoLogger, CustomErrorLogger, InitLoggerReturn } from "./type"

    const displayFormat = format.printf(({ level, message }) => {
      return `${level}: ${message}`
    })
    const color = format.colorize({ all: true })
    /**
     * @typedef InitLogger
     * @description ロガーのインスタンス生成
     * @param filePath ログ実行時ファイルパス
     * @returns InitLoggerReturn
     */
    type InitLogger = (filePath: string) => InitLoggerReturn
    export const initLogger: InitLogger = (filePath) => {
      const createCustomLoggerData: CreateCustomLoggerData = (logLevel, message, data) => {
        const logger = createLogger({
          level: logLevel,
          format: format.combine(color, displayFormat),
          transports: [new transports.Console()],
        })

        return {
          logger,
          msg: JSON.stringify({
            message,
            data,
            filePath,
          }),
        }
      }

      const infoLogger: CustomInfoLogger = (message, data) => {
        const { logger, msg } = createCustomLoggerData("info", message, data)
        logger.info(msg)
      }
      const errorLogger: CustomErrorLogger = (message, error) => {
        const stringError = JSON.stringify(error, Object.getOwnPropertyNames(error))

        const { logger, msg } = createCustomLoggerData("error", message, { error: stringError })
        logger.error(msg)
      }

      return { infoLogger, errorLogger }
    }

##### type.ts

    import { Logger } from "winston"

    /**
     * @typedef CustomLogger
     * @description カスタムロガーの型定義
     * @param message ログメッセージ
     * @param data ログ出力データ
     */
    export type CustomInfoLogger = (message: string, data?: Record<string, unknown>) => void
    export type CustomErrorLogger = (message: string, error: unknown) => void
    /**
     * @typedef InitLoggerReturn
     * @description InitLoggerの戻り値の型
     */
    export type InitLoggerReturn = {
      infoLogger: CustomInfoLogger
      errorLogger: CustomErrorLogger
    }

    /**
     * @typedef CreateCustomLoggerDataReturn
     * @description CreateCustomLoggerDataの戻り値の型
     */
    export type CreateCustomLoggerDataReturn = { logger: Logger; msg: string }

    /**
     * winston準拠のログレベル
     */
    type LogLevel = "error" | "warn" | "info" | "http" | "verbose" | "debug" | "silly"

    /**
     * @typedef CreateCustomLoggerData
     * @description カスタムロガーと文字列化された出力メッセージ
     * @param logLevel ログレベル
     * @param message メインメッセージ
     * @param data オプショナルデータ
     */
    export type CreateCustomLoggerData = (
      logLevel: LogLevel,
      message: string,
      data?: Record<string, unknown>,
    ) => CreateCustomLoggerDataReturn

###　使う時

下記のようにimportして使うことができる。(console使うとeslintエラー出るのでwinstonを使用)

    import { initLogger } from "./shared/initLogger"

    const { infoLogger, errorLogger } = initLogger("src/index.ts")

    infoLogger("infoLoggerです")

    errorLogger("errorLoggerです", { error: "失敗" })

## 最後に

`yarn run ts-node-dev src/app.ts`でサーバーが立ち上がる。
package.jsonのscriptに下記の設定をすることで`yarn dev`のコマンドで上記と同じように起動する。
`"dev": "ts-node-dev src/app.ts"`

`Listening on port 8080`が表示されたら、別のターミナルで
`curl http://localhost:8080`を叩くと「Hello World」が表示されます！

これで最低限の設定が完了し、実装に進められます。