Node.jsとTypeScriptのプロジェクト構築: 本と作者の管理プロジェクトの依存関係と設定の解説

bookshelf パッケージの依存関係説明

ここで実装したコードの説明をします。

Dependencies

  1. Express:
    • Expressは、Node.jsで動作する軽量で柔軟なWebアプリケーションフレームワークです。Expressは、ミドルウェア、ルーティング、およびテンプレートエンジンを提供し、Webおよびモバイルアプリケーションを構築するための強力な基盤を提供します。
  2. Sequelize:
    • Sequelizeは、Node.js用の強力なORM(Object Relational Mapper)であり、SQLデータベース(PostgreSQL、MySQL、SQLite、およびMSSQLを含む)とのやり取りを容易にします。Sequelizeは、データベーススキーマの定義、データの検索と操作、およびマイグレーションのサポートを提供します。
  3. SQLite3:
    • SQLiteは、サーバーの設定が不要な軽量のディスクベースのデータベースエンジンです。SQLite3は、SQLiteデータベースの最新バージョンであり、組み込みアプリケーションやプロトタイプ制作に最適です。
  4. TypeScript:
    • TypeScriptは、JavaScriptのスーパーセットであり、静的型付けとクラスベースのオブジェクト指向プログラミングを提供します。TypeScriptは、大規模なアプリケーションの開発と保守を容易にし、型安全とエディターの支援を提供します。
  5. ts-node:
    • ts-nodeは、TypeScriptを直接実行し、リアルタイムでコンパイルするためのツールです。ts-nodeは、TypeScriptプロジェクトを簡単にテストおよび実行することを可能にします。
  6. @types/node:
    • @types/nodeは、Node.jsの型定義ファイルを提供するnpmパッケージです。これにより、TypeScriptプロジェクトでNode.jsのAPIを型安全な方法で使用することができます。
  7. @types/express:
    • @types/expressは、Expressフレームワークの型定義ファイルを提供するnpmパッケージです。これにより、TypeScriptプロジェクトでExpressのAPIを型安全な方法で使用することができます。
  8. dotenv:
    • dotenvは、アプリケーションの設定を.envファイルから簡単に読み込むためのツールです。このツールを使用することで、データベースの接続情報やAPIキーなどの機密情報をソースコードから分離し、安全に管理することができます。
  9. express-validator:
    • express-validatorは、Expressアプリケーションで受け取ったリクエストのデータを検証・整形するためのミドルウェアです。例えば、ユーザー登録フォームからの入力値が正しいフォーマットであるかチェックする際などに役立ちます。

tsconfig.json

このtsconfig.json(とは)ファイルは、プロジェクトのコンパイルに必要なルート ファイルとコンパイラ オプションを指定します。

  1. compilerOptions:
    • TypeScriptコンパイラのオプションを設定するオブジェクトです。
  2. target ("ES2022"):
    • コンパイルされたJavaScriptのターゲットとなるECMAScriptバージョンを指定します。"ES2022"は、ECMAScript 2022の機能をターゲットとすることを意味します。
  3. module ("commonjs"):
    • 出力ファイルで使用されるモジュールシステムを指定します。"commonjs"は、Node.jsで一般的に使用されるモジュールシステムです。
    • commonjs
      • 独立したモジュール: CommonJSでは、各ファイルは独立したモジュールとして扱われます。
      • デフォルトのアクセス制限: モジュール内で定義された関数や変数は、デフォルトで他のモジュールからアクセスできないようになっています。
      • エクスポートとインポート: exportsオブジェクトを使用して関数や変数をエクスポートし、require関数を使用して他のモジュールをインポートします。
  4. outDir ("./dist"):
    • コンパイルされたJavaScriptファイルを出力するディレクトリを指定します。この例では、./distディレクトリにファイルが出力されます。
  5. rootDir ("./src"):
    • TypeScriptファイルのルートディレクトリを指定します。この例では、./srcディレクトリがルートとして設定されています。
  6. strict (true):
    • 厳密な型チェックを有効にするかどうかを指定します。trueに設定すると、より厳密な型チェックが行われ、型関連のエラーをより簡単に検出できます。
  7. esModuleInterop (true):
    • ECMAScriptモジュールとの相互運用性を改善するためのフラグです。これは、import文を使用してCommonJSモジュールをインポートするときに便利です。これにより、デフォルトのインポート形式が必要なくなります。
  8. noImplicitAny (true): noImplicitAnyオプションは、TypeScriptの型チェックに関連する設定です。このオプションをtrueに設定すると、型注釈が明示的に提供されていない変数、パラメータ、または関数の戻り値に対して、TypeScriptコンパイラはany型を暗黙的に割り当てることを許可せず、エラーをスローします。

参考リンク:

データモデルの定義

Modelクラスを継承して定義する方法は3つある。


BookAttributes {
    id?: number;
    title: string;
    authorId: number;
}

// ジェネリック型のみを使用
class Book extends Model<BookAttributes> {}
// ジェネリック型とプロパティの宣言を併用
class Book extends Model<BookAttributes> implements BookAttributes {
    public id!: number;
    public title!: string;
    public authorId!: number;
}
// ジェネリック型を使用せずにプロパティのみを宣言
class Book extends Model {
    public id!: number;
    public title!: string;
    public authorId!: number;
}
  1. ジェネリック型のみを使用

    メリット:

    • シンプル: モデル定義がコンパクトであり、読みやすい。
    • 型の基本的なサポート: BookAttributes を使用して、基本的な型のサポートが提供される

    デメリット:

    • 直接アクセス時の不確実性:

      const book = new Book();
      book.title = "Sample Title"; // TypeScriptエラーが発生する可能性
      
  2. ジェネリック型とプロパティの宣言を併用

    メリット:

    • 明確な型のサポート: プロパティの明示的な宣言により、型の安全性が向上。
    • エディタのサポート: 型のヒントやコード補完が向上する。
    • コードの可読性: モデルの属性が明確に定義されており、可読性が高まる。

    デメリット:

    • 冗長性: モデルの属性を2回(インターフェースとクラス内で)定義する必要がある。
  3. ジェネリック型を使用せずにプロパティのみを宣言

    メリット:

    • 直接アクセスのサポート: インスタンスのプロパティに直接アクセスする際のサポート。

      const book = new Book();
      book.title = "Sample Title"; // 問題なく動作
      

    デメリット:

    • 型の不完全性: ジェネリック型が不在であるため、モデルに関連するメソッドや他のプロパティの型推論が不完全。
    • コードの不一致: データベースの変更や他のコードとの不一致が生じた際、この不整合を検出するのが難しくなる。

middleware

  1. errorHandler:
    1. このミドルウェアは、サーバー上でエラーが発生した場合のハンドリングを行います。エラーオブジェクトが存在すれば、そのエラーのメッセージをレスポンスとして返します。エラーオブジェクトが存在しない場合は、一般的なエラーメッセージを返します。
    2. 使い方:

        // app.ts
        app.use(errorHandler);
      
  2. asyncHandler
    1. このミドルウェアは非同期のルートハンドラやコントローラ関数で発生するエラーをキャッチし、次のミドルウェア(通常はエラーハンドリングのミドルウェア)に渡します。非同期関数内でのエラーは通常のtry-catchで取得することが難しいため、このミドルウェアはそのギャップを埋めます。
    2. 使い方

        // authorRoutes.ts
        router.get('/', asyncHandler(authorController.getAllAuthors));
      
  3. express.json():
    1. Expressの組み込みミドルウェアで、クライアントからのJSON形式のリクエストボディをJavaScriptのオブジェクトとしてパースします。
    2. 使い方

        // app.ts
        app.use(express.json());
      
  4. validateCreateAuthor
    1. author の作成時にデータのバリデーションを行います。このミドルウェアはexpress-validatorを使用していると思われます。
      1. 使い方
        // authorRoutes.ts
        router.post('/', validateCreateAuthor, asyncHandler(authorController.createAuthor));
      
        // authorController.ts
        export const validateCreateAuthor = [
            body('name').isString().notEmpty().withMessage('Name is required'),
        ];
      

データモデルの定義データベースの接続設定

  1. Sequelizeのインポート:
    1. SequelizeライブラリからSequelizeクラスをインポートしています。このクラスはデータベース接続やモデルの定義、データのクエリなどの操作を行うための主要なクラスです。
       import { Sequelize } from 'sequelize';
    
  2. データベースの接続設定:
    1. ここで新しいSequelizeインスタンスを作成しています。このインスタンスは、特定のデータベース設定(この場合はSQLite)を使用してデータベースへの接続を行います。

      export const sequelize = new Sequelize({
        dialect: 'sqlite',
        storage: './database.sqlite'
      });
      

      ※1 他のDB接続方法はドキュメント見てください。 ※2 SequelizeクラスのConstructors

    2. 使用方法:

      • このように、database.tsはアプリケーション全体で一貫したデータベース接続を提供する基盤として機能します。

         import { Model, DataTypes } from 'sequelize';
         import { sequelize } from './database.ts';
        
         class User extends Model {}
         User.init({
           username: DataTypes.STRING,
           password: DataTypes.STRING,
         }, {
           sequelize,
           modelName: 'user'
         });
        

環境変数

  1. app.ts:
    • dotenvはNode.jsで環境変数を操作するための便利なnpmパッケージです。dotenv.config()を呼び出すと、.envファイルから環境変数を読み込み、それをprocess.envに追加します。
      dotenv.config();
    
  2. environment.ts:
    • isProductionはヘルパー関数で、現在の環境が本番環境(production)かどうかを確認します。この関数は、process.env.NODE_ENVの値がproductionと等しい場合にtrueを返します。
      export function isProduction(): boolean {
          return process.env.NODE_ENV === 'production';
      }
    
  3. .env:
    • .envファイルは、環境変数を定義するためのファイルです。このファイル内で設定された変数は、dotenv.config()が呼び出されると、process.envに追加されます。
    • ここでは、NODE_ENV変数がdevelopとして設定されています。このため、上記のisProduction関数はfalseを返します。
      NODE_ENV=develop
    

サーバの起動

説明:

  • isProduction()関数によって現在の環境が本番環境かどうかを確認しています。
  • 本番環境の場合、アプリケーションはポート3000で直接起動します。
  • 本番環境以外の場合(例: 開発環境)、Sequelizeを使ってデータベースとの同期を行った後にサーバを起動します。コメントアウトされている部分はテーブルの再作成オプション(force: true)を示していますが、このオプションは現在は使用されていません。代わりにalter: trueオプションが使われており、これによってデータベースのテーブルの現在の状態を確認し、必要な変更を適用することができます。
if (isProduction()) {
    app.listen(3000, () => {
        console.log('Server is running on port 3000');
    });
} else {
    /*
    sequelize.sync({ force: true }).then(() => {
        // テーブルを作成し、既に存在する場合は最初に削除します
        app.listen(3000, () => {
            console.log('Server is running on port 3000');
        });
    });
    */
    // または

    sequelize.sync({ alter: true }).then(() => {
        // データベース内のテーブルの現在の状態を確認し、必要な変更を適用します
        app.listen(3000, () => {
            console.log('Server is running on port 3000');
        });
    });
}

sequelize.sync()

sequelize.sync()はSequelizeを使用したNode.jsアプリケーションで、モデル定義とデータベースのスキーマとの間の同期をとるためのメソッドです。この同期は以下のような動作を含むことがあります:

  • テーブルの作成
  • テーブル構造の変更
  • テーブルの削除

以下が、sequelize.sync()を本番環境で使用しない主な理由です:

  1. 安全性:sequelize.sync({ force: true })のようにforceオプションをtrueにすると、既存のテーブルが削除され、新しくテーブルが作成されます。これは、本番環境のデータを完全に失うリスクがあります。