🐆

NestJSを使って最低限のCRUDを実装してみる(認証なし)

2022/01/19に公開

こんにちは、新人エンジニアのtakuです!


今回は、NestJSをとりあえず触ってみたいという方に向けて、簡単な掲示板APIを作成していきたいと思います。


将来的に、フロントも実装した簡単な掲示板アプリの作成の紹介もしていきたいと考えています。

curlを利用してAPIの各エンドポイントを叩いて行きます



以下の記事の続きとなっています。
https://zenn.dev/senri/articles/331162304a78e0
参考にしてみてください。


今回の環境

  • typescript:4.3.5
  • NestJS:8系
  • typeorm:0.2系

目次

  1. ファイルを準備する
  2. TypeORMを利用してPostテーブルを作成する
  3. 簡単にCRUD機能を実装してみる


ファイルを準備する


NestJSの便利なコマンドを紹介

NestJSには便利なコマンドが用意されています。一覧として簡単にまとめておきます。(参照先)

nest generateコマンド

構成に基づいて、ファイルを生成・変更をしてくれます!
→勝手にファイルをいい感じに変更してくれる ので、楽です

主なコマンド

コマンド名 エイリアス 説明
controller co controllerファイルを生成します
module mo moduleファイルを生成します
service s serviceファイルを生成します
interface なし interfaceファイルを生成します
guard gu guardファイルを生成します
interceptor in interceptorファイルを生成します
pipe pi pipeファイルを生成します

主なオプション

コマンド内容 説明
--spec specファイル(テストファイル)を作成します(デフォルト)
--no-spec specファイルを作成しません

他にも、様々なコマンドが用意されていますが、公式ドキュメントを参考してください。

Postsモジュールを作成

以下のコマンドを入力します

docker compose exec api nest g mo posts

上記のコマンドを入力すると、postsファイルが作成されます。
また、postsディレクトリ内にposts.module.tsファイルが作成されていますね。

さらに、app.module.tsファイル内にも変更が入ります。

このように、いい感じにNestJS側で変更を加えてくれるので、ファイル構成を考えておけばエラーが少なく実装が可能だと思います。(cli便利!!)

Controllerファイルを作成

以下のコマンドを入力します(今後テストを記述する予定ですので、specファイルも作成します)
テストを記述しないという方は--no-specを最後に付け足してください。

docker compose exec api nest g co posts

以下のようなファイルがpostsディレクトリ内に作成されます

posts.controller.ts
import { Controller } from '@nestjs/common';

@Controller('posts')
export class PostsController {}

classの上にControllerデコレーターが書かれています。
このデコレーターをclassに装飾することで、そのクラスをコントローラーとして使用することができ、引数にパスプレフィックスを指定することができます。


postsを指定することによってルーティングをこのコントローラーにまとめることができ、コードを読みやすくなりますね。




デコレーターについては以下を参照してみてください。
https://qiita.com/taqm/items/4bfd26dfa1f9610128bc
https://www.typescriptlang.org/docs/handbook/decorators.html


また、NestJSには様々なデコレーターがあります。
さらに、カスタムデコレーターとして、自分で作成することも可能です。
https://docs.nestjs.com/custom-decorators

Serviceファイルを生成

以下のコマンドを入力します。

docker compose exec api nest g s posts



すると、以下のようなファイルが作成されます。

posts.service.ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class PostsService {}



Serviceファイルは主に、ロジックを記述クラスです。
MVCのModelとは異なりますが、似てるようなもの?なんでしょうかね。


potato4dさんのこちらの記事から引用

@Injectableというデコレーターがあります。これは、このクラスが依存関係の解決において注入可能なオブジェクトであることを示しており、このデコレーターを定義することで、後述する Module において、自動的な依存関係の解決を行ってくれます。

Entityファイルを作成

Entityファイルを作成するコマンドはないので、entitiesディレクトリを作成します
作成した、entitiesディレクトリ内にposts.entity.tsファイルを作成します。

以下のように記述しておきましょう。

posts.entity.ts
import {
  Column,
  CreateDateColumn,
  DeleteDateColumn,
  Entity,
  PrimaryGeneratedColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity('post')
export class Posts {
  @PrimaryGeneratedColumn({ type: 'int' })
  id: number;

  @Column({ nullable: false })
  title: string;

  @Column({ nullable: false })
  description: string;

  @CreateDateColumn()
  created_at: Date;

  @UpdateDateColumn()
  update_at: Date;

  @DeleteDateColumn()
  deleted_at: Date;
}



読めば分かる通りですが、Postテーブル内にカラムを定義しています。
詳細についてはTypeORMの公式ドキュメントを参照してください🙇‍♂️🙇‍♂️


というのもあれなので、少し解説すると


@PrimaryGeneratedColumnはprimary keyになるものをレコードができ次第、都度追加してくれます

@CreateDateColumn はレコードの作成日時を自動的に生成してくれます。


本来なら@Columnで色々とオプションを記述しないといけないのですが、@CreateDateColumn のおかげでオプション等を記述せずにスッキリ書けます。
@DeleteDateColumn,@UpdateDateColumn@CreateDateColumn とほぼ同様ですね。

これでエンティティファイルは完成したので、これをマイグレーションファイルを生成して、マイグレーションを実行すればデータベースを作成することができます!

TypeORMを利用してPostテーブルを作成する



例:

"typeorm": "npx ts-node ./node_modules/.bin/typeorm",
"migration:create": "npm run typeorm migration:create -n",
"migration:generate": "npm run typeorm migration:generate -n",
"migration:run": "npm run typeorm migration:run",
"migration:status": "npm run typeorm migration:show",
"migration:revert": "npm run typeorm migration:revert"


以下のコマンドを入力します

dc exec api npm run migration:generate



以下のような表示がターミナルにでたらOKです!

query: SELECT * FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = 'develop' AND `TABLE_NAME` = 'migrations'
query: SELECT * FROM `develop`.`migrations` `migrations` ORDER BY `id` DESC
 [X] posts1637276382930



もし、他のコマンドが知りたい場合は、

dc exec api typeorm -h

でヘルプを見ることができるので、活用してください。

これでテーブルの作成ができました!




簡単にCRUD機能を実装してみる



posts.controller.tsposts.service.tsを下記のようにします。
NestJSはLaravelなどとは異なり、routeファイルがなくデコレーターで定義できるのが良いですね。
routeファイルがカオスにならないです。

posts.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { PostsService } from './posts.service';

@Controller('posts')
import {
  Body,
  Controller,
  Get,
  Param,
  ParseIntPipe,
  Post,
  Put,
} from '@nestjs/common';
import { PostsService } from './posts.service';

@Controller('posts')
export class PostsController {
  constructor(private readonly postService: PostsService) {}
  @Get()
  getData() {
    return this.postService.get();
  }
  @Post()
  postData(
    @Body('title') title: string,
    @Body('description') description: string,
  ) {
    return this.postService.store(title, description);
  }
  @Put(':id')
  update(
    @Param('id', ParseIntPipe) id: number,
    @Body('title') title: string,
    @Body('description') description: string,
  ) {
    return this.postService.update(id, title, description);
  }
}



@Postデコレーターを関数につけることで、/postsのPostリクエストに対して、投稿が保存されるようになります。
詳しくはこちらの公式ドキュメントの翻訳をご覧ください


posts.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Posts } from './posts.entity';

@Injectable()
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Posts } from './posts.entity';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Posts)
    private readonly postRepository: Repository<Posts>,
  ) {}

  store(title: string, description: string) {
    const post = new Posts();
    post.title = title;
    post.description = description;
    return this.postRepository.save(post);
  }

  get() {
    return this.postRepository.find();
  }

  async update(id, title: string, description: string) {
    const post = await this.postRepository.findOne(id);
    post.title = title;
    post.description = description;
    return this.postRepository.save(post);
  }

  async delete(id: number) {
    const post = await this.postRepository.findOne(id);
    return this.postRepository.remove(post);
  }
}

上記についての説明は、potato4dさんのこちらの記事から引用させてください

@InjectRepository デコレータを利用した上で、対象となるレポジトリを定義します。こうすることで、 Post へのアクセスを管理するためのレポジトリを TypeOrmModule が生成してくれます。型情報は、 TypeORM 本体の Repository の Generic でまかないます。

こうすることで、postRepository はオーソドックスなレポジトリ層として利用可能となります。 .findOne や .find や .insert など、ありがちなものがだいたいあるので、補完に任せていろいろ試してみても面白いかと思います。

少し、この記事に合わせて最適化してあります。



storeメソッド内では、Postインスタンスを初期化して、それぞれにstoreメソッドの引数の値を代入しています。
その後、typeormのsaveメソッドを利用してDBに投稿の内容を保存という流れです。

また、@Put(':id')とすることでurlのidを取得することができます。
関数の引数に@Param('id') id:numberとすることでidを利用することができます。
ParseIntPipeについてはこちら
その他のメソッドについては特に説明するほどの事でもないので、わからない場合はコメント下さい。



では実際に、エンドポイントを叩いて、レスポンスが返ってくると思います
以下のコマンドを実行します。

curl -XPOST -H "Content-Type: application/json" -d '{"title": "テスト", "description": "テストの内容"}' http://localhost:3000/posts/

2回ほどPostメソッドでAPIエンドポイントを叩くとデータが追加されているはずです。


Getメソッドで情報をすべて取得してみましょう。

curl http://localhost:3000/posts



そうすると、以下のようなレスポンスが返ってくるはずです。

[{"id":1,"title":"テスト","description":"テストの内容","created_at":"2022-01-18T23:48:41.361Z","update_at":"2022-01-18T23:48:41.361Z","deleted_at":null},{"id":2,"title":"テスト","description":"テストの内容","created_at":"2022-01-18T23:48:49.778Z","update_at":"2022-01-18T23:48:49.778Z","deleted_at":null}]%



次にPUTメソッドでデータを更新します。
以下のコマンドでAPIを叩きましょう

curl http://localhost:3000/posts/2 -XPUT -H "Content-Type: application/json" -d '{"title": "テスト", "description": "テストを更新しました"}'

これで内容が更新されました。



もう一度GETリクエストを送信すると、内容が更新されているのがわかります。

次にDELETEしてみましょう。
以下のコマンドでAPIを叩きます

curl http://localhost:3000/posts/2 -XDELETE

もう一度GETリクエストを送信すると、内容が1つ削除されているのがわかります。

以上でCRUD機能についてとても簡単にですが、実装できました。
めちゃめちゃ簡単に実装することがわかります。

是非、NestJS使って見てください!

https://nestjs.com/

Discussion