簡単なREST APIを開発する方法

本記事では、NestJSで簡単なREST APIを開発する方法を詳細に解説します。(CRUDモデルを中心に解説)

はじめに①-CRUDモデルとは

CRUDとは、永続的なデータを取り扱うソフトウェアに要求される4つの基本機能であるデータの作成(Create)、読み出し(Read)、更新(Update)、削除(Delete)の頭文字をつなげた言葉です。
主にデータベースを応用したシステムやデータベース管理システムについて用いられる言葉で、この4つに対応する機能や操作手段を実装することで、システム上に自在にデータの操作や管理を行うことができます。
本ページではCRUDモデルに従ってNestJS製のREST APIを実装する手順を詳細に解説します。

はじめに②-TypeORMとは

TypeORMとは、TypeScriptとJavaScriptで使えるORM(Object Relational Mapping)です。ORMとは、データベースとオブジェクト指向プログラミング言語の間の非互換的なデータを変換できるプログラミング手法です。
言い換えれば、オブジェクト指向とリレーショナルデータベース(RDB)の考え方を上手に変換して繋いでくれるものを意味します。
多くのプログラミング言語はオブジェクトを扱うので、そのオブジェクトをRDBに保存できるように、対応付を簡単にするためORMを活用します。より簡単に言えば、SQLを直接書くことなく、オブジェクトの操作でDBを操作できるということになります。

イントロダクション

実際のプロダクトやWebアプリケーションを想像してください。2つのエンティティ、例えばUserとProductのエンティティに対してCRUDエンドポイントを公開する必要があるとします。ベストプラクティスに従って、それぞれのエンティティに対して以下のようないくつかの操作を実装する必要があります。
  • モジュール(nest g mo)を生成して、コードを整理し、関連するコンポーネントをグループ化します。
  • CRUDルート(またはGraphQLアプリのクエリやミューテーション)を定義するためのコントローラー(nest g co)を作成します。
  • ビジネスロジックを実装し、分離するためのサービス(nest g s)を生成します。
  • リソースのデータを表現するためのエンティティクラスやインターフェイスを生成します。
  • データの転送オブジェクト(あるいはGraphQLアプリケーションの入力)を生成し、データがネットワーク上でどのように送信されるのかを定義します。
これらの繰り返し行われるプロセスをスピードアップするために、Nest CLIでは全ての定型的なコードを自動的に生成するジェネレータを提供し、この全てを行うことを避けて少ないコード量で開発を円滑に進められるようにします。

使用技術

  • TypeScript
  • NestJS
  • Visual Studio Code
  • TypeORM

大まかな手順

  1. 1.
    ファイルやディレクトリを用意する
  2. 2.
    TypeORMを使ってPostテーブルを作成する
  3. 3.
    簡単にCRUD機能を実装する

インストール

まずは以下のコマンドを入力して新規のNestJSプロジェクトを作成します。
npm i -g @nestjs/cli
nest new project-name

Postモジュールの作成

nest g mo posts
上記のコマンドを入力すると、postsファイルが作成されます。postsディレクトリ内にpost.module.tsが作成されます。
さらに、app.module.ts内にも変更が入ります。
このような感じでNestJS側がファイルに変更を加えてくれるので、エラーをより少なめに実装できます。

Controllerファイルを作成

以下のコマンドを入力します。
nest g co posts
以下のようなファイルがpostsディレクトリ内に作成されます。
posts.controller.ts
import { Controller } from '@nestjs/common'
@Controller('posts')
export class PostsController {}
classの上にControllerデコレータが作成されています。このデコレータをclassに装飾することで、そのクラスをControllerとして使用でき、引数にパスプレフィックスを指定できます。

注釈:プレフィックスとは

プレフィックス(prefix)とは、接頭辞、前につけるなどの意味を持つ英単語です。ITの分野では、番号や符号、識別名などの先頭部分に付けて、何らかの意味や情報を表現する短い部分のことを意味します。
例えば、プログラムの変数や関数で、strSomeStringValuestrgetSomeProperty()getのように、データ型や機能などを示す先頭の短い符号がプレフィックスです。

Serviceファイルを作成

以下のコマンドを入力します。
nest g s posts
以下のようなファイルが作成されます。
import { Injectable } from '@nestjs/common'
@Injectable()
export class PostsService {}
Serviceファイルは主にロジックを記述するクラスです。上記のプログラムにある@Injectable()は、下にあるクラスが依存関係の解決において注入可能なオブジェクトだということを示しています。このデコレータを定義することで、後述するModuleにおいて自動的な依存関係を解決します。

Entityファイルを作成

Entityファイルを作成するコマンドはないので、entitiesディレクトリを作成します。作成したentitiesディレクトリ内にpost.entity.tsを作成します。
post.entity.ts
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm'
@Entity('post')
export class Posts {
// primary keyになるものをレコードができ次第、都度追加してくれる
@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
}
Columnのオプションは、entityカラムの追加オプションを定義します。列のオプションは@Columnで指定できます。(TypeORMを使用しているので)
以下のコマンドを入力すれば、データベースが完成します。
npm run migration:generate

実装

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'ts
@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)
}
// :idと表記することで、urlのidを取得できる
@Put(':id')
update(
@Param('id', ParseIntPipe) id: number,
@Body('title') title: string,
@Body('description') description: string,
) {
return this.postService.update(id, title, description)
}
}
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)
}
}
2つのプログラムファイルを以上のようにすれば、CRUD機能を実装したREST APIを簡単に実装できます。