🚀
TypeScript +NestJSをプロジェクトで導入したら素晴らしかった件
概要
今回はTypescript製のバックエンドフレームワーク「NestJS」について紹介させていただきます
私が導入した時にお世話になった記事🙇♂️
記事のゴール
1.NestJSの概要を説明
2.NestJSを使用したREST APIをハンズオンで構築する
3.構築したAPIにリクエストを投げる
ここまでをゴールとします
NestJSとは?
- 効率的でスケーラブルなNode.jsサーバーサイドアプリケーションを構築するためのフレームワーク
- モダンなJavaScriptを使用し、TypeScript(純粋なJavaScriptとの互換性を保つ)で構築されている
- OOP(オブジェクト指向プログラミング)、FP(関数型プログラミング)、FRP(関数型リアクティブプログラミング)の要素を兼ね備えている
let's NestJS!!(・∀・)
環境構築
# NestJSをセットアップするディレクトリを作成します
mkdir nestjs-sample
# cliをインストールします
npm i -g @nestjs/cli
# プロジェクトを作成します
nest new sample
# 今回はnpmでインストールします
? Which package manager would you ❤️ to use? npm
npm-scriptでnestを起動します
nestjs-sample/sample
# モジュールをインストールします
npm i
# コードの変更を検知するwatchモードで起動
nest start --watch
package.json
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
},
自動的にCRUDを構築するベースを作成
- NestJSに慣れるまではCRUDジェネレーターで作られた雛形をカスタマイズするといいかもです!
nestjs-sample/sample
nest g resource users
? What transport layer do you use? REST API
? Would you like to generate CRUD entry points? Yes
CREATE src/users/users.controller.spec.ts (566 bytes)
CREATE src/users/users.controller.ts (894 bytes)
CREATE src/users/users.module.ts (247 bytes)
CREATE src/users/users.service.spec.ts (453 bytes)
CREATE src/users/users.service.ts (609 bytes)
CREATE src/users/dto/create-user.dto.ts (30 bytes)
CREATE src/users/dto/update-user.dto.ts (169 bytes)
CREATE src/users/entities/user.entity.ts (21 bytes)
UPDATE src/app.module.ts (411 bytes)
✔ Packages installed successfully.
TypeORMとSQLiteのセットアップ
nestjs-sample/sample
npm i --save @nestjs/typeorm typeorm sqlite3
TypeORMの設定ファイルを用意します
nestjs-sample/sample
touch ormconfig.json
sample/ormconfig.json
{
"type": "sqlite",
"database": "data/dev.sqlite",
"entities": [
"dist/**/entities/**/*.entity.js"
],
"migrations": [
"dist/**/migrations/**/*.js"
],
"logging": true
}
NestJSにTypeORMをセットアップ
sample/src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
//追加!
imports: [AppModule, TypeOrmModule.forRoot()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Entity定義
- Entityとはテーブルの構造をクラス構文で表現したものです
sample/src/users/entities/user.entity.ts
import { Column, PrimaryGeneratedColumn, Entity } from 'typeorm';
@Entity('users')
export class User {
@PrimaryGeneratedColumn({
comment: 'アカウントID',
})
readonly id: number;
@Column('varchar', { comment: 'アカウント名' })
name: string;
constructor(name: string) {
this.name = name;
}
}
マイグレーションファイルの生成
nestjs-sample/sample
# コンパイルします
npm run build
# マイグレーションファイルを生成します
npx typeorm migration:generate -d src/database/migrations -n create-user
sample/src/database/migrations/1639847070691-create-user.ts
import {MigrationInterface, QueryRunner} from "typeorm";
export class createUser1639847070691 implements MigrationInterface {
name = 'createUser1639847070691'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "users" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL)`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "users"`);
}
}
マイグレーションを実行
nestjs-sample/sample
npm run build
npx typeorm migration:run
モジュールにTypeORMを組み込む
sample/src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}
登録したリポジトリをサービスで依存解決します
sample/src/users/users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User) private userRepository: Repository<User>,
) {}
}
バリデーションを定義します
nestjs-sample/sample
npm install class-validator --save
sample/src/users/dto/create-user.dto.ts
import { MaxLength } from 'class-validator';
export class CreateUserDto {
@MaxLength(255, {
message: 'アカウント名は255文字以内で入力してください',
})
name: string;
}
サービスクラスにロジックを定義します
- 基本的なCRUDを作成します
sample/src/users/users.service.ts
import {
HttpException,
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User) private userRepository: Repository<User>,
) {}
/**
* 登録
* @param createUserDto
* @returns
*/
async create(createUserDto: CreateUserDto): Promise<{ message: string }> {
await this.userRepository
.save({
name: createUserDto.name,
})
.catch((e) => {
throw new InternalServerErrorException(
`[${e.message}]アカウントの登録に失敗しました。`,
);
});
return { message: 'アカウントの登録に成功しました' };
}
/**
* 一覧
* @returns
*/
async findAll(): Promise<User[]> {
return await this.userRepository.find();
}
/**
* 詳細
* @param id
* @returns
*/
async findOne(id: number): Promise<User> {
if (!id) throw new NotFoundException('IDが指定されていません');
return await this.userRepository.findOne(id);
}
/**
* 更新
* @param id
* @param updateUserDto
* @returns
*/
async update(
id: number,
updateUserDto: UpdateUserDto,
): Promise<{ message: string }> {
if (!id) throw new NotFoundException('IDが指定されていません');
await this.userRepository
.update(id, {
name: updateUserDto.name,
})
.catch((e) => {
throw new InternalServerErrorException(
`[${e.message}]アカウントID「${id}」の更新に失敗しました。`,
);
});
return { message: `アカウントID「${id}」の更新に成功しました。` };
}
/**
* 削除
* @param id
* @returns
*/
async remove(id: number): Promise<{ message: string }> {
if (!id) throw new NotFoundException('IDが指定されていません');
await this.userRepository.delete(id).catch((e) => {
throw new InternalServerErrorException(
`[${e.message}]アカウントID「${id}」の削除に失敗しました。`,
);
});
return { message: `アカウントID「${id}」の削除に成功しました。` };
}
}
コントローラークラスでサービスクラスを呼び出します
sample/src/users/users.controller.ts
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
/**
* 登録
* @param createUserDto
* @returns
*/
@Post()
async create(
@Body() createUserDto: CreateUserDto,
): Promise<{ message: string }> {
return await this.usersService.create(createUserDto);
}
/**
* @summary 一覧
* @returns
*/
@Get()
async findAll(): Promise<User[]> {
return await this.usersService.findAll();
}
/**
* @summary 詳細
* @param id
* @returns
*/
@Get(':id')
async findOne(@Param('id') id: string): Promise<User> {
return await this.usersService.findOne(+id);
}
/**
* @summary 更新
* @param id
* @param updateUserDto
* @returns
*/
@Patch(':id')
async update(
@Param('id') id: string,
@Body() updateUserDto: UpdateUserDto,
): Promise<{ message: string }> {
return await this.usersService.update(+id, updateUserDto);
}
/**
* @summary 削除
* @param id
* @returns
*/
@Delete(':id')
async remove(@Param('id') id: string): Promise<{ message: string }> {
return await this.usersService.remove(+id);
}
}
動作を確認します
nestjs-sample/sample
# 登録
curl -XPOST -H "Content-Type:application/json" localhost:3000/users -d '{"name":"サンプル太郎"}'
{"message":"アカウントの登録に成功しました"}%
curl -XPOST -H "Content-Type:application/json" localhost:3000/users -d '{"name":"サンプル二郎"}'
{"message":"アカウントの登録に成功しました"}%
# 一覧
curl -H "Content-Type:application/json" localhost:3000/users
[{"name":"サンプル太郎","id":1},{"name":"サンプル二郎","id":2}]%
# 詳細
curl -H "Content-Type:application/json" localhost:3000/users/1
{"name":"サンプル太郎","id":1}%
# 更新
curl -XPATCH -H "Content-Type:application/json" localhost:3000/users/2 -d '{"name":"更新したよ"}'
{"message":"アカウントID「2」の更新に成功しました。"}%
curl -H "Content-Type:application/json" localhost:3000/users
[{"name":"サンプル太郎","id":1},{"name":"更新したよ","id":2}]%
# 削除
curl -XDELETE -H "Content-Type:application/json" localhost:3000/users/2
{"message":"アカウントID「2」の削除に成功しました。"}%
curl -H "Content-Type:application/json" localhost:3000/users
[{"name":"サンプル太郎","id":1}]%
最後に
読んでいただきありがとうございます。
今回の記事はいかがでしたか?
・こういう記事が読みたい
・こういうところが良かった
・こうした方が良いのではないか
などなど、率直なご意見を募集しております。
Discussion