如何使用 Nest.js、MongoDB 和 Vue.js 构建博客

作者选择了(https://www.brightfunds.org/organizations/software-in-the-public-interest-inc)作为 写给捐款计划的一部分的捐款。

介绍

Nest.js是一个可扩展的服务器侧JavaScript框架,使用TypeScript构建,仍然保持与JavaScript的兼容性,这使得它成为构建高效可靠的后端应用程序的有效工具。

Vue.js是一个前端JavaScript框架,用于构建用户界面. 它有一个简单但非常强大的API以及出色的性能. Vue.js能够支持任何Web应用程序的前端层和逻辑,无论大小如何。

在这个教程中,你将建立一个Nest.js应用程序,让自己熟悉它的构件以及构建现代网络应用的基本原则. 通过将应用程序分为前端和后端两个不同部分来接近这个项目. 首先,你将专注于与Nest.js一起建造的后端API。 然后把注意力放在前端, 你和Vue.js一起建造。 这两个应用程序将运行在不同端口上,并作为单独的域来运行.

将构建博客应用程序,用户可藉此创建并保存新帖子,在主页上查看所保存帖子,并进行编辑和删除帖子等其它过程. 此外,您将把您的应用程序连接起来,并将其数据与MongoDB相接,该数据库是一个没有计划的NoSQL数据库,可以接收并存储JSON文档. 此教程侧重于在开发环境中构建您的应用程序 。 对于生产环境,您还应当考虑您的应用程序的用户认证.

前提条件

要完成本教程,您将需要:

Node.js 是 JavaScript 运行时环境,允许您在浏览器之外运行代码。它配有预安装的包管理器,名为 npm,允许您安装和更新包。 若要在 macOS 或 Ubuntu 18.04 上安装这些包,请遵循 如何在 macOS 上安装 Node.js 和创建本地开发环境或 [如何在 Ubuntu 18.04 上安装 Node.js 使用 PPA] 部分的步骤。 * MongoDB 安装在您的计算机上。 请遵循 这里下载并安装到您选择的操作系统。 若要成功安装 MongoDB,您可以使用它安装 MongoDB 或使用 MacBook 编辑器 [HebrewINK] 或 MacBook 编辑器 [S

<$>[注] 注: 本教程使用 macOS 机器进行开发. 如果您正在使用其他操作系统,您可能需要在整个教程中使用sudo用于npm命令。

步骤1:安装 Nest.js 和其他依赖

在本节中,您将通过在本地机器上安装应用程序和所需的依赖程序来开始使用 Nest.js。您可以通过使用 Nest.js 提供的 CLI来轻松安装 Nest.js,或者通过从 GitHub 安装启动项目来安装 Nest.js。

1npm i -g @nestjs/cli

您将看到类似于以下的输出:

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

要确认您安装了 Nest CLI,请从您的终端执行此命令:

1nest --version

您将看到输出显示在您的机器上安装的当前版本:

1[secondary_label Output]
25.8.0

您将使用命令来管理您的项目,并使用它来生成相关文件 - 例如控制器,模块和提供商。

要开始本教程的项目,请使用命令创建一个名为blog-backend的新Nest.js项目,从您的终端运行以下命令:

1nest new blog-backend

在运行命令后,‘nest’将提示您提供一些基本信息,如‘描述’、‘版本’和‘作者’。

接下来,您将选择一个包管理器. 为此教程的目的,选择npm,然后点击ENTER,开始安装 Nest.js。

Alt Creating a Nest project

这将在本地开发文件夹中的博客后端文件夹中生成一个新的Nest.js项目。

接下来,从您的终端导航到新项目的文件夹:

1cd blog-backend

运行以下命令来安装其他服务器依赖:

1npm install --save @nestjs/mongoose mongoose

您已经安装了 @nestjs/mongoose,这是一个 Nest.js 专用包,用于 MongoDB 的对象建模工具,以及 mongoose,是 Mongoose 的包。

现在,您将使用以下命令启动应用程序:

1npm run start

现在,如果你从你最喜欢的浏览器中导航到http://localhost:3000,你会看到你的应用程序正在运行。

Alt Welcome page of the fresh installation of Nest.js application

通过利用Nest CLI命令的可用性,您成功地生成了该项目,随后您继续运行应用程序并在本地计算机上的默认端口3000访问该应用程序。

步骤 2 — 配置和连接到数据库

在此步骤中,您将配置和集成 MongoDB 到您的 Nest.js 应用程序中。您将使用 MongoDB 来存储您的应用程序的数据。 MongoDB 将其数据存储在 documents 中,作为 field : value pairs。 要访问此数据结构,您将使用 Mongoose,这是一个对象文档建模(ODM),允许您定义代表 MongoDB 数据库存储的数据类型的方案。

要启动 MongoDB,打开一个单独的终端窗口,以便应用程序可以继续运行,然后执行以下命令:

1sudo mongod

这将启动MongoDB服务,并在您的机器的背景下运行数据库。

在文本编辑器中打开blog-backend项目,然后导航到./src/app.module.ts。你可以通过将安装的MongooseModule列入 rootApplicationModule中来建立连接到数据库。

 1[label ~/blog-backend/src/app.module.ts]
 2import { Module } from '@nestjs/common';
 3import { AppController } from './app.controller';
 4import { AppService } from './app.service';
 5import { MongooseModule } from '@nestjs/mongoose';
 6
 7@Module({
 8  imports: [
 9    MongooseModule.forRoot('mongodb://localhost/nest-blog', { useNewUrlParser: true }),
10  ],
11  controllers: [AppController],
12  providers: [AppService],
13})
14export class AppModule { }

在此文件中,您使用「forRoot()」方法提供连接到数据库. 保存并关闭文件,当您完成编辑。

有了这个,您使用 MongoDB 的 Mongoose 模块设置了数据库连接,在下一节中,您将使用 Mongoose 库、TypeScript 接口和数据传输对象 (DTO) 方案创建数据库方案。

步骤 3 — 创建数据库方案、接口和 DTO

在此步骤中,您将使用 Mongoose 为您的数据库创建 schemainterfacedata transfer object。Mongoose 可帮助管理数据之间的关系,并为数据类型提供方案验证。

  • database schema: 这是一个数据的组织作为一个蓝图来定义数据库需要存储的结构和数据类型。 * interfaces: TypeScript 界面用于类型检查。

首先,回到您的终端,该应用程序目前正在运行,并用CTRL + C停止过程,然后导航到./src/文件夹:

1cd ./src/

然后,创建一个名为博客的目录,并在其中创建一个方案文件夹:

1mkdir -p blog/schemas

方案文件夹中,创建一个名为blog.schema.ts的新文件,然后使用文本编辑器打开它,然后添加以下内容:

 1[label ~/blog-backend/src/blog/schemas/blog.schema.ts]
 2import * as mongoose from 'mongoose';
 3
 4export const BlogSchema = new mongoose.Schema({
 5    title: String,
 6    description: String,
 7    body: String,
 8    author: String,
 9    date_posted: String
10})

在这里,您使用 Mongoose 来定义将存储在数据库中的数据类型. 您已指定所有字段将存储并只接受字符串值。

现在,随着数据库方案的确定,您可以继续创建接口。

首先,返回博客文件夹:

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

创建一个名为界面的新文件夹,并进入它:

1mkdir interfaces

界面文件夹中,创建一个名为post.interface.ts的新文件,并使用文本编辑器打开它。

 1[label ~/blog-backend/src/blog/interfaces/post.interface.ts]
 2import { Document } from 'mongoose';
 3
 4export interface Post extends Document {
 5    readonly title: string;
 6    readonly description: string;
 7    readonly body: string;
 8    readonly author: string;
 9    readonly date_posted: string
10}

在此文件中,您已成功定义一个邮件类型的数据类型为字符串值. 保存和退出文件。

由于您的应用程序将执行将数据发布到数据库的功能,您将创建一个数据传输对象,该对象将定义如何通过网络发送数据。

要做到这一点,在./src/blog文件夹中创建一个dto文件夹,在新创建的文件夹中创建一个名为create-post.dto.ts的文件夹。

返回博客文件夹:

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

然后创建一个名为dto的文件夹并进入它:

1mkdir dto

dto文件夹中,创建一个名为create-post.dto.ts的新文件,并使用文本编辑器打开它,添加以下内容:

1[label ~/blog-backend/src/blog/dto/create-post.dto.ts]
2export class CreatePostDTO {
3    readonly title: string;
4    readonly description: string;
5    readonly body: string;
6    readonly author: string;
7    readonly date_posted: string
8}

您已经标记了CreatePostDTO类中的每个个别属性以具有字符串的数据类型,并将其标记为 readonly,以避免不必要的突变。

在此步骤中,您为您的数据库创建了数据库方案、接口和数据传输对象,然后为您的数据库存储数据。

第4步:为博客创建模块、控制器和服务

在此步骤中,您将通过为您的博客创建一个模块来改进应用程序的现有结构。该模块将组织您的应用程序的文件结构。接下来,您将创建一个控制器来处理路由并处理客户端的HTTP请求。

创建一个模块

Nest.js 应用程序具有模块化的设计;它配备了单个根模块,这对于一个小型应用程序往往是足够的。

在 Nest.js 中,一个 _module 被@Module()装饰器识别出来,并包含一个具有控制器供应商等属性的对象。

您将为此博客应用程序生成一个新的模块,以保持结构更加有组织。 首先,在~/blog-backend文件夹中,执行以下命令:

1nest generate module blog

您将看到类似于以下的输出:

1[secondary_label Output]
2CREATE /src/blog/blog.module.ts
3
4UPDATE /src/app.module.ts

该命令为应用程序生成了一个名为blog.module.ts的新模块,并将新创建的模块导入到应用程序的根模块中。

在这个文件中,你会看到以下代码:

1[label ~/blog-backend/src/blog/blog.module.ts]
2import { Module } from '@nestjs/common';
3
4@Module({})
5export class BlogModule {}

您将在教程中稍后更新此BlogModule以所需的属性. 保存和退出文件。

创建一个服务

可在 Nest.js 中称为服务提供商的 service 旨在从控制器中删除逻辑,这些控制器仅用于处理 HTTP 请求,并将更复杂的任务重定向到服务中。

1nest generate service blog

您将看到类似于以下的输出:

1[secondary_label Output]  
2CREATE /src/blog/blog.service.spec.ts (445 bytes)
3
4CREATE /src/blog/blog.service.ts (88 bytes)
5
6UPDATE /src/blog/blog.module.ts (529 bytes)

这里使用的nest命令创建了一个blog.service.spec.ts文件,你可以使用它来测试它还创建了一个新的blog.service.ts文件,它将包含这个应用程序的所有逻辑,并处理将文档添加到MongoDB数据库中,并自动导入新创建的服务并添加到blog.module.ts。

该服务处理应用程序内的所有逻辑,负责与数据库进行交互,并将适当的回复返回控制器. 要做到这一点,请在文本编辑器中打开‘blog.service.ts’文件,并用以下内容替换内容:

 1[label ~/blog-backend/src/blog/blog.service.ts]
 2import { Injectable } from '@nestjs/common';
 3import { Model } from 'mongoose';
 4import { InjectModel } from '@nestjs/mongoose';
 5import { Post } from './interfaces/post.interface';
 6import { CreatePostDTO } from './dto/create-post.dto';
 7
 8@Injectable()
 9export class BlogService {
10
11    constructor(@InjectModel('Post') private readonly postModel: Model<Post>) { }
12
13    async getPosts(): Promise<Post[]> {
14        const posts = await this.postModel.find().exec();
15        return posts;
16    }
17
18    async getPost(postID): Promise<Post> {
19        const post = await this.postModel
20            .findById(postID)
21            .exec();
22        return post;
23    }
24
25    async addPost(createPostDTO: CreatePostDTO): Promise<Post> {
26        const newPost = await this.postModel(createPostDTO);
27        return newPost.save();
28    }
29
30    async editPost(postID, createPostDTO: CreatePostDTO): Promise<Post> {
31        const editedPost = await this.postModel
32            .findByIdAndUpdate(postID, createPostDTO, { new: true });
33        return editedPost;
34    }
35
36    async deletePost(postID): Promise<any> {
37        const deletedPost = await this.postModel
38            .findByIdAndRemove(postID);
39        return deletedPost;
40    }
41
42}

在此文件中,您首先从 @nestjs/common, mongoose@nestjs/mongoose 导入所需的模块,您还导入了一个名为 Post 的接口和一个数据传输对象 CreatePostDTO

构建者中,你添加了@InjectModel(``Post),将Post模型注入到这个BlogService类别中,你现在可以使用这个注入模型来检索所有帖子,收集一个帖子,并进行其他与数据库相关的活动。

接下来,您创建了以下方法:

  • getPosts():从数据库中获取所有帖子. * getPost():从数据库中获取单个帖子. * addPost():添加新帖子. * editPost():更新单个帖子. * deletePost():删除特定帖子。

保存和退出文件,当你完成。

您已经完成了设置和创建几种方法,将从后端API处理与MongoDB数据库的正确交互,现在,您将创建所需的路径,以处理来自前端客户端的HTTP呼叫。

生成控制器

在 Nest. js 中, controllers 负责处理来自应用程序客户端的任何传入请求,并返回适当的响应。

为了满足您的博客应用程序的所有 HTTP 请求,您将利用命令生成新的控制器文件。

1nest generate controller blog

你会看到类似的输出:

1[secondary_label Output]
2CREATE /src/blog/blog.controller.spec.ts (474 bytes)
3
4CREATE /src/blog/blog.controller.ts (97 bytes)
5
6UPDATE /src/blog/blog.module.ts (483 bytes)

输出表明,这个命令在src/blog目录中创建了两个新文件,它们是blog.controller.spec.tsblog.controller.ts。前者是你可以用来为新创建的控制器编写自动测试的文件。后者是控制器文件本身。Nest.js中的控制器是用@Controller元数据装饰的TypeScript文件。该命令还导入了新创建的控制器并添加到博客模块中。

接下来,用文本编辑器打开‘blog.controller.ts’文件,并更新它以以下内容:

 1[label ~/blog-backend/src/blog/blog.controller.ts]
 2import { Controller, Get, Res, HttpStatus, Param, NotFoundException, Post, Body, Query, Put, Delete } from '@nestjs/common';
 3import { BlogService } from './blog.service';
 4import { CreatePostDTO } from './dto/create-post.dto';
 5import { ValidateObjectId } from '../shared/pipes/validate-object-id.pipes';
 6
 7@Controller('blog')
 8export class BlogController {
 9
10    constructor(private blogService: BlogService) { }
11
12    @Get('posts')
13    async getPosts(@Res() res) {
14        const posts = await this.blogService.getPosts();
15        return res.status(HttpStatus.OK).json(posts);
16    }
17
18    @Get('post/:postID')
19    async getPost(@Res() res, @Param('postID', new ValidateObjectId()) postID) {
20        const post = await this.blogService.getPost(postID);
21        if (!post) throw new NotFoundException('Post does not exist!');
22        return res.status(HttpStatus.OK).json(post);
23
24    }
25
26    @Post('/post')
27    async addPost(@Res() res, @Body() createPostDTO: CreatePostDTO) {
28        const newPost = await this.blogService.addPost(createPostDTO);
29        return res.status(HttpStatus.OK).json({
30            message: "Post has been submitted successfully!",
31            post: newPost
32        })
33    }
34}

在此文件中,您首先导入了从@nestjs/common模块处理HTTP请求所需的模块,然后,您导入了三个新模块:BlogService,CreatePostDTOValidateObjectId。之后,您通过构建器将BlogService注入控制器,以获取访问并利用已在BlogService文件中定义的功能。

最后,您创建了以下非同步方法:

  • getPosts():此方法将执行从客户端接收 HTTP GET 请求的功能,从数据库中提取所有帖子,然后返回适当的回复。它装饰着一个 @Get(posts). * getPost():此方法将一个 postID 作为参数,并从数据库中提取一个单一的帖子。 除了向该方法传递的 postID 参数外,您还实现了添加一个名为 ValidateObjectId() 的额外方法。 此方法实现了 Nest.js 的 PipeTransform' 接口。 其目的是验证并确保在数据库中找到 post ID参数。 您将在下一节中定义此方法。 *addPost()

要编辑和删除某个特定帖子,你需要将两个方法添加到blog.controller.ts文件中。 要做到这一点,请在你之前在blog.controller.ts中添加的addPost()方法后直接添加以下editPost()deletePost()方法:

 1[label ~/blog-backend/src/blog/blog.controller.ts]
 2
 3...
 4@Controller('blog')
 5export class BlogController {
 6    ...
 7    @Put('/edit')
 8    async editPost(
 9        @Res() res,
10        @Query('postID', new ValidateObjectId()) postID,
11        @Body() createPostDTO: CreatePostDTO
12    ) {
13        const editedPost = await this.blogService.editPost(postID, createPostDTO);
14        if (!editedPost) throw new NotFoundException('Post does not exist!');
15        return res.status(HttpStatus.OK).json({
16            message: 'Post has been successfully updated',
17            post: editedPost
18        })
19    }
20
21    @Delete('/delete')
22    async deletePost(@Res() res, @Query('postID', new ValidateObjectId()) postID) {
23        const deletedPost = await this.blogService.deletePost(postID);
24        if (!deletedPost) throw new NotFoundException('Post does not exist!');
25        return res.status(HttpStatus.OK).json({
26            message: 'Post has been deleted!',
27            post: deletedPost
28        })
29    }
30}

这里你添加了:

  • editPost():此方法接受一个postID的查询参数,并将执行更新单个帖子的功能。它还使用了ValidateObjectId方法来为你需要编辑的帖子提供适当的验证。 * deletePost():此方法将接受一个postID的查询参数,并将从数据库中删除特定帖子。

BlogController类似,您在这里定义的每一种非同步方法都有一个元数据装饰器,并采用了Nest.js作为路由机制的前缀,它控制哪个控制器接收哪个请求,并指向应该处理请求并返回响应的方法。

例如,您在本节中创建的BlogController有一个blog前缀和一种名为getPosts()的方法,其中包含一个posts前缀,这意味着任何向blog/posts的终端发送的GET请求(http:localhost:3000/blog/posts)都将通过getPosts()方法处理。

保存和退出文件。

对于完整的‘blog.controller.ts’文件,请访问此应用程序的 DO 社区存储库

在本节中,您创建了一个模块来保持应用程序更有组织性。您还创建了一个服务来处理应用程序的业务逻辑,通过与数据库进行交互并返回适当的响应。 最后,您生成了一个控制器,并创建了处理HTTP请求的必要方法,如从客户端处理GET,POST,PUTDELETE

步骤5 — 创建Mongoose的额外验证

您可以在博客应用程序中通过一个独特的ID来识别每个帖子,也称为PostID。这意味着收集一个帖子将要求您将这个ID传递为查询参数。为了验证这个postID参数并确保该帖子在数据库中可用,您需要创建一个可重复使用的函数,可以从BlogController中的任何方法中初始化。

要配置此功能,请导航到 ./src/blog 文件夹:

1cd ./src/blog/

然后,创建一个名为共享的新文件夹:

1mkdir -p shared/pipes

pipes文件夹中,使用文本编辑器创建一个名为validate-object-id.pipes.ts的新文件,然后打开它。

 1[label ~/blog-backend/src/blog/shared/pipes/validate-object-id.pipes.ts]
 2import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
 3import * as mongoose from 'mongoose';
 4
 5@Injectable()
 6export class ValidateObjectId implements PipeTransform<string> {
 7   async transform(value: string, metadata: ArgumentMetadata) {
 8       const isValid = mongoose.Types.ObjectId.isValid(value);
 9       if (!isValid) throw new BadRequestException('Invalid ID!');
10       return value;
11   }
12}

ValidateObjectId()类从@nestjs/common模块中实现了PipeTransform方法. 它有一个名为transform()的单一方法,在这种情况下将值作为参数 - postID。 使用上述方法,本应用程序的前端的任何具有postID的HTTP请求将被视为无效。

在创建服务和控制器后,您需要设置基于BlogSchema邮件模型。 此配置可以在ApplicationModule根中设置,但在这种情况下,在BlogModule中构建模型将维持您的应用程序的组织。

 1[label ~/blog-backend/src/blog/blog.module.ts]
 2import { Module } from '@nestjs/common';
 3import { BlogController } from './blog.controller';
 4import { BlogService } from './blog.service';
 5import { MongooseModule } from '@nestjs/mongoose';
 6import { BlogSchema } from './schemas/blog.schema';
 7
 8@Module({
 9 imports: [
10   MongooseModule.forFeature([{ name: 'Post', schema: BlogSchema }])
11],
12 controllers: [BlogController],
13 providers: [BlogService]
14})
15export class BlogModule { }

此模块使用MongooseModule.forFeature()方法来定义应该在模块中注册的模型.如果不这样做,使用@injectModel()装饰器在BlogService中注入PostModel就不会起作用。

在此步骤中,您已经与 Nest.js 创建了完整的后端 RESTful API,并将其集成到 MongoDB. 在下一节中,您将配置服务器以允许来自其他服务器的 HTTP 请求,因为您的前端应用程序将运行在不同的端口。

步骤6 - 允许 CORS

HTTP 请求从一个域到另一个域通常是默认阻止的,除非服务器指定允许它。 为了让您的前端应用程序向后端服务器提出请求,您必须启用 Cross-origin resource sharing (CORS),这是一种允许在网页上对有限资源的请求的技术。

在 Nest.js 中,要启用 CORS,您需要将单一方法添加到您的 main.ts 文件中,然后在您的文本编辑器中打开该文件,该文件位于 `./src/main.ts,并以以下突出内容更新:

 1[label ~/blog-backend/src/main.ts]
 2import { NestFactory } from '@nestjs/core';
 3import { AppModule } from './app.module';
 4
 5async function bootstrap() {
 6 const app = await NestFactory.create(AppModule);
 7 app.enableCors();
 8 await app.listen(3000);
 9}
10bootstrap();

保存和退出文件。

现在你已经完成了后端设置,你将把注意力转移到前端,并使用Vue.js来消耗迄今为止建立的API。

第7步:创建Vue.js前端

在本节中,您将使用 Vue.js 创建前端应用程序。 Vue CLI是一个标准工具,允许您快速生成和安装新的 Vue.js 项目,而无需太多的麻烦。

首先,您需要在您的计算机上全球安装 Vue CLI. 打开另一个终端,而不是从博客后端文件夹中工作,请导航到本地项目的开发文件夹并运行:

1npm install -g @vue/cli

一旦安装过程完成,您将使用vue命令创建新的 Vue.js 项目:

1vue create blog-frontend

输入此命令后,您将看到一个简短的提示。选择手动选择功能选项,然后通过在您的计算机上按SPACE来选择您需要的此项目的功能,以突出多个功能。

Alt Vue project CLI set up

对于下一个指示,键入y以使用路由器的历史模式;这将确保路由器文件中启用历史模式,该模式将自动生成用于该项目。 此外,选择仅使用错误预防的ESLint以选择一个Linter/formatter配置。 接下来,选择Lint on save以获取额外的Lint功能。 然后选择将您的配置保存到专门的配置文件为未来的项目。 输入您的预设名称,例如vueconfig

Alt Vue.js final CLI set up

Vue.js 将开始在一个名为blog-frontend的目录中创建应用程序及其所需的所有依赖。

一旦安装过程完成,请导航 Vue.js 应用程序:

1cd blog-frontend

然后,启动开发服务器:

1npm run serve

您的应用程序将在http://localhost:8080上运行。

Alt Vue.js home view

由于您将在本应用程序内执行 HTTP 请求,您将需要安装 Axios,该应用程序是浏览器的基于承诺的 HTTP 客户端。您将使用 Axios 在此执行应用程序内的不同组件的 HTTP 请求。

1npm install axios --save

您的前端应用程序将从应用程序内部的不同组件对特定域的后端 API 进行 API 调用. 为了确保该应用程序的正确结构,您可以创建一个帮助文件并定义服务器的baseURL

首先,从您仍在blog-frontend中的终端,导航到./src/文件夹:

1cd ./src/

创建一个名为utils的文件夹:

1mkdir utils

用途文件夹中,使用文本编辑器创建一个名为helper.js的新文件,然后打开它。

1[label ~blog-frontend/src/utils/helper.js]
2export const server = {
3
4baseURL: 'http://localhost:3000'
5
6}

通过定义baseURL,您将能够从您 Vue.js 组件文件中的任何地方调用它. 如果您需要更改 URL,则更容易更新该文件中的baseURL,而不是在您的应用程序中。

在本节中,您安装了Vue CLI,一个用于创建新的Vue.js应用程序的工具. 您使用这个工具来创建blog-frontend应用程序. 此外,您运行了应用程序并安装了一个名为Axios的库,您将每次在应用程序中使用HTTP调用。

第8步:创建可重复使用的组件

现在,您将为您的应用程序创建可重复使用的组件,这是 Vue.js 应用程序的标准结构。Vue.js 中的组件系统使开发人员能够构建一个独立的界面单个单元,可以有自己的状态,标记和风格,这使得 Vue.js 中的组件可以重复使用。

每个 Vue.js 组件都包含三个不同的部分:

  • <template>:包含 HTML 内容 * <script>:包含所有基本的前端逻辑,并定义函数 * <style>:每个组件的样式表

首先,您将创建一个组件来创建一个新的帖子。 要做到这一点,在./src/components文件夹中创建一个名为post的新文件夹,该文件夹将容纳帖子所需的可重复使用的组件。 然后使用文本编辑器,在新创建的post文件夹中,创建另一个文件并命名它为Create.vue。 打开新文件并添加以下代码,其中包含提交帖子所需的输入字段:

 1[label ~blog-frontend/src/components/post/Create.vue]
 2<template>
 3  <div>
 4       <div class="col-md-12 form-wrapper">
 5         <h2> Create Post </h2>
 6         <form id="create-post-form" @submit.prevent="createPost">
 7              <div class="form-group col-md-12">
 8               <label for="title"> Title </label>
 9               <input type="text" id="title" v-model="title" name="title" class="form-control" placeholder="Enter title">
10              </div>
11             <div class="form-group col-md-12">
12                 <label for="description"> Description </label>
13                 <input type="text" id="description" v-model="description" name="description" class="form-control" placeholder="Enter Description">
14             </div>
15             <div class="form-group col-md-12">
16                 <label for="body"> Write Content </label>
17                 <textarea id="body" cols="30" rows="5" v-model="body" class="form-control"></textarea>
18             </div>
19             <div class="form-group col-md-12">
20                 <label for="author"> Author </label>
21                 <input type="text" id="author" v-model="author" name="author" class="form-control">
22             </div>
23
24             <div class="form-group col-md-4 pull-right">
25                 <button class="btn btn-success" type="submit"> Create Post </button>
26             </div>          
27         </form>
28       </div>
29   </div>
30</template>

这是CreatePost组件的<模板>部分,它包含创建新帖子所需的HTML输入元素,每个输入字段都有一个v-model指令作为输入属性,以确保每个表单输入的双向数据连接,以便Vue.js轻松获取用户的输入。

接下来,将<script>部分添加到相同的文件中,直接跟随前面的内容:

 1[label ~blog-frontend/src/components/post/Create.vue]
 2...
 3<script>
 4import axios from "axios";
 5import { server } from "../../utils/helper";
 6import router from "../../router";
 7export default {
 8 data() {
 9   return {
10     title: "",
11     description: "",
12     body: "",
13     author: "",
14     date_posted: ""
15   };
16 },
17 created() {
18   this.date_posted = new Date().toLocaleDateString();
19 },
20 methods: {
21   createPost() {
22     let postData = {
23       title: this.title,
24       description: this.description,
25       body: this.body,
26       author: this.author,
27       date_posted: this.date_posted
28     };
29     this.__submitToServer(postData);
30   },
31   __submitToServer(data) {
32     axios.post(`${server.baseURL}/blog/post`, data).then(data => {
33       router.push({ name: "home" });
34     });
35   }
36 }
37};
38</script>

在这里,你添加了一种名为createPost()的方法来创建一个新的帖子,并使用Axios将其提交给服务器。

您将在本教程中稍后配置 vue-router 来实现重定向。

完成编辑后保存并关闭该文件. 对于完整的 Create.vue 文件,请访问此应用程序的 DO 社区存储库

现在,您需要创建另一个组件来编辑特定的帖子。 导航到 ./src/components/post 文件夹并创建另一个文件,并命名它为 Edit.vue. 添加以下代码,其中包含 <template> 部分:

 1[label ~blog-frontend/src/components/post/Edit.vue]
 2<template>
 3<div>
 4      <h4 class="text-center mt-20">
 5       <small>
 6         <button class="btn btn-success" v-on:click="navigate()"> View All Posts </button>
 7       </small>
 8    </h4>
 9        <div class="col-md-12 form-wrapper">
10          <h2> Edit Post </h2>
11          <form id="edit-post-form" @submit.prevent="editPost">
12            <div class="form-group col-md-12">
13                <label for="title"> Title </label>
14                <input type="text" id="title" v-model="post.title" name="title" class="form-control" placeholder="Enter title">
15            </div>
16            <div class="form-group col-md-12">
17                <label for="description"> Description </label>
18                <input type="text" id="description" v-model="post.description" name="description" class="form-control" placeholder="Enter Description">
19            </div>
20            <div class="form-group col-md-12">
21                <label for="body"> Write Content </label>
22                <textarea id="body" cols="30" rows="5" v-model="post.body" class="form-control"></textarea>
23            </div>
24            <div class="form-group col-md-12">
25                <label for="author"> Author </label>
26                <input type="text" id="author" v-model="post.author" name="author" class="form-control">
27            </div>
28
29            <div class="form-group col-md-4 pull-right">
30                <button class="btn btn-success" type="submit"> Edit Post </button>
31            </div>
32          </form>
33        </div>
34    </div>
35</template>

此模板部分包含与CreatePost()组件类似的内容;唯一的区别是它包含需要编辑的特定帖子的详细信息。

接下来,在Edit.vue中的</template>部分后直接添加<script>部分:

 1[label ~blog-frontend/src/components/post/Edit.vue]
 2...
 3<script>
 4import { server } from "../../utils/helper";
 5import axios from "axios";
 6import router from "../../router";
 7export default {
 8  data() {
 9    return {
10      id: 0,
11      post: {}
12    };
13  },
14  created() {
15    this.id = this.$route.params.id;
16    this.getPost();
17  },
18  methods: {
19    editPost() {
20      let postData = {
21        title: this.post.title,
22        description: this.post.description,
23        body: this.post.body,
24        author: this.post.author,
25        date_posted: this.post.date_posted
26      };
27
28      axios
29        .put(`${server.baseURL}/blog/edit?postID=${this.id}`, postData)
30        .then(data => {
31          router.push({ name: "home" });
32        });
33    },
34    getPost() {
35      axios
36        .get(`${server.baseURL}/blog/post/${this.id}`)
37        .then(data => (this.post = data.data));
38    },
39    navigate() {
40      router.go(-1);
41    }
42  }
43};
44</script>

在这里,您获得了路由参数 id 来识别特定的帖子,然后创建了一种名为 getPost() 的方法,从数据库中获取该帖子的详细信息,并用它更新页面。

若要查看完整的「Edit.vue」檔案,請前往此應用程式的 DO Community repository

现在,你将创建一个新的组件在./src/components/post文件夹中,并将其命名为Post.vue

 1[label ~blog-frontend/src/components/post/Post.vue]
 2<template>
 3    <div class="text-center">
 4        <div class="col-sm-12">
 5      <h4 style="margin-top: 30px;"><small><button class="btn btn-success" v-on:click="navigate()"> View All Posts </button></small></h4>
 6      <hr>
 7      <h2>{{ post.title }}</h2>
 8      <h5><span class="glyphicon glyphicon-time"></span> Post by {{post.author}}, {{post.date_posted}}.</h5>
 9      <p> {{ post.body }} </p>
10
11    </div>
12    </div>
13</template>

此代码返回包含标题作者身体的帖子的细节。

现在,直接在</template>之后,将以下代码添加到文件中:

 1[label ~blog-frontend/src/components/post/Post.vue]
 2...
 3<script>
 4import { server } from "../../utils/helper";
 5import axios from "axios";
 6import router from "../../router";
 7export default {
 8  data() {
 9    return {
10      id: 0,
11      post: {}
12    };
13  },
14  created() {
15    this.id = this.$route.params.id;
16    this.getPost();
17  },
18  methods: {
19    getPost() {
20      axios
21        .get(`${server.baseURL}/blog/post/${this.id}`)
22        .then(data => (this.post = data.data));
23    },
24    navigate() {
25      router.go(-1);
26    }
27  }
28};
29</script>

类似于编辑帖子组件的<script>部分,您获得了路线参数id并使用它来检索特定帖子的详细信息。

当您完成添加内容时,请保存并关闭该文件. 对于完整的 Post.vue 文件,请访问此应用程序的 DO Community repository

接下来,要向用户显示所有创建的帖子,您将创建一个新组件. 如果您在src/views中的Views文件夹中导航,您将看到一个Home.vue组件 - 如果这个文件不存在,请使用文本编辑器创建它,添加以下代码:

 1[label ~blog-frontend/src/views/Home.vue]
 2<template>
 3    <div>
 4
 5      <div class="text-center">
 6        <h1>Nest Blog Tutorial</h1>
 7       <p> This is the description of the blog built with Nest.js, Vue.js and MongoDB</p>
 8
 9       <div v-if="posts.length === 0">
10            <h2> No post found at the moment </h2>
11        </div>
12      </div>
13
14        <div class="row">
15           <div class="col-md-4" v-for="post in posts" :key="post._id">
16              <div class="card mb-4 shadow-sm">
17                <div class="card-body">
18                   <h2 class="card-img-top">{{ post.title }}</h2>
19                  <p class="card-text">{{ post.body }}</p>
20                  <div class="d-flex justify-content-between align-items-center">
21                    <div class="btn-group" style="margin-bottom: 20px;">
22                      <router-link :to="{name: 'Post', params: {id: post._id}}" class="btn btn-sm btn-outline-secondary">View Post </router-link>
23                       <router-link :to="{name: 'Edit', params: {id: post._id}}" class="btn btn-sm btn-outline-secondary">Edit Post </router-link>
24                       <button class="btn btn-sm btn-outline-secondary" v-on:click="deletePost(post._id)">Delete Post</button>
25                    </div>
26                  </div>
27
28                  <div class="card-footer">
29                    <small class="text-muted">Posted on: {{ post.date_posted}}</small><br/>
30                    <small class="text-muted">by: {{ post.author}}</small>
31                  </div>
32
33                </div>
34              </div>
35            </div>
36      </div>
37    </div>
38</template>

在这里,在<template>部分中,您使用<router-link>创建了编辑和查看帖子的链接,通过将post._id作为查询参数。您还使用了v-if指令为用户提供条件性帖子。

若要查看完整的「Home.vue」檔案,請前往此應用程式的 DO Community repository

现在,直接在Home.vue中的</template>部分之后,添加以下</script>部分:

 1[label ~blog-frontend/src/views/Home.vue]
 2...
 3<script>
 4// @ is an alias to /src
 5import { server } from "@/utils/helper";
 6import axios from "axios";
 7
 8export default {
 9  data() {
10    return {
11      posts: []
12    };
13  },
14  created() {
15    this.fetchPosts();
16  },
17  methods: {
18    fetchPosts() {
19      axios
20        .get(`${server.baseURL}/blog/posts`)
21        .then(data => (this.posts = data.data));
22    },
23    deletePost(id) {
24      axios.delete(`${server.baseURL}/blog/delete?postID=${id}`).then(data => {
25        console.log(data);
26        window.location.reload();
27      });
28    }
29  }
30};
31</script>

在该文件的<script>部分中,您创建了一种名为fetchPosts()的方法来从数据库中提取所有帖子,并更新了来自服务器的数据。

现在,您将更新前端应用程序的应用程序组件,以创建链接到首页创建组件。

 1[label ~blog-frontend/src/App.vue]
 2<template>
 3  <div id="app">
 4    <div id="nav">
 5      <router-link to="/">Home</router-link> |
 6      <router-link to="/create">Create</router-link>
 7    </div>
 8    <router-view/>
 9  </div>
10</template>
11
12<style>
13#app {
14  font-family: "Avenir", Helvetica, Arial, sans-serif;
15  -webkit-font-smoothing: antialiased;
16  -moz-osx-font-smoothing: grayscale;
17  color: #2c3e50;
18}
19#nav {
20  padding: 30px;
21  text-align: center;
22}
23
24#nav a {
25  font-weight: bold;
26  color: #2c3e50;
27}
28
29#nav a.router-link-exact-active {
30  color: #42b983;
31}
32</style>

除了包含首页创建组件的链接外,您还包括了<风格>部分,该部分是该组件的风格表,它包含页面上某些元素的风格定义。

在此步骤中,您已经为您的应用程序创建了所有必要的组件,接下来,您将配置路由器文件。

步骤9:设置路由

创建所有必要的可重复使用的组件后,您现在可以通过更新其内容并链接到您创建的所有组件来正确配置路由器文件。

 1[label ~blog-frontend/src/router.js]
 2import Vue from 'vue'
 3import Router from 'vue-router'
 4import HomeComponent from '@/views/Home';
 5import EditComponent from '@/components/post/Edit';
 6import CreateComponent from '@/components/post/Create';
 7import PostComponent from '@/components/post/Post';
 8
 9Vue.use(Router)
10
11export default new Router({
12 mode: 'history',
13 routes: [
14   { path: '/', redirect: { name: 'home' } },
15   { path: '/home', name: 'home', component: HomeComponent },
16   { path: '/create', name: 'Create', component: CreateComponent },
17   { path: '/edit/:id', name: 'Edit', component: EditComponent },
18   { path: '/post/:id', name: 'Post', component: PostComponent }
19 ]
20});

您从Vue Router模块中导入了Router,并通过通过模式路径参数进行实例化。Vue Router的默认模式是一个哈希模式,它使用URL hash来模拟完整的URL,以便页面不会在URL更改时重新加载。为了使哈希不必要,您在这里使用历史模式来实现URL导航而无需重新加载。最后,在路径选项中,您指定了终端点的路径 - 路径的名称和路径在应用程序中呼叫时应该返回的组件。 保存和退出文件。

现在你已经设置了向应用程序的路由,你需要包括 Bootstrap 文件,以帮助为应用程序的用户界面预先构建的样式。 要做到这一点,在文本编辑器中打开 ./public/index.html 文件,并添加 Bootstrap 的 CDN 文件,将下列内容添加到文件中:

 1[label ~blog-frontend/public/index.html]
 2<!DOCTYPE html>
 3<html lang="en">
 4<head>
 5  ...
 6  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
 7  <title>blog-frontend</title>
 8</head>
 9<body>
10   ...
11</body>
12</html>

保存和退出文件,然后重新启动应用程序为您的博客前端npm run server,如果它目前没有运行。

注意:** 确保后端服务器和 MongoDB 实例都运行,否则,从另一个终端导航到blog-backend并运行npm run start

现在你可以通过创建和编辑帖子来测试你的博客。

Alt Create a new post

点击您的应用程序中的创建以查看创建帖子屏幕,该屏幕与创建组件文件有关并渲染。 输入字段中输入值,然后点击创建帖子按钮提交帖子。

应用程序的首页表示HomeComponent。这个组件有一个方法,发送了一个HTTP调用来从数据库中收集所有帖子并向用户显示。

Alt View all posts from the database

点击特定帖子的编辑帖子按钮将带您到编辑页面,您可以将任何更改纳入并保存您的帖子。

Alt Edit a new post

在本节中,您配置并设置了应用程序的路由,有了这个,您的博客应用程序已经准备好了。

结论

在本教程中,您通过使用 Nest.js 探索了一种新方法来构建 Node.js 应用程序,您使用 Nest.js 创建了一个简单的博客应用程序,以构建后端 RESTful API,并使用 Vue.js 来处理所有前端逻辑。

要了解如何将身份验证添加到您的应用程序中,您可以使用 Passport.js,一个流行的 Node.js 身份验证库。

您可以找到这个项目的完整源代码(在GitHub上)。有关Nest.js的更多信息,请访问官方文件(在GitHub上)。

Published At
Categories with 技术
comments powered by Disqus