NestJSを使って最低限のCRUDを実装してみる(認証なし)
こんにちは、新人エンジニアのtakuです!
今回は、NestJSをとりあえず触ってみたいという方に向けて、簡単な掲示板APIを作成していきたいと思います。
将来的に、フロントも実装した簡単な掲示板アプリの作成の紹介もしていきたいと考えています。
curlを利用してAPIの各エンドポイントを叩いて行きます
以下の記事の続きとなっています。
参考にしてみてください。
今回の環境
- typescript:4.3.5
- NestJS:8系
- typeorm:0.2系
目次
- ファイルを準備する
- TypeORMを利用してPostテーブルを作成する
- 簡単に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ディレクトリ内に作成されます
import { Controller } from '@nestjs/common';
@Controller('posts')
export class PostsController {}
classの上にControllerデコレーターが書かれています。
このデコレーターをclassに装飾することで、そのクラスをコントローラーとして使用することができ、引数にパスプレフィックスを指定することができます。
postsを指定することによってルーティングをこのコントローラーにまとめることができ、コードを読みやすくなりますね。
デコレーターについては以下を参照してみてください。
また、NestJSには様々なデコレーターがあります。
さらに、カスタムデコレーターとして、自分で作成することも可能です。
Serviceファイルを生成
以下のコマンドを入力します。
docker compose exec api nest g s posts
すると、以下のようなファイルが作成されます。
import { Injectable } from '@nestjs/common';
@Injectable()
export class PostsService {}
Serviceファイルは主に、ロジックを記述クラスです。
MVCのModelとは異なりますが、似てるようなもの?なんでしょうかね。
potato4dさんのこちらの記事から引用
@Injectableというデコレーターがあります。これは、このクラスが依存関係の解決において注入可能なオブジェクトであることを示しており、このデコレーターを定義することで、後述する Module において、自動的な依存関係の解決を行ってくれます。
Entityファイルを作成
Entityファイルを作成するコマンドはないので、entitiesディレクトリを作成します
作成した、entitiesディレクトリ内に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.ts
とposts.service.ts
を下記のようにします。
NestJSはLaravelなどとは異なり、routeファイルがなくデコレーターで定義できるのが良いですね。
routeファイルがカオスにならないです。
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リクエストに対して、投稿が保存されるようになります。
詳しくはこちらの公式ドキュメントの翻訳をご覧ください
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使って見てください!
Discussion