序章

Node.jsアプリケーションで作業したことがある場合は、時間の経過とともに保守が難しくなっていることに気付いたかもしれません。 アプリケーションに新しい機能を追加すればするほど、コードベースは大きくなります。

Nest.js は、効率的で信頼性が高くスケーラブルなアプリケーションを構築するためのサーバー側のNode.jsフレームワークです。 これは、バックエンドアプリケーションに、コードを個別のモジュールに編成するためのモジュラー構造を提供します。 これは、まとまりのないコードベースを排除するために構築されました。

Nest.jsはAngularに大きく影響を受けており、 TypeScript で構築され、内部で Express.js を使用しているため、Expressミドルウェアの大部分と互換性があります。

この投稿では、ユーザーが書店で本を取得、作成、削除できるようにする小さなRESTfulAPIを作成します。

前提条件

このチュートリアルを完了するには、次のものが必要です。

Nest.jsの構成要素を理解する

Nest.jsアプリケーションを構築するときに使用されるビルディングブロックは次のとおりです。

  • コントローラー
  • プロバイダー
  • モジュール

まず、コントローラーについて見ていきます。 ほとんどのWebフレームワークと同様に、Nest.jsのコントローラーは、着信要求を処理し、アプリケーションのクライアント側に応答を返す役割を果たします。 たとえば、/homeなどの特定のエンドポイントに対してAPI呼び出しを行うと、コントローラーはこの要求を受信し、使用可能なリソースに基づいて適切な応答を返します。

Nest.jsは、ルーティングメカニズムが、特定の要求の処理を担当するコントローラーを制御できるように構成されています。

Nest.jsでコントローラーを定義するには、次のコードスニペットに示すように、TypeScriptファイルを作成し、デコレーター@Controller()を含めます。

 [label users.controller.ts]

import { Controller, Get } from '@nestjs/common';

@Controller('users')
export class UsersController {
 @Get()
 findAll() { 
   return 'This will return all the users';
 }
}

コントローラデコレータ内のusersのプレフィックスは、UsersControllerに、アプリケーション内の/users GET要求を処理し、指定された適切な応答を返すように促します。 コントローラによって処理されるその他のHTTPリクエストには、チュートリアルの後半で説明するように、POSTPUTDELETEが含まれます。

コントローラを作成したら、Nest.jsが簡単に認識できるように、モジュール定義に追加する必要があります。 これは、ルートApplicationModuleまたはアプリケーション内で作成された他のモジュールである可能性があります。 これについて詳しくは、この投稿のモジュールセクションをご覧ください。

それでは、プロバイダーを見てみましょう。

前述のように、Nest.jsはAngularに大きく影響を受けており、Angularアプリケーションと同様に、プロバイダーを作成してコントローラーや他のプロバイダーに注入できます。 これらのプロバイダーはサービスとも呼ばれ、あらゆる形式の複雑さとロジックを抽象化するように設計されています。

Nest.jsのサービスプロバイダーは、上部に特別な@Injectable()デコレーターを持つJavaScriptクラスです。 たとえば、ユーザーを取得するサービスを作成できます。

users.service.ts

import { Injectable } from '@nestjs/common';
import { User } from './interfaces/user.interface';

@Injectable()
export class UsersService {
  private readonly users: User[] = [];

  create(user: User) { 
    this.users.push(user);   }

  findAll(): User[] {
    return this.users;
  }
}

上で作成したプロバイダーは、create()findAll()の2つのメソッドを持つクラスであり、それぞれすべてのユーザーを作成して返すために使用できます。 また、タイプチェックを簡単に支援するために、インターフェイスを使用して、メソッドが受け取る必要のある要素のタイプを指定しました。

最後に、モジュールを見てみましょう。 モジュールを使用すると、関連ファイルをグループ化できます。 @Moduleデコレータで装飾されたTypescriptファイルです。 このアタッチされたデコレータは、Nestがアプリケーション構造を整理するために使用するメタデータを提供します。

各Nest.jsアプリケーションには、通常ルートモジュールと呼ばれるモジュールが少なくとも1つ必要です。 このルートモジュールはトップレベルのモジュールであり、通常は小さなアプリケーションには十分です。 アプリケーションの構造を維持するのに役立つため、大規模なアプリケーションを複数のモジュールに分割することをお勧めします。

ユーザーに関する多くのデータや機能を管理するアプリケーションがある場合は、コントローラー、サービス、およびその他の関連ファイルをUsersModuleのように1つのモジュールにグループ化できます。

import { Module } from '@nestjs/common';
import { UsersController } from './users.controller.ts';
import { UsersService } from './users.service.ts';

@Module({
  controllers: [UsersController],
  providers: [UsersService]
})

export class UsersModule {}

この例では、UsersControllerUsersServiceの両方を含むUsersModuleがエクスポートされます。 これが適切に行われると、次のコードスニペットに示すように、アプリケーションのルートモジュール内でUsersModuleのインポートと使用に進むことができます。

...
import { UsersModule } from './users/users.module';

@Module({
  ...
})

export class AppModule { }

Nest.jsには、他にもいくつかの重要な概念があります。

  • DTO :データ転送オブジェクトは、ネットワークを介してデータを送信する方法を定義するオブジェクトです。
  • インターフェース:TypeScriptインターフェースは、コントローラーまたはNestサービスに渡すことができるデータのタイプをチェックおよび定義するために使用されます。
  • 依存性注入:依存性注入は、アプリケーションの効率とモジュール性を向上させるために使用されるデザインパターンです。 これは、コードをクリーンで使いやすくするために、最大のフレームワークでよく使用されます。 Nest.jsは、基本的に結合されたコンポーネントを作成するためにもそれを利用します。

このパターンを使用すると、コントローラー、プロバイダー、モジュールなどのビルディングブロック間の依存関係を非常に簡単に管理できます。 必要なのは、次に示すように、コントローラーのコンストラクターでUsersService()などの依存関係を定義することだけです。


...
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService){}
 ...
}

これらの概念のいくつかを簡単に説明したら、次のセクションに進むことができます。ここでは、Nest.jsを使用してRESTful APIをシームレスに構築する方法を学びながら、この投稿でこれまでに得たすべての知識を活用します。

この投稿の前半で述べたように、Nest.jsのコアコンセプトのいくつかをよく理解するのに役立つサンプルアプリケーションを作成します。

このアプリケーションは、特に書店向けです。 投稿の最後に、ユーザーが説明の少ない新しい本を作成して既存の本のリストに追加できるようにするマイクロサービスを作成します。 これはデータベースからのものである可能性がありますが、この投稿を簡単にするために、アプリケーションをデータベースに接続することはまだありません。 ただし、代わりに、書籍の模擬データを使用し、新しい書籍が作成されたら、それをプッシュしてリストに追加します。

ステップ1-Nest.jsをインストールする

新しいNest.jsアプリケーションをスキャフォールディングするには、 NestCLIアプリケーションをグローバルにインストールする必要があります。 これは、新しいNest.jsアプリを作成し、さまざまなファイルを生成して適切に構造化されたアプリケーションを作成するためのいくつかのコマンドへのアクセスを提供するために特別に作成されたコマンドラインツールです。

CLIツールを使用する以外に、Gitを使用してGitHubからスタータープロジェクトを複製することにより、新しいNest.jsアプリケーションをインストールすることもできます。このチュートリアルでは、次のコマンドを実行してNestCLIをインストールします。

  1. npm install -g @nestjs/cli

これにより、プロジェクトのインストールやその他のプロジェクト固有のコマンド用のnestコマンドにアクセスできるようになります。

次に、次のコマンドを実行して、開発フォルダー内にbookstore-nestという名前の新しいプロジェクトをインストールします。

  1. nest new bookstore-nest

インストール中にいくつかの質問があります。プロンプトに従って、それに応じて応答してください。 次に、インストールが完了したら、作業ディレクトリを新しく作成したプロジェクトに切り替えます。

  1. cd bookstore-nest

次のコマンドでアプリケーションを起動します。

  1. npm run start

プロジェクトでNodemonを使用するために、次のコマンドを実行することもできます。


// start the application using nodemon
npm run start:dev

ブラウザでhttp://localhost:3000に移動すると、 Hello World が表示されます! 次の画像に示すようなメッセージ:

プロジェクトを開始したら、ルートモジュールを作成しましょう。

ステップ2–モジュールの生成

書店のモジュールを生成してみましょう。 これを行うには、NestCLIのファイルジェネレーターを利用します。 次のコマンドを実行して、アプリケーションの新しいモジュールをスキャフォールディングします。

  1. nest generate module books

これにより、srcフォルダー内にbooksという名前の新しいフォルダーが作成されます。 booksフォルダー内に、books.module.tsファイルがあります。

src / books / books / module.ts

import { Module } from '@nestjs/common';
@Module({})
export class BooksModule {}

これはコマンドによって生成され、モジュールはアプリケーションのルートモジュールであるapp.module.tsにも追加されています。

次に、エンドポイントのルートを作成します

ステップ3–ルートとコントローラーの作成

前述のように、ルートはコントローラーに存在するため、個々のエンドポイントを処理するコントローラーを作成する必要があります。 ここでも、Nest CLIを使用してコントローラーを生成し、次のコマンドを実行します。

  1. nest generate controller books

これにより、booksフォルダー内にコントローラーが作成されます。 今のところデータベースに接続しないので、書店のサンプルモックデータを作成します。 srcフォルダーの下に、mocksという名前のサブフォルダーを作成し、新しく作成したフォルダー内に、books.mock.tsという名前の新しいTypeScriptファイルを作成して、次のコードを追加します。


[label src/mocks/books.mock.ts]
export const BOOKS = [
    { id: 1, title: 'First book', description: "This is the description for the first book", author: 'Olususi Oluyemi' },
    { id: 2, title: 'Second book', description: "This is the description for the second book", author: 'John Barry' },
    { id: 3, title: 'Third book', description: "This is the description for the third book", author: 'Clement Wilfred' },
    { id: 4, title: 'Fourth book', description: "This is the description for the fourth book", author: 'Christian nwamba' },
    { id: 5, title: 'Fifth book', description: "This is the description for the fifth book", author: 'Chris anderson' },
    { id: 6, title: 'Sixth book', description: "This is the description for the sixth book", author: 'Olususi Oluyemi' },
];

次に、書店のすべてのロジックを保持するサービスを作成します。

ステップ4–サービスの設定

次のコマンドを実行して、サービスを生成します。

nest generate service books

このコマンドは、./src/booksフォルダー内にbooks.service.tsという名前の新しいファイルを作成します。

次に、新しく作成したファイルを開き、以下を貼り付けます。


[label /src/books/books.service.ts]

  import { Injectable, HttpException } from '@nestjs/common';
  import { BOOKS } from '../mocks/books.mock';

  @Injectable()
  export class BooksService {
      books = BOOKS;

      getBooks(): Promise<any> {
          return new Promise(resolve => {
              resolve(this.books);
          });
      }
      getBook(bookID): Promise<any> {
          let id = Number(bookID);
          return new Promise(resolve => {
              const book = this.books.find(book => book.id === id);
              if (!book) {
                  throw new HttpException('Book does not exist!', 404);
              }
              resolve(book);
          });
      }
  }

まず、requiresモジュールをNest.jsからインポートし、BOOKSを以前に作成したモックデータからインポートしました。

次に、getBooks()getBook()という名前の2つの異なるメソッドを作成して、模擬データから本のリストを取得し、bookIDをパラメーターとして使用して1冊の本だけをフェッチしました。

次に、getBook()メソッドの直後の/src/books/books.service.tsに次のメソッドを追加します。

src / books / books.service.ts

import { Injectable, HttpException } from '@nestjs/common';
import { BOOKS } from '../mocks/books.mock';
@Injectable()
export class BooksService {
    books = BOOKS;
    ...
    addBook(book): Promise<any> {
        return new Promise(resolve => {
            this.books.push(book);
            resolve(this.books);
        });
    }
}

上記の方法は、新しい本を既存のリストにプッシュするために使用されます

最後に、bookIDをパラメーターとして使用して、特定の本を削除する最後のメソッドを追加します。

src / books / books.service.ts

import { Injectable, HttpException } from '@nestjs/common';
import { BOOKS } from '../mocks/books.mock';
@Injectable()
export class BooksService {
    books = BOOKS;
    ...
    deleteBook(bookID): Promise<any> {
        let id = Number(bookID);
        return new Promise(resolve => {
            let index = this.books.findIndex(book => book.id === id);
            if (index === -1) {
                throw new HttpException('Book does not exist!', 404);
            }
            this.books.splice(1, index);
            resolve(this.books);
        });
    }
}

ステップ5–サービスをコントローラーに注入する

ここでは、依存性注入のデザインパターンを使用して、コンストラクターを介してBooksServiceBooksControllerに渡します。 前に作成したBooksControllerを開き、次のコードを貼り付けます。

src / books / books.controller.ts

import { Controller, Get, Param, Post, Body, Query, Delete } from '@nestjs/common';
import { BooksService } from './books.service';
import { CreateBookDTO } from './dto/create-book.dto';

@Controller('books')
export class BooksController {
    constructor(private booksService: BooksService) { }

    @Get()
    async getBooks() {
        const books = await this.booksService.getBooks();
        return books;
    }

    @Get(':bookID')
    async getBook(@Param('bookID') bookID) {
        const book = await this.booksService.getBook(bookID);
        return book;
    }

    @Post()
    async addBook(@Body() createBookDTO: CreateBookDTO) {
        const book = await this.booksService.addBook(createBookDTO);
        return book;
    }

    @Delete()
    async deleteBook(@Query() query) {
        const books = await this.booksService.deleteBook(query.bookID);
        return books;
    }
}

まず、重要なモジュールが@nestjs/commonからインポートされ、BooksServiceCreateBookDTOの両方もそれぞれインポートされます。 CreateBookDTOは、データ転送オブジェクトであり、タイプチェック用に作成されたTypeScriptクラスであり、新しい本を作成するときにオブジェクトがどのように見えるかの構造を定義します。 このDTOを少し作成します。

次に、constructorを使用してBooksServiceをコントローラーに注入し、次の4つの異なるメソッドを作成しました。

  • getBooks():すべての本のリストを取得するために使用されます。 @Get()デコレータがアタッチされています。 これは、/booksに送信されたGETリクエストをこのコントローラーにマップするのに役立ちます。
  • getBook()bookIDをパラメーターとして渡すことにより、特定の本の詳細を取得するために使用されます。
  • addBook():新しい本を作成して既存の本のリストに投稿するために使用されます。 また、データベースに永続化していないため、新しく追加された本はメモリにのみ保持されます。
  • deleteBook():クエリパラメータとしてbookIDを渡すことにより、本を削除するために使用されます。

各メソッドには特別なデコレータがアタッチされているため、各HTTPリクエストをコントローラ内の特定のメソッドに簡単にルーティングできます。

ステップ6–DTOの定義

前のセクションでは、CreateBookDTOというデータ転送オブジェクトを使用しました。 作成するには、./src/booksフォルダーに移動し、新しいサブフォルダー名dtoを作成します。 次に、新しく作成したフォルダー内に別のファイルを作成し、create-book.dto.tsという名前を付けて、次のファイルを貼り付けます。

src / books / dto / create-book.dto.ts

export class CreateBookDTO {
    readonly id: number;
    readonly title: string;
    readonly description: string;
    readonly author: string;
}

アプリケーションはほぼ完成です。 以前に作成した ./src/books/books.module.tsファイルに戻り、次のコードで更新します。

src / books / books.module.ts

import { Module } from '@nestjs/common';
import { BooksController } from './books.controller';
import { BooksService } from './books.service';
@Module({
  controllers: [BooksController],
  providers: [BooksService]
})
export class BooksModule {}

現在実行されていない場合は、次のコマンドでアプリケーションを再起動します。

  1. npm run start

次に、postmanを使用してAPIをテストします

新しい本をいくつか作成します。

IDを使用して本を入手します。

そして本を削除します:

結論

このチュートリアルでは、Nest.jsの基本と基本的な構成要素を簡単に確認してから、RESTfulAPIを構築しました。

このチュートリアルの完全なソースコードは、GitHubにあります。