Nest.js、MongoDB、Vue.jsでブログを作成する方法

提供:Dev Guides
移動先:案内検索

著者は、 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マシンを使用します。 別のオペレーティングシステムを使用している場合は、チュートリアル全体でnpmコマンドにsudoを使用する必要がある場合があります。


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

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

npm i -g @nestjs/cli

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

Output@nestjs/[email protected]
added 220 packages from 163 contributors in 49.104s

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

nest --version

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

Output5.8.0

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

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

nest new blog-backend

コマンドを実行した直後に、nestは、descriptionversionauthorなどの基本情報を入力するように求めます。 先に進み、適切な詳細を提供します。 各プロンプトに応答した後、続行するには、コンピューターでENTERを押します。

次に、パッケージマネージャーを選択します。 このチュートリアルでは、npmを選択し、ENTERを押してNest.jsのインストールを開始します。

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

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

cd blog-backend

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

npm install --save @nestjs/mongoose mongoose

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

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

npm run start

これで、お気に入りのブラウザからhttp://localhost:3000に移動すると、アプリケーションが実行されていることがわかります。

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

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

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

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

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

  • データベーススキーマ:これは、データベースに格納する必要のあるデータの構造とタイプを定義するための青写真としてのデータの編成です。
  • interfaces :TypeScriptインターフェースはタイプチェックに使用されます。 これを使用して、アプリケーションに渡す必要のあるデータのタイプを定義できます。
  • データ転送オブジェクト:これは、データがネットワークを介して送信され、プロセス間でデータを伝送する方法を定義するオブジェクトです。

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

cd ./src/

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

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フォルダーに戻ります。

cd ~/blog-backend/src/blog/

interfacesという名前の新しいフォルダーを作成し、そのフォルダーに移動します。

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タイプのデータタイプを文字列値として正常に定義しています。 ファイルを保存して終了します。

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

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

blogフォルダーに戻ります。

cd ~/blog-backend/src/blog/

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

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は、@Module()デコレータによって識別され、controllersprovidersなどのプロパティを持つオブジェクトを取り込みます。 これらの各プロパティは、それぞれcontrollersprovidersの配列を取ります。

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

nest generate module blog

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

OutputCREATE /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()デコレータがあります。 新しいサービスを生成するには、プロジェクトディレクトリ内にいる間に、ターミナルから次のコマンドを実行します。

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)

ここで使用する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/commonmongoose、および@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にいることを確認し、次のコマンドを実行します。

nest generate controller blog

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

OutputCREATE /src/blog/blog.controller.spec.ts (474 bytes)

CREATE /src/blog/blog.controller.ts (97 bytes)

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

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

次に、テキストエディタで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
        })
    }
}

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

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

  • getPosts():このメソッドは、クライアントからHTTP GET要求を受信してデータベースからすべての投稿をフェッチし、適切な応答を返す機能を実行します。 @Get(``'``posts``'``)で飾られています。
  • getPost():これはpostIDをパラメーターとして受け取り、データベースから単一の投稿をフェッチします。 このメソッドに渡されるpostIDパラメーターに加えて、ValidateObjectId()という名前の追加のメソッドが追加されていることに気づきました。 このメソッドは、Nest.jsのPipeTransformインターフェイスを実装します。 その目的は、postIDパラメーターがデータベースで検出されることを検証および確認することです。 このメソッドは次のセクションで定義します。
  • addPost():このメソッドは、データベースに新しい投稿を追加するためのPOSTHTTPリクエストを処理します。

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

〜/ 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と、プレフィックスpostsを受け取るgetPosts()という名前のメソッドがあります。 これは、blog/postshttp:localhost:3000/blog/posts)のエンドポイントに送信されたGET要求は、getPosts()メソッドによって処理されることを意味します。 この例は、他のメソッドがHTTP要求を処理する方法と似ています。

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

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

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

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

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

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

cd ./src/blog/

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

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;
   }
}

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

サービスとコントローラーの両方を作成したら、BlogSchemaに基づくPostモデルをセットアップする必要があります。 この構成はルート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()メソッドを使用して、モジュールに登録するモデルを定義します。 これがないと、@injectModel()デコレータを使用してBlogService内にPostModelを挿入することはできません。 コンテンツの追加が完了したら、ファイルを保存して閉じます。

このステップでは、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フォルダーから作業する代わりに、ローカルプロジェクトの開発フォルダーに移動して次のコマンドを実行します。

npm install -g @vue/cli

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

vue create blog-frontend

このコマンドを入力すると、短いプロンプトが表示されます。 manually select featuresオプションを選択し、コンピューターでSPACEを押して複数の機能を強調表示することにより、このプロジェクトに必要な機能を選択します。 BabelRouterLinter / Formatterを選択します。

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

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

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

cd blog-frontend

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

npm run serve

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

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

npm install axios --save

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

まず、blog-frontend内にある端末から、./src/フォルダーに移動します。

cd ./src/

utilsという名前の別のフォルダーを作成します。

mkdir utils

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

〜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>:個別のコンポーネントごとのスタイルシート

まず、新しい投稿を作成するためのコンポーネントを作成することから始めます。 これを行うには、./src/componentsフォルダー内にpostという名前の新しいフォルダーを作成します。このフォルダーには、投稿に必要な再利用可能なコンポーネントが格納されます。 次に、テキストエディタを使用して、新しく作成した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>

これは、CreatePostコンポーネントの<template>セクションです。 これには、新しい投稿を作成するために必要な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()コンポーネントと同様のコンテンツが含まれています。 唯一の違いは、編集が必要な特定の投稿の詳細が含まれていることです。

次に、Edit.vue</template>セクションの直後に<script>セクションを追加します。

〜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>

このコードは、titleauthor、および投稿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コミュニティリポジトリにアクセスしてください。

次に、作成したすべての投稿をユーザーに表示するには、新しいコンポーネントを作成します。 src/viewsviewsフォルダーに移動すると、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コミュニティリポジトリにアクセスしてください。

ここで、Home.vue</template>セクションの直後に、次の</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コンポーネントを更新して、HomeおよびCreateコンポーネントへのリンクを作成します。 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>

HomeコンポーネントとCreateコンポーネントの両方へのリンクを含めるほかに、このコンポーネントのスタイルシートであり、一部のスタイルの定義を保持する<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 }
 ]
});

vue-routerモジュールからRouterをインポートし、modeおよびroutesパラメーターを渡してインスタンス化しました。 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>

ファイルを保存して終了し、blog-frontendが現在実行されていない場合は、npm run serveを使用してアプリケーションを再起動します。

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


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の詳細については、公式ドキュメントにアクセスしてください。