9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

NestJSをはじめてみる ~#2 PosrgreSQLでRestAPIのCRUDを実装する~

Last updated at Posted at 2020-11-22

はじめに

30代未経験からエンジニアを目指して勉強中のYNと申します。
TypeScriptを勉強しているなか、NestJSというフレームワークにたどり着きました。これから長い付き合いになりそうです。
学んだことをメモとして書きます。

やったこと

Usersというテーブルを定義して下記二つのAPIを実装します。

  • Get: テーブルに登録された全てのUserを返す
  • Post: テーブルにUserを登録する

スクリーンショット 2020-11-22 8.32.12.png

全体像

可視化しようとしたら逆に分かりづらくなってしまいましたが、全体図はこんな感じです。順に手順を書いていきます。
スクリーンショット 2020-11-22 9.51.56.png

プロジェクトをはじめる

npm i -g @nestjs/cli  #グローバルインストール
nest new <project-name> #プロジェクト作成
yarn add typeorm @nestjs/typeorm pg class-transformer class-validator #必要なライブラリをインストール

この時点で全体像は下図の通りです。
スクリーンショット 2020-11-22 9.15.59.png

データベースに接続する

app moduleとPostgresSQLコンテナを接続します。
スクリーンショット 2020-11-22 9.00.41.png

コンテナを起動

dockerを使わずPostgreSQLアプリを使う場合はこのステップは飛ばして結構です。dockerを使う場合は、ポート番号が被るのでPostgreSQLアプリは落としておきましょう。
ルートディレクトリにdocker-composeファイルを作ります。

docker-compose.yml
version: '3'
services:
  postgres:
    image: 'postgres:9.6.1'
    ports:
      - '5432:5432'
    environment:
      POSTGRES_USER: 'test'
      POSTGRES_PASSWORD: 'test'
      POSTGRES_DB: 'test'

そして実行します。

docker-compose up -d #コンテナ起動
docker-compose exec postgres psql -U test #コンテナ上でpsqlコマンドをtestユーザーとして実行 

app moduleとデータベースを接続

公式ドキュメント参照。

typeormの設定

まずはtypeormの設定を記述します。

ormconfig.json
{
  "type": "postgres",
  "host": "localhost",
  "port": 5432,
  "username": "test",
  "password": "test",
  "database": "test",
  "synchronize": false,
  "logging": false,
  "entities": ["dist/**/*.entity.js"],
  "migrations": ["dist/migration/**/*.js"],
  "cli": {
    "entitiesDir": "src/entity",
    "migrationsDir": "src/migration",
    "subscribersDir": "src/subscriber"
  }
}

"synchronize": false

trueにするとデータベース接続時に自動マイグレーションを実行します。

"entities": ["dist/**/*.entity.js"]

テーブルスキーマを定義するentityファイルの場所を記述します。typeormおよびNestJSでは、コマンド実行時にコンパイル済のJSファイルを読み込む設定となっているので、entity関連のファイルはdistフォルダを指定します。(TSファイルを読み込もうとするとimport関連のエラーが出ると思います。)つまり、データベースに接続する前に必ずentityファイルのコンパイルが必要です。

"migrations": ["dist/migration/**/*.js"]

テーブルを作成したり、データを埋め込むmigrationファイルの場所を記述します。上記同様、migration関連のファイルの場所はdistフォルダを指定します。migration実行前にコンパイルが必要です。

app moduleの設定

次にapp moduleのimport部分の設定をします。

src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm/dist/typeorm.module';
import { UsersModule } from './users/users.module';

@Module({
  imports: [TypeOrmModule.forRoot(), UsersModule], //UserModuleは後ほど説明
})
export class AppModule {}

imports: [TypeOrmModule.forRoot()]でデータベースに接続できます。

テーブルをつくる

スクリーンショット 2020-11-22 9.55.30.png

テーブルのスキーマを定義する

entityファイルを定義します。

src/entity/user.entity.ts
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

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

  @Column()
  firstName: string;

  @Column()
  lastName: string;

  @Column({ default: true })
  isActive: boolean;
}

migrationを実行する

migrationファイルを生成する

まずはtypeormのCLIを使って、entityファイルを参照してmigrationファイルを生成します。

npx typeorm migration:generate -n Test

ここで、typeormのCLIが参照するentityファイルと、生成されたmigrationファイルの保存場所は下記に指定してある通りです。

ormconfig.json
 "cli": {
    "entitiesDir": "src/entity",
    "migrationsDir": "src/migration",
  }

migrationファイルを実行する

migrationを実行する時、コンパイル済のJSファイルを実行することになるので、先にコンパイルします。

yarn build #コンパイル
npx typeorm migration:run #テーブル作成
npx typeorm migration:revert #ロールバック

テーブルが作成できたか確認する

下記コマンドでテーブルが作成されたかどうか確認できます。

docker-compose up -d
docker-compose exec postgres psql -U test
\dt

users moduleをテーブルに接続する

nestのCLIをつかってmoduleを作ります。

nest g module users

そしてテーブルに接続します。

src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm/dist';
import { User } from '../entity/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])], //ここでUserエンティティを引数にとり、テーブルに接続する
})
export class UsersModule {}

スクリーンショット 2020-11-22 13.40.42.png

users contoroller/service の実装

controllerとserviceをつくる

nestのCLIをつかって作ります。

nest g controller users
nest g service users

users moduleも自動的に修正されているのが分かります。

src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm/dist';
import { User } from '../entity/user.entity';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UsersService], //自動で追記
  controllers: [UsersController], //自動で追記
})
export class UsersModule {}

controllerの実装

controllerには前回同様にvalidationを実装します。
まずは、validationのためのDTOを定義します。

src/users/dto/create-user.dto.ts
import { IsString } from 'class-validator';

export class CreateUserDto {
  @IsString()
  firstName: string;

  @IsString()
  lastName: string;
}

そして、@UserPipes()@Body()デコレータをつかってvalidationを実装します。

src/users/users.controller.ts
import {
  Body,
  Controller,
  Get,
  Post,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';
import { UsersService } from './users.service';

@Controller('users')
@UsePipes(new ValidationPipe({ transform: true }))
export class UsersController {
  constructor(private readonly service: UsersService) {}

  @Get()
  getUsers() {
    return this.service.findAll();
  }

  @Post()
  addUser(@Body() createUserDto: CreateUserDto) {
    const { firstName, lastName } = createUserDto;
    return this.service.createUser(firstName, lastName);
  }
}

ここまでの全体像はこんな感じです。
スクリーンショット 2020-11-22 13.53.24.png

serviceの実装

最後にserviceを実装します。
@InjectRepositoryデコレータを使って、UserエンティティをtypeormのRepositoryとして使うことができ、Repositoryメソッドを使ってSQLを実行することができます。

src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../entity/user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private usersRepository: Repository<User>,
  ) {}

  findAll(): Promise<User[]> { 
    return this.usersRepository.find();
  }

  createUser(firstName: string, lastName: string) {
    const newUser = new User();
    newUser.firstName = firstName;
    newUser.lastName = lastName;
    newUser.isActive = true;
    this.usersRepository.insert(newUser);
    return newUser.id;
  }

  findOne(id: string): Promise<User> { //今回は使ってない
    return this.usersRepository.findOne(id);
  }

  async remove(id: string): Promise<void> { //今回は使ってない
    await this.usersRepository.delete(id);
  }
}

これで完成です。
スクリーンショット 2020-11-22 14.01.50.png

動作確認

yarn start:dev #サーバー起動

postしてから
スクリーンショット 2020-11-22 14.07.36.png
getすると、期待どおり動いていることが分かります。
スクリーンショット 2020-11-22 14.08.39.png
スクリーンショット 2020-11-22 14.10.40.png

validationも機能しています。
スクリーンショット 2020-11-22 14.09.34.png

最後に

NestJSを使うと、APIをマイクロアーキテクチャに分解でき、とても設計しやすいと感じました。
これからも使っていきたいです。

9
8
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?