著者は、 Write for DOnations プログラムの一環として寄付を受け取るために、 Software in the Public InterestIncを選択しました。

序章

Nest.js は、TypeScriptで構築されたスケーラブルなサーバーサイドJavaScriptフレームワークであり、JavaScriptとの互換性を維持しているため、効率的で信頼性の高いバックエンドアプリケーションを構築するための効果的なツールになります。 Node.js開発の世界に成熟した構造的なデザインパターンを提供するモジュラーアーキテクチャを備えています。

Vue.js は、ユーザーインターフェイスを構築するためのフロントエンドJavaScriptフレームワークです。 シンプルでありながら非常に強力なAPIと優れたパフォーマンスを備えています。 Vue.jsは、サイズに関係なく、任意のWebアプリケーションのフロントエンドレイヤーとロジックに電力を供給することができます。 他のライブラリや既存のプロジェクトと簡単に統合できるため、最新のWebアプリケーションのほとんどに最適です。

このチュートリアルでは、Nest.jsアプリケーションを作成して、その構成要素と、最新のWebアプリケーションを作成するための基本原則を理解します。 アプリケーションをフロントエンドとバックエンドの2つの異なるセクションに分割することで、このプロジェクトに取り組みます。 まず、Nest.jsで構築されたRESTfulバックエンドAPIに集中します。 次に、Vue.jsで構築するフロントエンドに焦点を当てます。 両方のアプリケーションは異なるポートで実行され、別々のドメインとして機能します。

ユーザーが新しい投稿を作成して保存したり、保存した投稿をホームページに表示したり、投稿の編集や削除などの他のプロセスを実行したりできるブログアプリケーションを作成します。 さらに、アプリケーションを接続し、そのデータを MongoDB で永続化します。これは、JSONドキュメントを受信および保存できるスキーマレスのNoSQLデータベースです。 このチュートリアルは、開発環境でのアプリケーションの構築に焦点を当てています。 実稼働環境では、アプリケーションのユーザー認証も検討する必要があります。

前提条件

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

注:このチュートリアルでは、開発にmacOSマシンを使用します。 別のオペレーティングシステムを使用している場合は、使用する必要がある場合があります sudo 為に npm チュートリアル全体のコマンド。

ステップ1—Nest.jsおよびその他の依存関係のインストール

このセクションでは、アプリケーションとその必要な依存関係をローカルマシンにインストールすることにより、Nest.jsの使用を開始します。 Nest.jsが提供するCLIを使用するか、GitHubからスタータープロジェクトをインストールすることで、Nest.jsを簡単にインストールできます。 このチュートリアルでは、CLIを使用してアプリケーションをセットアップします。 まず、ターミナルから次のコマンドを実行して、マシンにグローバルにインストールします。

  1. npm i -g @nestjs/cli

次のような出力が表示されます。

Output
@nestjs/cli@5.8.0 added 220 packages from 163 contributors in 49.104s

Nest CLIのインストールを確認するには、ターミナルから次のコマンドを実行します。

  1. nest --version

マシンにインストールされている現在のバージョンを示す出力が表示されます。

Output
5.8.0

あなたは利用します nest プロジェクトを管理し、それを使用して、コントローラー、モジュール、プロバイダーなどの関連ファイルを生成するコマンド。

このチュートリアルのプロジェクトを開始するには、 nest 名前の付いた新しいNest.jsプロジェクトを作成するコマンド blog-backend ターミナルから次のコマンドを実行します。

  1. nest new blog-backend

コマンドを実行した直後、 nest 次のような基本情報を入力するように求められます description, version、 と author. 先に進み、適切な詳細を提供します。 打つ ENTER 各プロンプトに応答した後、続行するためにコンピュータ上で。

次に、パッケージマネージャーを選択します。 このチュートリアルの目的のために、を選択します npm とヒット ENTER Nest.jsのインストールを開始します。

これにより、新しいNest.jsプロジェクトが生成されます。 blog-backend ローカル開発フォルダー内のフォルダー。

次に、ターミナルから新しいプロジェクトのフォルダに移動します。

  1. cd blog-backend

次のコマンドを実行して、他のサーバーの依存関係をインストールします。

  1. npm install --save @nestjs/mongoose mongoose

インストールしました @nestjs/mongoose、MongoDBのオブジェクトモデリングツール用のNest.js専用パッケージであり、 mongoose、マングースのパッケージです。

次に、次のコマンドを使用してアプリケーションを起動します。

  1. npm run start

さて、あなたがにナビゲートすると http://localhost:3000 お気に入りのブラウザから、アプリケーションが実行されているのがわかります。

Nest CLIコマンドの可用性を活用して、プロジェクトを正常に生成しました。 その後、アプリケーションの実行に進み、デフォルトのポートでアクセスしました 3000 ローカルマシンで。 次のセクションでは、データベース接続の構成をセットアップすることにより、アプリケーションをさらに進めます。

ステップ2—データベースの構成と接続

このステップでは、MongoDBを構成してNest.jsアプリケーションに統合します。 MongoDBを使用して、アプリケーションのデータを保存します。 MongoDBは、データをドキュメントフィールド:値のペアとして格納します。 このデータ構造にアクセスするには、 Mongoose を使用します。これは、MongoDBデータベースが格納するデータのタイプを表すスキーマを定義できるオブジェクトドキュメントモデリング(ODM)です。

MongoDBを起動するには、アプリケーションが実行を継続できるように別のターミナルウィンドウを開き、次のコマンドを実行します。

  1. sudo mongod

これにより、MongoDBサービスが開始され、マシンのバックグラウンドでデータベースが実行されます。

プロジェクトを開く blog-backend テキストエディタで、に移動します ./src/app.module.ts. インストールされているものを含めることで、データベースへの接続を設定できます MongooseModule ルート内 ApplicationModule. これを実現するには、次のコンテンツを更新します app.module.ts 次の強調表示された行で:

〜/ blog-backend / src / app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  imports: [
    MongooseModule.forRoot('mongodb://localhost/nest-blog', { useNewUrlParser: true }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

このファイルでは、 forRoot() データベースへの接続を提供するメソッド。 編集が終了したら、ファイルを保存して閉じます。

これで、MongoDB用のMongooseモジュールを使用してデータベース接続をセットアップしました。 次のセクションでは、Mongooseライブラリ、TypeScriptインターフェイス、およびデータ転送オブジェクト(DTO)スキーマを使用してデータベーススキーマを作成します。

ステップ3—データベーススキーマ、インターフェイス、およびDTOを作成する

このステップでは、Mongooseを使用して、データベース用の schema interface 、および data transferobjectを作成します。 Mongooseは、データ間の関係を管理するのに役立ち、データ型のスキーマ検証を提供します。 アプリケーションのデータベース内のデータの構造とデータ型を定義しやすくするために、以下を決定するファイルを作成します。

  • データベーススキーマ:これは、データベースに格納する必要のあるデータの構造とタイプを定義するための青写真としてのデータの編成です。

  • interfaces :TypeScriptインターフェースはタイプチェックに使用されます。 これを使用して、アプリケーションに渡す必要のあるデータのタイプを定義できます。

  • データ転送オブジェクト:これは、データがネットワークを介して送信され、プロセス間でデータを伝送する方法を定義するオブジェクトです。

開始するには、アプリケーションが現在実行されているターミナルに戻り、次のコマンドでプロセスを停止します。 CTRL + C、次にに移動します ./src/ フォルダ:

  1. cd ./src/

次に、という名前のディレクトリを作成します blog、および schemas その中のフォルダ:

  1. mkdir -p blog/schemas

の中に schemas フォルダ、という名前の新しいファイルを作成します blog.schema.ts テキストエディタを使用して開きます。 次に、次のコンテンツを追加します。

〜/ blog-backend / src / blog / schemas / blog.schema.ts
import * as mongoose from 'mongoose';

export const BlogSchema = new mongoose.Schema({
    title: String,
    description: String,
    body: String,
    author: String,
    date_posted: String
})

ここでは、Mongooseを使用して、データベースに保存するデータのタイプを定義しました。 すべてのフィールドが文字列値のみを格納して受け入れるように指定しました。 編集が終了したら、ファイルを保存して閉じます。

これで、データベーススキーマが決定されたので、インターフェイスの作成に進むことができます。

開始するには、に戻ってナビゲートします blog フォルダ:

  1. cd ~/blog-backend/src/blog/

名前の付いた新しいフォルダを作成します interfaces それに移動します:

  1. mkdir interfaces

の中に interfaces フォルダ、という名前の新しいファイルを作成します post.interface.ts テキストエディタを使用して開きます。 次のコンテンツを追加して、 Post:

〜/ blog-backend / src / blog / interfaces / post.interface.ts
import { Document } from 'mongoose';

export interface Post extends Document {
    readonly title: string;
    readonly description: string;
    readonly body: string;
    readonly author: string;
    readonly date_posted: string
}

このファイルでは、データの種類を正常に定義しています。 Post 文字列値として入力します。 ファイルを保存して終了します。

アプリケーションはデータベースにデータを投稿する機能を実行するため、ネットワークを介してデータを送信する方法を定義するデータ転送オブジェクトを作成します。

これを実現するには、フォルダを作成します dto./src/blog フォルダ。 新しく作成されたフォルダ内に、という名前の別のファイルを作成します create-post.dto.ts

に戻ります blog フォルダ:

  1. cd ~/blog-backend/src/blog/

次に、という名前のフォルダを作成します dto それに移動します:

  1. mkdir dto

の中に dto フォルダ、という名前の新しいファイルを作成します create-post.dto.ts テキストエディタを使用して開き、次のコンテンツを追加します。

〜/ blog-backend / src / blog / dto / create-post.dto.ts
export class CreatePostDTO {
    readonly title: string;
    readonly description: string;
    readonly body: string;
    readonly author: string;
    readonly date_posted: string
}

の個々のプロパティのそれぞれにマークを付けました CreatePostDTO データ型が string ととして readonly 不必要な突然変異を避けるため。 編集が終了したら、ファイルを保存して終了します。

このステップでは、データベースのデータベーススキーマ、インターフェイス、およびデータベースに保存するデータのデータ転送オブジェクトを作成しました。 次に、ブログのモジュール、コントローラー、およびサービスを生成します。

ステップ4—ブログのモジュール、コントローラー、およびサービスを作成する

このステップでは、ブログ用のモジュールを作成することにより、アプリケーションの既存の構造を改善します。 このモジュールは、アプリケーションのファイル構造を整理します。 次に、ルートを処理し、クライアントからのHTTPリクエストを処理するコントローラーを作成します。 まとめとして、アプリケーションのコントローラーが処理するには複雑すぎるすべてのビジネスロジックを処理するサービスを設定します。

モジュールの生成

AngularフロントエンドWebフレームワークと同様に、Nest.jsはモジュラー構文を使用します。 Nest.jsアプリケーションはモジュラー設計です。 単一のルートモジュールがインストールされているため、小さなアプリケーションには十分な場合があります。 しかし、アプリケーションが成長し始めると、Nest.jsは、コードを関連する機能に分割する複数モジュールの編成を推奨します。

Nest.jsのモジュールは、 @Module() デコレータと次のようなプロパティを持つオブジェクトを取り込みます controllersproviders. これらの各プロパティは、 controllersproviders それぞれ。

構造をより整理された状態に保つために、このブログアプリケーション用の新しいモジュールを生成します。 始めに、まだ ~/blog-backend フォルダで、次のコマンドを実行します。

  1. nest generate module blog

次のような出力が表示されます。

Output
CREATE /src/blog/blog.module.ts UPDATE /src/app.module.ts

コマンドは、という名前の新しいモジュールを生成しました blog.module.ts アプリケーションの場合、新しく作成されたモジュールをアプリケーションのルートモジュールにインポートします。 これにより、Nest.jsはルートモジュール以外の別のモジュールを認識できるようになります。

このファイルには、次のコードが含まれています。

〜/ blog-backend / src / blog / blog.module.ts
import { Module } from '@nestjs/common';

@Module({})
export class BlogModule {}

これを更新します BlogModule チュートリアルの後半で必要なプロパティを使用します。 ファイルを保存して終了します。

サービスの生成

service は、Nest.jsではプロバイダーとも呼ばれ、コントローラーからロジックを削除するように設計されています。これは、HTTP要求のみを処理し、より複雑なタスクをサービスにリダイレクトすることを目的としています。 サービスはプレーンなJavaScriptクラスであり、 @Injectable() それらの上にデコレータ。 新しいサービスを生成するには、プロジェクトディレクトリ内にいる間に、ターミナルから次のコマンドを実行します。

  1. nest generate service blog

次のような出力が表示されます。

[secondary_label Output]  
CREATE /src/blog/blog.service.spec.ts (445 bytes)

CREATE /src/blog/blog.service.ts (88 bytes)

UPDATE /src/blog/blog.module.ts (529 bytes)

The nest ここで使用されるコマンドは、 blog.service.spec.ts テストに使用できるファイル。 また、新しいを作成しました blog.service.ts ファイル。このアプリケーションのすべてのロジックを保持し、MongoDBデータベースへのドキュメントの追加と取得を処理します。 また、新しく作成されたサービスが自動的にインポートされ、blog.module.tsに追加されました。

このサービスは、アプリケーション内のすべてのロジックを処理し、データベースとの対話を担当し、適切な応答をコントローラーに返します。 これを行うには、 blog.service.ts テキストエディタでファイルを作成し、内容を次のように置き換えます。

〜/ blog-backend / src / blog / blog.service.ts
import { Injectable } from '@nestjs/common';
import { Model } from 'mongoose';
import { InjectModel } from '@nestjs/mongoose';
import { Post } from './interfaces/post.interface';
import { CreatePostDTO } from './dto/create-post.dto';

@Injectable()
export class BlogService {

    constructor(@InjectModel('Post') private readonly postModel: Model<Post>) { }

    async getPosts(): Promise<Post[]> {
        const posts = await this.postModel.find().exec();
        return posts;
    }

    async getPost(postID): Promise<Post> {
        const post = await this.postModel
            .findById(postID)
            .exec();
        return post;
    }

    async addPost(createPostDTO: CreatePostDTO): Promise<Post> {
        const newPost = await this.postModel(createPostDTO);
        return newPost.save();
    }

    async editPost(postID, createPostDTO: CreatePostDTO): Promise<Post> {
        const editedPost = await this.postModel
            .findByIdAndUpdate(postID, createPostDTO, { new: true });
        return editedPost;
    }

    async deletePost(postID): Promise<any> {
        const deletedPost = await this.postModel
            .findByIdAndRemove(postID);
        return deletedPost;
    }

}

このファイルでは、最初に必要なモジュールをからインポートしました @nestjs/common, mongoose、 と @nestjs/mongoose. また、という名前のインターフェイスをインポートしました Post およびデータ転送オブジェクト CreatePostDTO.

の中に constructor、追加しました @InjectModel(``'``Post``'``)、注入します Post これにモデル化 BlogService クラス。 これで、この挿入されたモデルを使用して、すべての投稿を取得したり、1つの投稿を取得したり、その他のデータベース関連のアクティビティを実行したりできるようになります。

次に、次のメソッドを作成しました。

  • getPosts():データベースからすべての投稿をフェッチします。
  • getPost():データベースから単一の投稿を取得します。
  • addPost():新しい投稿を追加します。
  • editPost():単一の投稿を更新します。
  • deletePost():特定の投稿を削除します。

終了したら、ファイルを保存して終了します。

これで、バックエンドAPIからのMongoDBデータベースとの適切な相互作用を処理するいくつかのメソッドの設定と作成が完了しました。 次に、フロントエンドクライアントからのHTTP呼び出しを処理する必要なルートを作成します。

コントローラーの生成

巣の中。 js、 controllers は、アプリケーションのクライアント側からの着信要求を処理し、適切な応答を返す責任があります。 他のほとんどのWebフレームワークと同様に、アプリケーションが要求をリッスンしてそれに応答することが重要です。

ブログアプリケーションのすべてのHTTPリクエストに対応するには、 nest 新しいコントローラファイルを生成するコマンド。 プロジェクトディレクトリにいることを確認してください。 blog-backend、次のコマンドを実行します。

  1. nest generate controller blog

次のような出力が表示されます。

Output
CREATE /src/blog/blog.controller.spec.ts (474 bytes) CREATE /src/blog/blog.controller.ts (97 bytes) UPDATE /src/blog/blog.module.ts (483 bytes)

出力は、このコマンドが内に2つの新しいファイルを作成したことを示しています src/blog ディレクトリ。 彼らです blog.controller.spec.tsblog.controller.ts. 前者は、新しく作成されたコントローラーの自動テストを作成するために使用できるファイルです。 後者はコントローラーファイル自体です。 Nest.jsのコントローラーは、で装飾されたTypeScriptファイルです。 @Controller メタデータ。 このコマンドは、新しく作成されたコントローラーもインポートし、ブログモジュールに追加しました。

次に、 blog.controller.ts テキストエディタでファイルし、次のコンテンツで更新します。

〜/ blog-backend / src / blog / blog.controller.ts
import { Controller, Get, Res, HttpStatus, Param, NotFoundException, Post, Body, Query, Put, Delete } from '@nestjs/common';
import { BlogService } from './blog.service';
import { CreatePostDTO } from './dto/create-post.dto';
import { ValidateObjectId } from '../shared/pipes/validate-object-id.pipes';


@Controller('blog')
export class BlogController {

    constructor(private blogService: BlogService) { }

    @Get('posts')
    async getPosts(@Res() res) {
        const posts = await this.blogService.getPosts();
        return res.status(HttpStatus.OK).json(posts);
    }

    @Get('post/:postID')
    async getPost(@Res() res, @Param('postID', new ValidateObjectId()) postID) {
        const post = await this.blogService.getPost(postID);
        if (!post) throw new NotFoundException('Post does not exist!');
        return res.status(HttpStatus.OK).json(post);

    }

    @Post('/post')
    async addPost(@Res() res, @Body() createPostDTO: CreatePostDTO) {
        const newPost = await this.blogService.addPost(createPostDTO);
        return res.status(HttpStatus.OK).json({
            message: "Post has been submitted successfully!",
            post: newPost
        })
    }
}

このファイルでは、最初にHTTPリクエストを処理するために必要なモジュールをインポートしました。 @nestjs/common モジュール。 次に、次の3つの新しいモジュールをインポートしました。 BlogService, CreatePostDTO、 と ValidateObjectId. その後、あなたは注射しました BlogService アクセスを取得し、内ですでに定義されている関数を利用するために、コンストラクターを介してコントローラーに接続します。 BlogService ファイル。 これは、Nest.jsで使用される依存性注入と見なされるパターンであり、効率を高め、アプリケーションのモジュール性を強化します。

最後に、次の非同期メソッドを作成しました。

  • getPosts():このメソッドは、クライアントからHTTP GET要求を受信してデータベースからすべての投稿をフェッチし、適切な応答を返す機能を実行します。 それはで飾られています @Get(``'``posts``'``).

  • getPost():これには postID パラメータとして、データベースから単一の投稿をフェッチします。 に加えて postID このメソッドに渡されたパラメーター、という名前の追加のメソッドの追加に気づきました ValidateObjectId(). このメソッドは、 PipeTransform Nest.jsからのインターフェース。 その目的は、 postID パラメータはデータベースにあります。 このメソッドは次のセクションで定義します。

  • addPost():このメソッドは、データベースに新しい投稿を追加するためのPOSTHTTPリクエストを処理します。

特定の投稿を編集および削除できるようにするには、さらに2つのメソッドを追加する必要があります。 blog.controller.ts ファイル。 そのためには、以下を含めます editPost()deletePost() 直後のメソッド addPost() 以前に追加したメソッド blog.controller.ts:

〜/ blog-backend / src / blog / blog.controller.ts

...
@Controller('blog')
export class BlogController {
    ...
    @Put('/edit')
    async editPost(
        @Res() res,
        @Query('postID', new ValidateObjectId()) postID,
        @Body() createPostDTO: CreatePostDTO
    ) {
        const editedPost = await this.blogService.editPost(postID, createPostDTO);
        if (!editedPost) throw new NotFoundException('Post does not exist!');
        return res.status(HttpStatus.OK).json({
            message: 'Post has been successfully updated',
            post: editedPost
        })
    }


    @Delete('/delete')
    async deletePost(@Res() res, @Query('postID', new ValidateObjectId()) postID) {
        const deletedPost = await this.blogService.deletePost(postID);
        if (!deletedPost) throw new NotFoundException('Post does not exist!');
        return res.status(HttpStatus.OK).json({
            message: 'Post has been deleted!',
            post: deletedPost
        })
    }
}

ここに追加しました:

  • editPost():このメソッドは、のクエリパラメータを受け入れます postID 単一の投稿を更新する機能を実行します。 それはまた利用しました ValidateObjectId 編集する必要のある投稿に適切な検証を提供する方法。

  • deletePost():このメソッドは、のクエリパラメータを受け入れます postID データベースから特定の投稿を削除します。

と同様に BlogController、ここで定義した各非同期メソッドにはメタデータデコレータがあり、Nest.jsがルーティングメカニズムとして使用するプレフィックスを取ります。 これは、どのコントローラーがどの要求を受信するかを制御し、それぞれ要求を処理して応答を返す必要があるメソッドを指します。

たとえば、 BlogController このセクションで作成したプレフィックスには、 blog およびという名前のメソッド getPosts() のプレフィックスを取ります posts. これは、のエンドポイントに送信されるGETリクエストを意味します blog/posts (http:localhost:3000/blog/posts)によって処理されます getPosts()方法。 この例は、他のメソッドがHTTP要求を処理する方法と似ています。

ファイルを保存して終了します。

完全な blog.controller.ts ファイルについては、このアプリケーションのDOコミュニティリポジトリにアクセスしてください。

このセクションでは、アプリケーションをより整理された状態に保つためのモジュールを作成しました。 また、データベースと対話して適切な応答を返すことにより、アプリケーションのビジネスロジックを処理するサービスを作成しました。 最後に、コントローラーを生成し、次のようなHTTPリクエストを処理するために必要なメソッドを作成しました。 GET, POST, PUT、 と DELETE クライアント側から。 次のステップでは、バックエンドのセットアップを完了します。

ステップ5—マングースの追加の検証を作成する

ブログアプリケーションの各投稿は、一意のID(別名)で識別できます。 PostID. つまり、投稿を取得するには、このIDをクエリパラメータとして渡す必要があります。 これを検証するには postID パラメータを設定し、投稿がデータベースで利用可能であることを確認するには、内の任意のメソッドから初期化できる再利用可能な関数を作成する必要があります BlogController.

これを構成するには、に移動します ./src/blog フォルダ:

  1. cd ./src/blog/

次に、という名前の新しいフォルダを作成します shared:

  1. mkdir -p shared/pipes

の中に pipes フォルダ、テキストエディタを使用して、という新しいファイルを作成します validate-object-id.pipes.ts そしてそれを開きます。 次のコンテンツを追加して、承認済みを定義します postID データ:

〜/ blog-backend / src / blog / shared / pipes / validate-object-id.pipes.ts
import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import * as mongoose from 'mongoose';

@Injectable()
export class ValidateObjectId implements PipeTransform<string> {
   async transform(value: string, metadata: ArgumentMetadata) {
       const isValid = mongoose.Types.ObjectId.isValid(value);
       if (!isValid) throw new BadRequestException('Invalid ID!');
       return value;
   }
}

The ValidateObjectId() クラスは PipeTransform からの方法 @nestjs/common モジュール。 それはという名前の単一のメソッドを持っています transform() パラメータとして値を取ります— postID この場合。 上記の方法では、このアプリケーションのフロントエンドからのHTTPリクエストは postID データベースで見つからないものは無効と見なされます。 ファイルを保存して閉じます。

サービスとコントローラーの両方を作成したら、 Post に基づくモデル BlogSchema. この構成は、ルート内で設定できます ApplicationModule、ただし、この例では、でモデルを構築します BlogModule アプリケーションの組織を維持します。 を開きます ./src/blog/blog.module.ts 次の強調表示された行で更新します。

〜/ blog-backend / src / blog / blog.module.ts
import { Module } from '@nestjs/common';
import { BlogController } from './blog.controller';
import { BlogService } from './blog.service';
import { MongooseModule } from '@nestjs/mongoose';
import { BlogSchema } from './schemas/blog.schema';

@Module({
 imports: [
   MongooseModule.forFeature([{ name: 'Post', schema: BlogSchema }])
],
 controllers: [BlogController],
 providers: [BlogService]
})
export class BlogModule { }

このモジュールは MongooseModule.forFeature() モジュールに登録するモデルを定義するメソッド。 これがなければ、注入する PostModel 以内 BlogService を使用して @injectModel() デコレータは機能しません。 コンテンツの追加が完了したら、ファイルを保存して閉じます。

このステップでは、Nest.jsを使用して完全なバックエンドRESTful APIを作成し、MongoDBと統合しました。 次のセクションでは、フロントエンドアプリケーションが別のポートで実行されるため、別のサーバーからのHTTPリクエストを許可するようにサーバーを構成します。

ステップ6—CORSを有効にする

あるドメインから別のドメインへのHTTPリクエストは、サーバーによって許可するように指定されている場合を除いて、デフォルトでブロックされることがよくあります。 フロントエンドアプリケーションがバックエンドサーバーにリクエストを送信するには、クロスオリジンリソースシェアリング(CORS)を有効にする必要があります。これは、ウェブページで制限されたリソースのリクエストを許可する手法です。

Nest.jsでCORSを有効にするには、単一のメソッドを追加する必要があります main.ts ファイル。 このファイルをテキストエディタで開きます。テキストエディタは次の場所にあります。 ./src/main.ts、および次の強調表示されたコンテンツで更新します。

〜/ blog-backend / src / main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
 const app = await NestFactory.create(AppModule);
 app.enableCors();
 await app.listen(3000);
}
bootstrap();

ファイルを保存して終了します。

バックエンドのセットアップが完了したので、フォーカスをフロントエンドに移し、Vue.jsを使用してこれまでに構築されたAPIを使用します。

ステップ7—Vue.jsフロントエンドを作成する

このセクションでは、Vue.jsを使用してフロントエンドアプリケーションを作成します。 Vue CLI は、手間をかけずに新しいVue.jsプロジェクトをすばやく生成してインストールできる標準ツールです。

まず、マシンにVueCLIをグローバルにインストールする必要があります。 別のターミナルを開き、 blog-backend フォルダー、ローカルプロジェクトの開発フォルダーに移動し、実行します。

  1. npm install -g @vue/cli

インストールプロセスが完了したら、を利用します vue 新しいVue.jsプロジェクトを作成するコマンド:

  1. vue create blog-frontend

このコマンドを入力すると、短いプロンプトが表示されます。 を選択してください manually select features オプションをクリックし、を押してこのプロジェクトに必要な機能を選択します SPACE 複数の機能を強調表示するためにコンピュータ上で。 選択します Babel, Router、 と Linter / Formatter.

次の手順については、次のように入力します y ルーターに履歴モードを使用する。 これにより、ルーターファイル内で履歴モードが有効になり、このプロジェクト用に自動的に生成されます。 さらに、 ESLint with error prevention only リンター/フォーマッター構成を選択します。 次に、 Lint on save 追加のLint機能について。 次に、構成をに保存することを選択します dedicated config file 将来のプロジェクトのために。 プリセットの名前を次のように入力します vueconfig.

Vue.jsは、アプリケーションとそれに必要なすべての依存関係を次の名前のディレクトリに作成し始めます。 blog-frontend.

インストールプロセスが完了したら、Vue.jsアプリケーション内を移動します。

  1. cd blog-frontend

次に、次のコマンドで開発サーバーを起動します。

  1. npm run serve

アプリケーションはで実行されます http://localhost:8080.

このアプリケーション内でHTTPリクエストを実行するため、ブラウザ用のPromiseベースのHTTPクライアントであるAxiosをインストールする必要があります。 ここではAxiosを使用して、アプリケーション内のさまざまなコンポーネントからHTTPリクエストを実行します。 を押してフロントエンドアプリケーションを停止します CTRL + C コンピュータのターミナルから次のコマンドを実行します。

  1. npm install axios --save

フロントエンドアプリケーションは、アプリケーション内のさまざまなコンポーネントから特定のドメインのバックエンドAPIへのAPI呼び出しを行います。 このアプリケーションの適切な構造を確保するために、 helper ファイルを作成し、サーバーを定義します baseURL.

まず、あなたのターミナルからまだ blog-frontend、に移動します ./src/ フォルダ:

  1. cd ./src/

名前の付いた別のフォルダを作成します utils:

  1. mkdir utils

の中に utils フォルダ、テキストエディタを使用して、という新しいファイルを作成します helper.js そしてそれを開きます。 次のコンテンツを追加して、 baseURL バックエンドのNest.jsプロジェクトの場合:

〜blog-frontend / src / utils / helper.js
export const server = {

baseURL: 'http://localhost:3000'

}

を定義することによって baseURL、Vue.jsコンポーネントファイル内のどこからでも呼び出すことができます。 URLを変更する必要がある場合は、更新するプロセスが簡単になります。 baseURL アプリケーション全体ではなく、このファイルにあります。

このセクションでは、新しいVue.jsアプリケーションを作成するためのツールであるVueCLIをインストールしました。 このツールを使用して、 blog-frontend 応用。 さらに、アプリケーションを実行し、Axiosという名前のライブラリをインストールしました。このライブラリは、アプリ内でHTTP呼び出しが発生するたびに使用されます。 次に、アプリケーションのコンポーネントを作成します。

ステップ8—再利用可能なコンポーネントの作成

次に、Vue.jsアプリケーションの標準構造である、アプリケーションの再利用可能なコンポーネントを作成します。 Vue.jsのコンポーネントシステムを使用すると、開発者は、独自の状態、マークアップ、およびスタイルを持つことができるインターフェイスの単一の独立したユニットを構築できます。 これにより、Vue.jsのコンポーネントを再利用できるようになります。

すべてのVue.jsコンポーネントには、次の3つの異なるセクションが含まれています。

  • <template>:HTMLコンテンツが含まれています

  • <script>:すべての基本的なフロントエンドロジックを保持し、機能を定義します

  • <style>:各コンポーネントのスタイルシート

まず、新しい投稿を作成するためのコンポーネントを作成することから始めます。 これを行うには、という名前の新しいフォルダを作成します post 以内 ./src/components 投稿に必要な再利用可能なコンポーネントを格納するフォルダ。 次に、新しく作成したテキストエディタ内でテキストエディタを使用します post フォルダ、別のファイルを作成し、名前を付けます Create.vue. 新しいファイルを開き、投稿を送信するために必要な入力フィールドを含む次のコードを追加します。

〜blog-frontend / src / components / post / Create.vue
<template>
  <div>
       <div class="col-md-12 form-wrapper">
         <h2> Create Post </h2>
         <form id="create-post-form" @submit.prevent="createPost">
              <div class="form-group col-md-12">
               <label for="title"> Title </label>
               <input type="text" id="title" v-model="title" name="title" class="form-control" placeholder="Enter title">
              </div>
             <div class="form-group col-md-12">
                 <label for="description"> Description </label>
                 <input type="text" id="description" v-model="description" name="description" class="form-control" placeholder="Enter Description">
             </div>
             <div class="form-group col-md-12">
                 <label for="body"> Write Content </label>
                 <textarea id="body" cols="30" rows="5" v-model="body" class="form-control"></textarea>
             </div>
             <div class="form-group col-md-12">
                 <label for="author"> Author </label>
                 <input type="text" id="author" v-model="author" name="author" class="form-control">
             </div>

             <div class="form-group col-md-4 pull-right">
                 <button class="btn btn-success" type="submit"> Create Post </button>
             </div>          
         </form>
       </div>
   </div>
</template>

これは <template> のセクション CreatePost 成分。 これには、新しい投稿を作成するために必要なHTML入力要素が含まれています。 各入力フィールドには、 v-model 入力属性としてのディレクティブ。 これは、Vue.jsがユーザーの入力を簡単に取得できるように、各フォーム入力で双方向のデータバインディングを確保するためです。

次に、 <script> 前のコンテンツの直後にある同じファイルへのセクション:

〜blog-frontend / src / components / post / Create.vue
...
<script>
import axios from "axios";
import { server } from "../../utils/helper";
import router from "../../router";
export default {
 data() {
   return {
     title: "",
     description: "",
     body: "",
     author: "",
     date_posted: ""
   };
 },
 created() {
   this.date_posted = new Date().toLocaleDateString();
 },
 methods: {
   createPost() {
     let postData = {
       title: this.title,
       description: this.description,
       body: this.body,
       author: this.author,
       date_posted: this.date_posted
     };
     this.__submitToServer(postData);
   },
   __submitToServer(data) {
     axios.post(`${server.baseURL}/blog/post`, data).then(data => {
       router.push({ name: "home" });
     });
   }
 }
};
</script>

ここに、という名前のメソッドを追加しました createPost() 新しい投稿を作成し、Axiosを使用してサーバーに送信します。 ユーザーが新しい投稿を作成すると、アプリケーションはホームページにリダイレクトされ、そこでユーザーは作成された投稿のリストを表示できます。

このチュートリアルの後半で、リダイレクトを実装するようにvue-routerを構成します。

編集が終了したら、ファイルを保存して閉じます。 完全な Create.vue ファイルについては、このアプリケーションのDOコミュニティリポジトリにアクセスしてください。

次に、特定の投稿を編集するための別のコンポーネントを作成する必要があります。 案内する ./src/components/post フォルダを作成し、別のファイルを作成して名前を付けます Edit.vue. を含む次のコードを追加します <template> それへのセクション:

〜blog-frontend / src / components / post / Edit.vue
<template>
<div>
      <h4 class="text-center mt-20">
       <small>
         <button class="btn btn-success" v-on:click="navigate()"> View All Posts </button>
       </small>
    </h4>
        <div class="col-md-12 form-wrapper">
          <h2> Edit Post </h2>
          <form id="edit-post-form" @submit.prevent="editPost">
            <div class="form-group col-md-12">
                <label for="title"> Title </label>
                <input type="text" id="title" v-model="post.title" name="title" class="form-control" placeholder="Enter title">
            </div>
            <div class="form-group col-md-12">
                <label for="description"> Description </label>
                <input type="text" id="description" v-model="post.description" name="description" class="form-control" placeholder="Enter Description">
            </div>
            <div class="form-group col-md-12">
                <label for="body"> Write Content </label>
                <textarea id="body" cols="30" rows="5" v-model="post.body" class="form-control"></textarea>
            </div>
            <div class="form-group col-md-12">
                <label for="author"> Author </label>
                <input type="text" id="author" v-model="post.author" name="author" class="form-control">
            </div>

            <div class="form-group col-md-4 pull-right">
                <button class="btn btn-success" type="submit"> Edit Post </button>
            </div>
          </form>
        </div>
    </div>
</template>

このテンプレートセクションには、 CreatePost() 成分; 唯一の違いは、編集が必要な特定の投稿の詳細が含まれていることです。

次に、<script> 次のセクション </template> のセクション Edit.vue:

〜blog-frontend / src / components / post / Edit.vue
...
<script>
import { server } from "../../utils/helper";
import axios from "axios";
import router from "../../router";
export default {
  data() {
    return {
      id: 0,
      post: {}
    };
  },
  created() {
    this.id = this.$route.params.id;
    this.getPost();
  },
  methods: {
    editPost() {
      let postData = {
        title: this.post.title,
        description: this.post.description,
        body: this.post.body,
        author: this.post.author,
        date_posted: this.post.date_posted
      };

      axios
        .put(`${server.baseURL}/blog/edit?postID=${this.id}`, postData)
        .then(data => {
          router.push({ name: "home" });
        });
    },
    getPost() {
      axios
        .get(`${server.baseURL}/blog/post/${this.id}`)
        .then(data => (this.post = data.data));
    },
    navigate() {
      router.go(-1);
    }
  }
};
</script>

ここでは、ルートパラメータを取得しました id 特定の投稿を識別するため。 次に、という名前のメソッドを作成しました getPost() データベースからこの投稿の詳細を取得し、それでページを更新します。 最後に、 editPost() 編集した投稿をPUTHTTPリクエストでバックエンドサーバーに送信するメソッド。

ファイルを保存して終了します。 完全な Edit.vue ファイルについては、このアプリケーションのDOコミュニティリポジトリにアクセスしてください。

次に、内に新しいコンポーネントを作成します ./src/components/post フォルダと名前を付けます Post.vue. これにより、ホームページから特定の投稿の詳細を表示できます。 次のコンテンツをに追加します Post.vue:

〜blog-frontend / src / components / post / Post.vue
<template>
    <div class="text-center">
        <div class="col-sm-12">
      <h4 style="margin-top: 30px;"><small><button class="btn btn-success" v-on:click="navigate()"> View All Posts </button></small></h4>
      <hr>
      <h2>{{ post.title }}</h2>
      <h5><span class="glyphicon glyphicon-time"></span> Post by {{post.author}}, {{post.date_posted}}.</h5>
      <p> {{ post.body }} </p>

    </div>
    </div>
</template>

このコードは、以下を含む投稿の詳細をレンダリングします。 title, author、および投稿 body.

さて、直接フォロー </template>、次のコードをファイルに追加します。

〜blog-frontend / src / components / post / Post.vue
...
<script>
import { server } from "../../utils/helper";
import axios from "axios";
import router from "../../router";
export default {
  data() {
    return {
      id: 0,
      post: {}
    };
  },
  created() {
    this.id = this.$route.params.id;
    this.getPost();
  },
  methods: {
    getPost() {
      axios
        .get(`${server.baseURL}/blog/post/${this.id}`)
        .then(data => (this.post = data.data));
    },
    navigate() {
      router.go(-1);
    }
  }
};
</script>

に似ています <script> 編集投稿コンポーネントのセクションで、ルートパラメータを取得しました id 特定の投稿の詳細を取得するために使用しました。

コンテンツの追加が完了したら、ファイルを保存して閉じます。 完全な Post.vue ファイルについては、このアプリケーションのDOコミュニティリポジトリにアクセスしてください。

次に、作成したすべての投稿をユーザーに表示するには、新しいコンポーネントを作成します。 に移動した場合 views のフォルダ src/views、が表示されます Home.vue コンポーネント—このファイルが存在しない場合は、テキストエディタを使用して作成し、次のコードを追加します。

〜blog-frontend / src / views / Home.vue
<template>
    <div>

      <div class="text-center">
        <h1>Nest Blog Tutorial</h1>
       <p> This is the description of the blog built with Nest.js, Vue.js and MongoDB</p>

       <div v-if="posts.length === 0">
            <h2> No post found at the moment </h2>
        </div>
      </div>

        <div class="row">
           <div class="col-md-4" v-for="post in posts" :key="post._id">
              <div class="card mb-4 shadow-sm">
                <div class="card-body">
                   <h2 class="card-img-top">{{ post.title }}</h2>
                  <p class="card-text">{{ post.body }}</p>
                  <div class="d-flex justify-content-between align-items-center">
                    <div class="btn-group" style="margin-bottom: 20px;">
                      <router-link :to="{name: 'Post', params: {id: post._id}}" class="btn btn-sm btn-outline-secondary">View Post </router-link>
                       <router-link :to="{name: 'Edit', params: {id: post._id}}" class="btn btn-sm btn-outline-secondary">Edit Post </router-link>
                       <button class="btn btn-sm btn-outline-secondary" v-on:click="deletePost(post._id)">Delete Post</button>
                    </div>
                  </div>

                  <div class="card-footer">
                    <small class="text-muted">Posted on: {{ post.date_posted}}</small><br/>
                    <small class="text-muted">by: {{ post.author}}</small>
                  </div>

                </div>
              </div>
            </div>
      </div>
    </div>
</template>

ここでは、 <template> セクション、あなたは使用しました <router-link> 編集および投稿を表示するためのリンクを作成するには、 post._id クエリパラメータとして。 あなたも使用しました v-if ユーザーの投稿を条件付きでレンダリングするディレクティブ。 データベースからの投稿がない場合、ユーザーには次のテキストのみが表示されます:現在投稿が見つかりません

ファイルを保存して終了します。 完全な Home.vue ファイルについては、このアプリケーションのDOコミュニティリポジトリにアクセスしてください。

さて、 </template> のセクション Home.vue、以下を追加します </script> セクション:

〜blog-frontend / src / views / Home.vue
...
<script>
// @ is an alias to /src
import { server } from "@/utils/helper";
import axios from "axios";

export default {
  data() {
    return {
      posts: []
    };
  },
  created() {
    this.fetchPosts();
  },
  methods: {
    fetchPosts() {
      axios
        .get(`${server.baseURL}/blog/posts`)
        .then(data => (this.posts = data.data));
    },
    deletePost(id) {
      axios.delete(`${server.baseURL}/blog/delete?postID=${id}`).then(data => {
        console.log(data);
        window.location.reload();
      });
    }
  }
};
</script>

以内 <script> このファイルのセクションで、という名前のメソッドを作成しました fetchPosts() データベースからすべての投稿をフェッチし、サーバーから返されたデータでページを更新しました。

今、あなたは更新します App リンクを作成するためのフロントエンドアプリケーションのコンポーネント HomeCreate コンポーネント。 開ける src/App.vue 次のように更新します。

〜blog-frontend / src / App.vue
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/create">Create</router-link>
    </div>
    <router-view/>
  </div>
</template>

<style>
#app {
  font-family: "Avenir", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
}
#nav {
  padding: 30px;
  text-align: center;
}

#nav a {
  font-weight: bold;
  color: #2c3e50;
}

#nav a.router-link-exact-active {
  color: #42b983;
}
</style>

両方へのリンクを含めることは別として HomeCreate コンポーネント、あなたも含めました <Style> このコンポーネントのスタイルシートであり、ページ上のいくつかの要素のスタイルの定義を保持するセクション。 ファイルを保存して終了します。

このステップでは、アプリケーションに必要なすべてのコンポーネントを作成しました。 次に、ルーターファイルを構成します。

ステップ9—ルーティングの設定

必要なすべての再利用可能なコンポーネントを作成したら、作成したすべてのコンポーネントへのリンクでコンテンツを更新することにより、ルーターファイルを適切に構成できます。 これにより、フロントエンドアプリケーション内のすべてのエンドポイントが、適切なアクションのために特定のコンポーネントにマップされます。 案内する ./src/router.js その内容を次のように置き換えます。

〜blog-frontend / src / router.js
import Vue from 'vue'
import Router from 'vue-router'
import HomeComponent from '@/views/Home';
import EditComponent from '@/components/post/Edit';
import CreateComponent from '@/components/post/Create';
import PostComponent from '@/components/post/Post';

Vue.use(Router)

export default new Router({
 mode: 'history',
 routes: [
   { path: '/', redirect: { name: 'home' } },
   { path: '/home', name: 'home', component: HomeComponent },
   { path: '/create', name: 'Create', component: CreateComponent },
   { path: '/edit/:id', name: 'Edit', component: EditComponent },
   { path: '/post/:id', name: 'Post', component: PostComponent }
 ]
});

インポートしました Router から vue-router モジュールを渡し、 moderoutes パラメーター。 のデフォルトモード vue-router はハッシュモードであり、URLハッシュを使用して完全なURLをシミュレートし、URLが変更されたときにページが再ロードされないようにします。 ハッシュを不要にするために、ここでは履歴モードを使用して、ページをリロードせずにURLナビゲーションを実現しました。 最後に、 routes オプションで、エンドポイントのパスを指定しました。これは、アプリケーション内でルートが呼び出されたときにレンダリングされるルートとコンポーネントの名前です。 ファイルを保存して終了します。

アプリケーションへのルーティングを設定したので、アプリケーションのユーザーインターフェイスの事前に作成されたスタイル設定に役立つBootstrapファイルを含める必要があります。 それを達成するには、 ./public/index.html テキストエディタでファイルを作成し、次のコンテンツをファイルに追加して、ブートストラップ用のCDNファイルを含めます。

〜blog-frontend / public / index.html
<!DOCTYPE html>
<html lang="en">
<head>
  ...
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
  <title>blog-frontend</title>
</head>
<body>
   ...
</body>
</html>

ファイルを保存して終了し、次のコマンドでアプリケーションを再起動します npm run serve あなたのための blog-frontend、現在実行されていない場合。

注:バックエンドサーバーとMongoDBインスタンスの両方が実行されていることを確認してください。 それ以外の場合は、に移動します blog-backend 別の端末から実行します npm run start. また、実行してMongoDBサービスを開始します sudo mongod 新しいターミナルからも。

次の場所でアプリケーションに移動します。 http://localhost:8080. これで、投稿を作成および編集してブログをテストできます。

アプリケーションでCreateをクリックすると、 Create Post 画面が表示されます。この画面は、 CreateComponent ファイル。 入力フィールドに値を入力し、投稿の作成ボタンをクリックして投稿を送信します。 完了すると、アプリケーションはホームページにリダイレクトします。

アプリケーションのホームページは、 HomeComponent. このコンポーネントには、データベースからすべての投稿をフェッチしてユーザーに表示するHTTP呼び出しを送信するメソッドがあります。

特定の投稿の投稿の編集ボタンをクリックすると、編集ページに移動し、変更を組み込んで投稿を保存できます。

このセクションでは、アプリケーションのルーティングを構成および設定しました。 これで、ブログアプリケーションの準備が整いました。

結論

このチュートリアルでは、Nest.jsを使用してNode.jsアプリケーションを構築する新しい方法を検討しました。 Nest.jsを使用してバックエンドRESTfulAPIを構築する簡単なブログアプリケーションを作成し、Vue.jsを使用してすべてのフロントエンドロジックを処理しました。 さらに、MongoDBをNest.jsアプリケーションのデータベースとして統合しました。

アプリケーションに認証を追加する方法の詳細については、人気のあるNode.js認証ライブラリであるPassport.jsを利用できます。 Passport.jsの統合については、Nest.jsのドキュメントで学ぶことができます。

このプロジェクトの完全なソースコードは、GitHubにあります。 Nest.jsの詳細については、公式ドキュメントにアクセスしてください。