Open12

NestJS

jyasukawajyasukawa

vite
vue
typescript
Docker
に加えて
NestJS
MySQL
を使ったTodoアプリ作成のおおまかなセットアップ

ディレクトリ構造
project-root/
│
├── frontend/
│   ├── Dockerfile
│   └── (その他のVueプロジェクトファイル)
│
├── server/
│   ├── Dockerfile
│   └── (その他のNestJSプロジェクトファイル)
│
├── docker-compose.yml
└── .env
     .gitignore →.DS_Storeはいれておく
  1. https://zenn.dev/link/comments/875f82bf330b77 を参照にfrontendファイルを作成
    2. [cd ../server]
  2. [npm i -g @nestjs/cli]
    [nest new .] ー> .gitが作成されプッシュできなくなる可能性あり-> オプションありそう
  3. [npm install --save @nestjs/typeorm typeorm mysql2 class-validator class-transformer]
    MySQL への接続に必要なパッケージのインストール
    注)serverディレクトリに移動してから
    modulesは.gitignoreにいれておくべき
    ルートディレクトリには何もおきたくない
  4. Dockerfile,docker-compose.yml, .envを作成
  5. [docker-compose up --build]
jyasukawajyasukawa

#メモ
データベース管理はsequal ace が楽!

CLIでのデータベースアクセス
[docker-compose exec -it db bash]
[mysql -u root -p]
[SHOW DATABASES;]
[USE nest_db;]
[SHOW TABLES;]

jyasukawajyasukawa

「NestJS の DI とは?」

NestJS の DI(Dependency Injection)(依存性の注入)は、モジュール、コントローラー、サービスなどのクラスの依存関係を自動的に解決する仕組み。具体的には、あるクラスが別のクラスを必要とするとき、NestJS が自動的にそのインスタンスを生成し、クラスに注入してくれる。
NestJS ではクラス間の依存関係を管理するために使用される設計パターン。このパターンを使うことで、コードの再利用性、テストの容易さ、管理のしやすさが向上する。

例えば、サービスクラスをコントローラーで利用するとき、手動でインスタンス化(new など)をせずに、NestJS の DI コンテナを介して注入を行う。

jyasukawajyasukawa

1.Controllerファイルを作る

Controllerの役割は、クライアントからのリクエストを受け付け、クライアントにレスポンスを返すこと。
Controllerには具体的な処理は書かずServiceに実装するため、原則ルーティングとServiceを呼び出す記述のみを記載します。Controllerファイルの作成方法は、Nest CLIを使うか手動で作成するかの2通り。
Nest CLIを使う場合は、以下のコマンドを実行する。

nest generate controller(または、nest g co)
例)
nest generate controller cat
nest generate service cat
nest generate module cat

手動で作成する場合には、任意のファイル名で「.controller.ts」という拡張子を付けて保存。Controllerファイルを作成すると、ルーティングの設定ができるようになる。

jyasukawajyasukawa

2.Serviceファイルを作る

NestJSでロジックを定義するファイルが、Serviceファイル。Serviceファイルは、Controllerファイルから呼び出されて具体的な処理をおこなう。
具体的な処理とは、データベースへのアクセスや外部APIの呼び出し、バリデーションやエラーハンドリングなど。また、Serviceファイルを作る際は、DIの概念を理解する必要がある。つまり、「依存関係にあるオブジェクトを外部から渡すようにしなければいけない」ということです。

Serviceファイルを書く例。

import { Injectable } from '@nestjs/common';

@Injectable()
export class UserService {
  getUsers(): string[] {
    return ['John', 'Doe', 'Mary'];
  }
}

まず、nestjs/commonパッケージから、Injectableデコレータをインポートする。その後、Injectableデコレータ(@Injectable())をクラスに付与する。

Injectableデコレータ(@Injectable())は、NestJSがServiceクラスのインスタンスを作成するために必要なマーカー。デコレータを付与することで、Serviceクラス内で他のInjectableクラスをDIできる。

DIすることで、テストや本番環境によって異なる実装が必要な場合でも、依存元のプログラム(例:Controller)を書き換えず依存先のプログラム(例:Service)を切り替えられる。その結果、再利用性や保守性も高まる。

jyasukawajyasukawa

3.Repositoryを作る

NestJSでは、データベースへのアクセスや操作するためにRepositoryというファイルを作成する。Repositoryは、データベースのテーブル内容を定義したEntityというファイルと紐づけられる。Repositoryには、EntityRepositoryデコレータ(@EntityRepository())を付与し、引数にEntityを渡す仕組み。

たとえば、Userというテーブルに対応するUserEntityとUserRepositoryは、以下のようになります。

// UserEntity
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @Column()
  email: string;
}
// UserRepository
import { EntityRepository, Repository } from 'typeorm';
import { User } from '../entities/user.entity';

@EntityRepository(User)
export class UserRepository extends Repository {}

「Repositoryはデコレータにテーブル情報を渡し、データベース処理を記述するもの」

jyasukawajyasukawa

4.Module化する

ControllserファイルとServiceファイル、Repositoryが作れたら、次はModule化を行う。Module化するコードの例は、以下。

// cats.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService], 
})
export class CatsModule {}

providersで指定するのは、ServiceなどのInjectableデコレータ(@Injectable())がついたクラス。controllersでは、Controllerデコレータがついたクラスを指定。importsでRepositoryといった、モジュールで使いたい外部モジュールを指定。

jyasukawajyasukawa

5.appモジュールに登録する

モジュールを作成したら、appモジュールに登録する処理を記述。appモジュールとは、作成したすべてのモジュールを記述する役割があり、登録方法の記述は以下。

// app.module.ts

import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule], // CatsModuleをインポートして、AppModuleに登録する
})
export class AppModule {}
jyasukawajyasukawa

「TypeORM マイグレーションの設定」

プロジェクトのルート (server) に data-source.ts を作成し、以下の内容を追加する。

import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
    type: 'mysql', //データベースの種類(例: mysql, postgres)
    host: 'localhost', 
    port: 3306,
    username: 'root',
    password: 'root',
    database: 'nest_db', // データベース名
    entities: ['src/**/entity/*.ts'],
    migrations: ['src/**/migration/*.ts'],
    synchronize: false,  // 本番環境では常に false を設定
});

以下のコマンドでマイグレーションファイルを作成。

[npx typeorm migration:create src/migrations/CreateTaskTable]

マイグレーションファイルを編集

マイグレーションをデータベースに適用

[npx ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js migration:run -d data-source.ts]
jyasukawajyasukawa

「フロントとサーバーとの接続」

VueでlocalStorageを使わずに、バックエンドのNestJSとMySQLと連携する方法。
具体的には、HTTPリクエストを使って、フロントエンドとバックエンド間でデータをやり取りする。

1.バックエンド (NestJS) API 作成

NestJSでTodoアプリ用のAPIを構築し、フロントエンドがアクセスできるエンドポイントを作成。基本的にはCRUD(Create, Read, Update, Delete)操作が必要。

2.フロントエンド (Vue + TypeScript) からHTTPリクエストを送る

axiosを使って、フロントエンドからバックエンドにリクエストを送る
(まず、Axiosをインストールします。)

npm install axios
Vueコンポーネント内でのAxiosの使用例)
const response = await axios.get<Task[]>('/task');

3. CORSの設定 今はcorsよりproxyが主流!

フロントエンドが別のポートで動作している場合、NestJS側でCORSを有効にする必要がある。

例)
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableCors();  // CORSを有効化
  await app.listen(3000);
}
bootstrap();

注) サーバー再起動、デバック状態なら解除

jyasukawajyasukawa

proxyの設定(例)

vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  server: {
    host: true, // または host: '0.0.0.0' これにより外部アクセスを許可
    port: 8080, // コンテナ内でのポート番号
    proxy: {
      '/task': {
        target: 'http://server:3000', // NestJSバックエンドのURL
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/task/, '/task'), // 必要に応じてパスを変更
      },
    },
    },
  optimizeDeps: {
    include: ['axios'],
  }
})