您的位置:首页 > Web前端 > Node.js

node-postgres_如何使用NestJS,Postgres和Sequelize构建Web API-入门指南

2020-08-20 12:32 701 查看

node-postgres

NestJS is an MVC framework for building efficient, scalable Node.js server-side applications.

NestJS是一个MVC框架,用于构建高效,可扩展的Node.js服务器端应用程序。

It is built with and fully supports TypeScript (yet still enables developers to code in pure JavaScript). It also combines elements of Object Oriented Programming, Functional Programming, and Functional Reactive Programming.

它使用TypeScript构建并完全支持TypeScript (但仍使开发人员能够使用纯JavaScript进行编码)。 它还结合了面向对象编程,功能编程和功能响应式编程的元素。

One of the key benefits of Nest is that it provides an out-of-the-box application architecture that allows developers and teams to create highly testable, scalable, loosely coupled, and easily maintainable applications.

Nest的主要优点之一是,它提供了一种现成的应用程序体系结构,使开发人员和团队可以创建可高度测试,可伸缩,松散耦合且易于维护的应用程序。

我们正在建造什么 (What we are building)

In this post, I will take you through the journey of getting started with Nest. We will build a Mini Blog that's a Web RESTful API Application.

在这篇文章中,我将带您完成Nest入门。 我们将建立一个迷你博客,它是一个Web RESTful API应用程序。

This simple Mini Blog application will cover:

这个简单的迷你博客应用程序将涵盖:

  • Setting up Sequelize and Postgres Database

    设置Sequelize和Postgres数据库
  • Authentication with Passport (Login and Sign up)

    使用Passport进行身份验证(登录和注册)
  • Validating user input

    验证用户输入
  • Route protection with JWT

    JWT的路线保护
  • Creating, Reading, Updating, and Deleting a blog post

    创建,阅读,更新和删除博客文章

先决条件 (Prerequisites)

Knowledge of TypeScript and JavaScript is very important to follow along with this tutorial. Experience with Angular is a plus, but no worries – this post will explain every concept you need to know about Nest.

TypeScript和JavaScript的知识对于本教程来说非常重要。 拥有Angular经验会加分,但不要担心–这篇文章将解释您需要了解的有关Nest的每个概念。

You will need to install Postman, as we will use it to test our API endpoints. And also make sure you have Node.js (>= 8.9.0) installed on your machine. Lastly, you can find a link to the final project GitHub repo here.

您将需要安装Postman ,因为我们将使用它来测试我们的API端点。 还要确保在计算机上安装了Node.js (> = 8.9.0)。 最后,您可以在此处找到指向最终项目GitHub repo的链接

建筑模块 (Building blocks)

Before we get started, we'll discuss some abstractions/concepts that will help you know where to put specific business logic from project to project.

在开始之前,我们将讨论一些抽象/概念,这些抽象/概念将帮助您了解将特定业务逻辑从一个项目放置到另一个项目的位置。

Nest is very similar to Angular – so if you are familiar with Angular concepts it will be straightforward to you.

Nest与Angular非常相似-因此,如果您熟悉Angular概念,它对您将很简单。

Still, I'll assume that you have no knowledge of these concepts and will explain them to you.

我仍然假设您不了解这些概念,并向您解释它们。

控制者 (Controller)

The controller is responsible for listening to requests that come into your application. It then formulates the responses that go out.

控制器负责侦听进入您的应用程序的请求。 然后,它制定出响应。

For instance, when you make an API call to

/posts
the controller will handle this request and return the appropriate response you specified.

例如,当您对

/posts
进行API调用时,控制器将处理此请求并返回您指定的适当响应。

This is just a basic Class declaration in TypeScript/JavaScript with a

@Controller
decorator. All Nest Controllers must have the decorator which is required to define a basic Controller in Nest.

这只是TypeScript / JavaScript中带有

@Controller
装饰器的基本Class声明。 所有Nest Controller必须具有装饰器,该装饰器是在Nest中定义基本Controller所必需的

Nest allows you to specify your routes as a parameter in the

@Controller()
decorator. This helps you group a set of related routes and minimises code repetition. Any request to
/posts
will be handled by this controller.

Nest允许您将路由指定为

@Controller()
装饰器中的参数。 这可以帮助您对一组相关的路由进行分组,并最大程度地减少代码重复。 对
/posts
任何请求都将由此控制器处理。

At the class methods level, you can specify which method should handle the

GET
,
POST,
DELETE
,
PUT/PATCH
HTTP requests.

在类方法级别,您可以指定哪个方法应处理

GET
POST,
DELETE
PUT/PATCH
HTTP请求。

In our example, the

findAll()
method with the
@Get()
decorator handles all
GET
HTTP requests to get all blog posts. While the
findOne()
method with the
@Get(': id')
decorator will handle a
GET /posts/1
request.

在我们的示例中,带有

@Get()
装饰器的
findAll()
方法处理所有
GET
HTTP请求以获取所有博客文章。 而带有
@Get(': id')
装饰器的
findOne()
方法将处理
GET /posts/1
请求。

提供者 (Providers)

Providers were designed to abstract any form of complexity and logic to a separate class. A provider can be a service, a repository, a factory, or a helper.

提供程序旨在将任何形式的复杂性和逻辑抽象到一个单独的类中。 提供程序可以是服务,存储库,工厂或帮助程序。

Providers are plain TypeScript/JavaScript classes with an

@Injectable()
decorator preceding their class declaration. Just like services in Angular, you can create and inject providers into other controllers or other providers as well.

提供程序是普通的TypeScript / JavaScript类,在其类声明之前带有

@Injectable()
装饰器。 就像Angular中的服务一样,您可以创建提供程序并将其注入到其他控制器或其他提供程序中。

A good use case for a service provider is to create a PostService that abstracts all communication to the database into this service. This keeps the

PostsController
nice and clean.

服务提供者的一个好用例是创建一个PostService,它将与数据库的所有通信抽象到该服务中。 这样可以使

PostsController
保持整洁。

This is just a plain TypeScript class with a

@Injectable()
decorator (this is how Nest knows it is a provider).
Post
is just an interface for type checking.

这只是带有

@Injectable()
装饰器的普通TypeScript类(这就是Nest知道它是提供者的方式)。
Post
只是用于类型检查的界面。

Here, we are using a simple data structure to store the data. In a real project, this service will be communicating with the database.

在这里,我们使用一个简单的数据结构来存储数据。 在实际的项目中,此服务将与数据库进行通信。

模组 (Modules)

A module is a JavaScript/TypeScript class with the

@Module()
decorator. The
@Module()
decorator provides metadata that Nest uses to organise the application structure.

模块是带有

@Module()
装饰器JavaScript / TypeScript类。
@Module()
装饰器提供Nest用来组织应用程序结构的元数据。

Modules are a very important aspect of Nest and each application must provide at least one Module: the application root module. The root module is the starting point Nest uses to build the application graph.

模块是Nest的一个非常重要的方面,每个应用程序必须至少提供一个模块:应用程序根模块。 根模块是Nest用来构建应用程序图的起点。

The post service, controller, post entity, and everything related to post should be grouped into a module (PostsModule). Below, we have defined the PostsModule.

邮政服务,控制器,邮政实体以及与邮政相关的所有内容都应归为一个模块(PostsModule)。 在下面,我们定义了PostsModule。

Then, we import this module into the root module

AppModule
:

然后,我们将此模块导入到根模块

AppModule

The

@Module()
decorator takes a single object whose properties describes the module:

@Module()
装饰器接受一个对象,该对象的属性描述模块:

  • imports:
    Other modules that are needed by this module.

    imports:
    此模块所需的其他模块。

  • exports:
    By default, modules encapsulate providers. It’s impossible to inject providers that are neither directly part of the current module nor are exported from the imported modules. To make the current module providers available to other modules in the application, they have to be exported here. We can also export modules we imported too.

    exports:
    默认情况下,模块封装提供程序。 注入既不是直接在当​​前模块中也不是从导入模块中导出的提供程序是不可能的。 为了使当前的模块提供程序可用于应用程序中的其他模块,必须将其导出到此处。 我们也可以导出导入的模块。

  • controllers:
    The set of controllers defined in this module which have to be instantiated.

    controllers:
    必须实例化在此模块中定义的一组控制器。

  • providers:
    in simple terms, all our services and providers within the module will be here.

    providers:
    简单来说,我们在模块中的所有服务和提供商都将在此处。

拦截器 (Interceptor)

An interceptor is a specialised set of middleware that lets you peek into the request that goes into the application. You can peek into the request either before it reaches the controller or after the controller is done with the request before it gets to the client-side as a response. You can manipulate the data on their way out in the interceptor.

拦截器是一组专门的中间件,可让您窥视进入应用程序的请求。 您可以在请求到达控制器之前或在控制器完成请求之后,再将请求作为响应送达客户端之前,先查看一下请求。 您可以在拦截器中处理数据的方式。

守卫 (Guard)

Guard is also a special kind of middleware that is used mainly for authentication and authorisation. It only returns a boolean value of true or false.

Guard也是一种特殊的中间件,主要用于身份验证和授权。 它仅返回布尔值true或false。

Guards have a single responsibility: they determine whether a given request will be handled by the route handler or not, depending on certain conditions (like permissions, roles, ACLs, etc.) present at run-time.

守卫有一个责任 :他们根据运行时出现的某些条件(例如权限,角色,ACL等)确定是否由路由处理程序处理给定的请求。

A Guard should also implement the

CanActivate
interface.

Guard还应该实现

CanActivate
接口。

管 (Pipe)

Pipes are also a special kind of middleware that sits between the client and the controller. They are mostly used for validation and transforming data before they get to the controller.

管道还是位于客户端和控制器之间的一种特殊的中间件。 它们主要用于在到达控制器之前进行验证和转换数据。

DTO(数据传输对象) (DTO (Data Transfer Object))

Data transfer object is an object that defines how data will be sent over the network. They are also used for validation and type checking.

数据传输对象是定义如何通过网络发送数据的对象。 它们还用于验证和类型检查。

介面 (Interfaces)

TypeScript interfaces are only used for type-checking and they do not compile down into JavaScript code.

TypeScript接口仅用于类型检查,不会编译为JavaScript代码。

安装 (Installation)

Install the NestJs CLI. Nest comes with an awesome CLI that makes it easy to scaffold a Nest application with ease. In your terminal or cmd run:

安装NestJs CLI。 Nest具有出色的CLI,可轻松轻松地构建Nest应用程序。 在终端或cmd中运行:

npm i -g @nestjs/cli

npm i -g @nestjs/cli

Now you have Nest installed globally in your machine.

现在,您已经在计算机中全局安装了Nest。

On your terminal or cmd, cd into the directory where you want to create your application, and run following commands:

在终端或cmd上,cd进入要创建应用程序的目录,然后运行以下命令:

nest new nest-blog-api
cd nest-blog-api
npm run start:dev

nest new nest-blog-api
cd nest-blog-api
npm run start:dev

Navigate to

http://localhost:3000
on any of your browsers. You should see
Hello World
. Bravo! you have created your first Nest app. Let’s continue.

在任何浏览器上导航至

http://localhost:3000
。 您应该看到
Hello World
。 太棒了! 您已经创建了第一个Nest应用。 让我们继续。

NOTE: As of this writing, if running

npm run start:dev
throws an error, change your
typescript:3.4.2
in your
package.json file
to
typescript:3.7.2
and then delete the
node_modules and package-lock.json
re-run
npm i
.

注:写这篇文章的作为,I F运行

npm run start:dev
抛出一个错误,改变你的
typescript:3.4.2
在你
package.json file
,以
typescript:3.7.2
,然后删除
node_modules and package-lock.json
重-运行
npm i

Your folder structure should look like this:

您的文件夹结构应如下所示:

序列化和数据库设置 (Sequelize and Database Setup)

We’ll start by installing the following dependencies. Make sure your terminal or cmd is currently on your project root directory. Then run the following commands:

我们将从安装以下依赖关系开始。 确保您的终端或cmd当前在项目根目录中。 然后运行以下命令:

npm install -g sequelizenpm install --save sequelize sequelize-typescript pg-hstore pgnpm install --save-dev @types/sequelizenpm install dotenv --save

npm install -g sequelizenpm install --save sequelize sequelize-typescript pg-hstore pgnpm install --save-dev @types/sequelizenpm install dotenv --save

Now, create a database module. Run

nest generate module /core/database
.

现在,创建一个数据库模块。 运行

nest generate module /core/database

数据库接口 (Database Interface)

Inside the database folder, create an

interfaces
folder, then create a
dbConfig.interface.ts
file inside it. This is for the database configuration interface.

在数据库文件夹中,创建一个

interfaces
文件夹,然后在其中创建一个
dbConfig.interface.ts
文件。 这用于数据库配置界面。

Each of the database environments should optionally have the following properties. Copy and paste the following code:

每个数据库环境都应可选地具有以下属性。 复制并粘贴以下代码:

数据库配置 (Database Configuration)

Now, let’s create a database environment configuration. Inside the database folder, create a

database.config.ts
file. Copy and paste the below code:

现在,让我们创建一个数据库环境配置。 在数据库文件夹内,创建一个

database.config.ts
文件。 复制并粘贴以下代码:

The environment will determine which configuration should be used.

环境将确定应使用哪种配置。

.env文件 (.env file)

On our project root folder, create

.env
and
.env.sample
files. Copy and paste the following code into both files:

在我们的项目根文件夹中,创建

.env
.env.sample
文件。 将以下代码复制并粘贴到两个文件中:

Fill the values with the correct information – only on the

.env
file – and make sure it’s added to the
.gitignore
file to avoid pushing it online. The
.env.sample
is for those who want to download your project and use it so you can push it online.

用正确的信息填充值-仅在

.env
文件上-并确保将其添加到
.gitignore
文件中,以避免将其联机。
.env.sample
适用于想要下载您的项目并使用它的人,以便您可以在线推送它。

HINTS: Your username, password, and database name should be what you use to set up your Postgres. Create a Postgres database with your database name.

提示: 您的用户名,密码和数据库名称应该是用来设置Postgres的名称。 使用您的数据库名称创建一个Postgres数据库。

Nest provides a

@nestjs/config
package out-of-the-box to help load our
.env
file. To use it, we first install the required dependency.

Nest提供了一个

@nestjs/config
@nestjs/config
软件包来帮助加载我们的
.env
文件。 要使用它,我们首先安装所需的依赖项。

Run

npm i --save @nestjs/config
.

运行

npm i --save @nestjs/config

Import the

@nestjs/config
into your app root module:

@nestjs/config
导入您的应用程序根模块:

Setting the

ConfigModule.forRoot({ isGlobal: true })
to
isGlobal: true
will make the
.env
properties available throughout the application.

ConfigModule.forRoot({ isGlobal: true })
isGlobal: true
将使
.env
属性在整个应用程序中可用。

数据库提供者 (Database Provider)

Let’s create a database provider. Inside the database folder, create a file called

database.providers.ts
.

让我们创建一个数据库提供程序。 在数据库文件夹中,创建一个名为

database.providers.ts
的文件。

The core directory will contain all our core setups, configuration, shared modules, pipes, guards, and middlewares.

核心目录将包含我们所有的核心设置,配置,共享模块,管道,防护和中间件。

In the

database.providers.ts
file, copy and paste this code:

database.providers.ts
文件中,复制并粘贴以下代码:

Here, the application decides what environment we are currently running on and then chooses the environment configuration.

在这里,应用程序确定我们当前在哪个环境上运行,然后选择环境配置。

All our models will be added to the

sequelize.addModels([User, Post])
function. Currently, there are no models.Best practice: It is a good idea to keep all string values in a constant file and export it to avoid misspelling those values. You'll also have a single place to change things.

我们所有的模型都将添加到

sequelize.addModels([User, Post])
函数中。 当前没有模型。 最佳实践最好将所有字符串值保存在一个恒定文件中,然后将其导出以避免拼写错误。 您还将有一个地方来进行更改。

Inside the core folder, create a

constants
folder and inside it create an
index.ts
file. Paste the following code:

在核心文件夹中,创建一个

constants
文件夹,并在其中创建一个
index.ts
文件。 粘贴以下代码:

Let’s add the database provider to our database module. Copy and paste this code:

让我们将数据库提供程序添加到我们的数据库模块中。 复制并粘贴以下代码:

We exported the database provider

exports: [...databaseProviders]
to make it accessible to the rest of the application that needs it.

我们导出了数据库提供程序

exports: [...databaseProviders]
以使需要它的其他应用程序可以访问它。

Now, let’s import the database module into our app root module to make it available to all our services.

现在,让我们将数据库模块导入到我们的应用程序根模块中,以使其可用于所有服务。

设置全局端点前缀 (Setting a global endpoint prefix)

We might want all our API endpoints to start with

api/v1
for different versioning. We don't want to have to add this prefix to all our controllers. Fortunately, Nest provides a way to set a global prefix.

我们可能希望所有API端点都以

api/v1
开头以进行不同的版本控制。 我们不想将此前缀添加到所有控制器中。 幸运的是,Nest提供了一种设置全局前缀的方法。

In the

main.ts
file, add
app.setGlobalPrefix('api/v1');

main.ts
文件中,添加
app.setGlobalPrefix('api/v1');

用户模块 (User Module)

Let’s add a User module to handle all user-related operations and to keep tabs on who is creating what post.

让我们添加一个“用户”模块来处理所有与用户相关的操作,并密切关注谁在创建什么帖子。

Run

nest generate module /modules/users
.This will automatically add this module to our root module
AppModule
.

运行

nest generate module /modules/users
这将自动将该模块添加到我们的根模块
AppModule

生成用户服务 (Generate User Service)

Run

nest generate service /modules/users
.This will automatically add this service to the Users module.

运行

nest generate service /modules/users
。这将自动将该服务添加到Users模块。

设置用户数据库架构模型 (Set Up User Database Schema Model)

Inside

modules/users
, create a file called
user.entity.ts
then copy and paste this code:

modules/users
内部,创建一个名为
user.entity.ts
的文件,然后复制并粘贴以下代码:

Here, we are specifying what our User table will contain. The

@column() decorator
provides information about each column in the table. The User table will have
name
email
password
and
gender
as columns. We imported all the Sequelize decorators from
sequelize-typescript
. To read more about Sequelize and TypeScript, check this out.

在这里,我们指定用户表将包含的内容。

@column() decorator
提供有关表中各列的信息。 “用户”表将具有
name
email
password
gender
作为列。 我们从
sequelize-typescript
typescript导入了所有Sequelize装饰器。 要了解有关Sequelize和TypeScript的更多信息,请查看此内容

用户DTO (User DTO)

Let’s create our User DTO (Data Transfer Object) schema. Inside the users folder, create a

dto
folder. Then create a
user.dto.ts
file inside it. Paste the following code in:

让我们创建用户DTO( 数据传输对象 )架构。 在用户文件夹内,创建一个

dto
文件夹。 然后在其中创建一个
user.dto.ts
文件。 将以下代码粘贴到:

用户存储库提供者 (User Repository provider)

Now, create a User Repository provider. Inside the user's folder, create a

users.providers.ts
file. This provider is used to communicate with the database.

现在,创建一个用户存储库提供程序。 在用户文件夹内,创建一个

users.providers.ts
文件。 该提供程序用于与数据库进行通信。

Add this

export const USER_REPOSITORY = 'USER_REPOSITORY';
to the constants
index.ts
file.

添加此

export const USER_REPOSITORY = 'USER_REPOSITORY';
到常量
index.ts
文件。

Also, add the user provider to the User module. Notice, we added the UserService to our

exports
array. That is because we’ll need it outside of the User Module.

此外,将用户提供程序添加到用户模块。 注意,我们将UserService添加到了

exports
数组中。 那是因为我们在用户模块之外需要它。

Let’s encapsulate user operations inside the UsersService. Copy and paste the following code:

让我们将用户操作封装在UsersService中。 复制并粘贴以下代码:

Here, we injected the user repository to communicate with the DB.

在这里,我们注入了用户存储库以与数据库进行通信。

  • create(user: UserDto)
    This method creates a new user into the user table and returns the newly created user object.

    create(user: UserDto)
    此方法在用户表中创建一个新用户,并返回新创建的用户对象。

  • findOneByEmail(email: string)
    This method is used to look up a user from the user table by email and returns the user.

    findOneByEmail(email: string)
    此方法用于通过电子邮件从用户表中查找用户并返回该用户。

  • findOneById(id: number)
    This method is used to look up a user from the user table by the user Id and returns the user.

    findOneById(id: number)
    此方法用于通过用户ID从用户表中查找用户并返回该用户。

We will use these methods later.

我们稍后将使用这些方法。

Lastly, let’s add the User model to the

database.providers.ts
file
sequelize.addModels([User]);
.

最后,让我们将User模型添加到

database.providers.ts
文件
sequelize.addModels([User]);

验证模块 (Auth Module)

生成身份验证模块 (Generate Auth Module)

This module will handle user authentication (Login and Sign up).Run

nest generate module /modules/auth
.This will automatically add this module to our root module
AppModule

该模块将处理用户身份验证(登录和注册)。运行

nest generate module /modules/auth
。这将自动将该模块添加到我们的根模块
AppModule

生成身份验证服务 (Generate Auth Service)

Run

nest generate service /modules/auth
.This will automatically add this service to the Auth module.

运行

nest generate service /modules/auth
。这将自动将该服务添加到Auth模块中。

生成身份验证控制器 (Generate Auth Controller)

Run

nest g co /modules/auth
.This will automatically add this controller to the Auth module.Note:
g
is an alias for
generate
and
co
is for
controller
.

运行

nest g co /modules/auth
。这将自动将此控制器添加到Auth模块中。 注意:
g
generate
的别名
co
controller
的别名

We will be using Passport to handle our authentication. It is straightforward to integrate this library with a Nest application using the @nestjs/passport module.

我们将使用Passport来处理我们的身份验证。 使用@ nestjs / passport模块将该库与Nest应用程序集成起来很简单。

We will implement two auth strategies for this application:

我们将为此应用程序实施两种身份验证策略:

  • Local Passport Strategy: This strategy will be used for logging in users. It will verify if the email/username and password provided by the user is valid or not. If user credentials are valid, it will return a token and user object, if not, it will throw an exception.

    本地护照策略:此策略将用于登录用户。 它将验证用户提供的电子邮件/用户名和密码是否有效。 如果用户凭据有效,它将返回令牌和用户对象,否则将抛出异常。

  • JWT Passport Strategy: This strategy will be used to protect protected resources. Only authenticated users with a valid token will be able to access these resources or endpoints.

    JWT护照策略:此策略将用于保护受保护的资源。 只有具有有效令牌的经过身份验证的用户才能访问这些资源或端点。

本地护照策略 (Local Passport Strategy)

Run

npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
npm install bcrypt --save

运行

npm install --save @nestjs/passport passport passport-local
npm install --save-dev @types/passport-local
npm install bcrypt --save

Inside the auth folder create a

local.strategy.ts
file and add the following code:

在auth文件夹中,创建一个

local.strategy.ts
文件并添加以下代码:

Here, we are importing

Strategy, PassportStrategy and AuthService.
We extend the
PassportStrategy
to create the
LocalStrategy.
In our use case with passport-local, there are no configuration options, so our constructor simply calls
super()
without any options object.

在这里,我们将导入

Strategy, PassportStrategy and AuthService.
我们扩展
PassportStrategy
以创建
LocalStrategy.
在使用本地护照的用例中,没有配置选项,因此我们的构造函数仅调用
super()
而不使用任何选项对象。

We must implement the

validate()
method. For the local-strategy, Passport expects a
validate()
method with the following signature:
validate(username: string, password:string): any
.

我们必须实现

validate()
方法。 对于本地策略,Passport希望使用带有以下签名的
validate()
方法:
validate(username: string, password:string): any

Most of the validation work is done in our

AuthService
(with the help of our
UserService
), so this method is quite straightforward.

大多数验证工作都在

AuthService
完成(借助于
UserService
),因此此方法非常简单。

We call the

validateUser()
method in the
AuthService
(we are yet to write this method), which checks if the user exists and if the password is correct.
authService.validateUser()
returns null if not valid or the user object if valid.

我们在

AuthService
调用
validateUser()
方法(我们尚未编写此方法),该方法检查用户是否存在以及密码是否正确。
authService.validateUser()
如果无效则返回null,如果有效则返回用户对象。

If a user is found and the credentials are valid, the user is returned so Passport can complete its tasks (e.g., creating the

user
property on the
Request
object), and the request handling pipeline can continue. If it's not found, we throw an exception and let our exceptions layer handle it.

如果找到了用户并且凭据有效,则将返回用户,以便Passport可以完成其任务(例如,在

Request
对象上创建
user
属性),并且请求处理管道可以继续。 如果找不到,我们将抛出一个异常并让我们的异常层处理它。

Now, add the

PassportModule, UserModule
and
LocalStrategy
to our AuthModule.

现在,将

PassportModule, UserModule
LocalStrategy
添加到我们的AuthModule中。

验证服务 (AuthService)

Let’s implement the

validateUser()
method.

让我们实现

validateUser()
方法。

Here, we check if the user exists with the email provided. Then we check if the password in the DB matched what the User provided. If any of these checks fail, we return

null,
if not, we return the user object.

在这里,我们检查用户是否存在并提供了电子邮件。 然后,我们检查数据库中的密码是否与用户提供的密码匹配。 如果这些检查中的任何一个失败,我们将返回

null,
否则,我们将返回用户对象。

comparePassword(enteredPassword, dbPassword):
This private method compares the user-entered password and user DB password and returns a boolean. If the password matches it returns true. If not, it returns false.

comparePassword(enteredPassword, dbPassword):
此私有方法比较用户输入的密码和用户DB的密码,并返回一个布尔值。 如果密码匹配,则返回true。 如果不是,则返回false。

智威汤逊护照战略 (JWT Passport Strategy)

Run

npm install @nestjs/jwt passport-jwt
npm install @types/passport-jwt --save-dev

运行

npm install @nestjs/jwt passport-jwt
npm install @types/passport-jwt --save-dev

Inside the auth folder create a

jwt.strategy.ts
file and add the following code:

在auth文件夹中,创建一个

jwt.strategy.ts
文件并添加以下代码:

Here, we are extending

PassportStrategy.
Inside the
super()
we added some options object. In our case, these options are:

在这里,我们正在扩展

PassportStrategy.
super()
内部,我们添加了一些options对象。 在我们的情况下,这些选项是:

  • jwtFromRequest:
    supplies the method by which the JWT will be extracted from the
    Request
    . We will use the standard approach of supplying a bearer token in the Authorization header of our API requests.

    jwtFromRequest:
    提供从
    Request
    提取JWT的方法。 我们将使用在API请求的Authorization标头中提供承载令牌的标准方法。

  • ignoreExpiration
    : just to be explicit, we choose the default
    false
    setting, which delegates the responsibility of ensuring that a JWT has not expired to the Passport module. This means that if our route is supplied with an expired JWT, the request will be denied and a
    401 Unauthorized
    response sent. Passport conveniently handles this automatically for us.

    ignoreExpiration
    :为了明确
    ignoreExpiration
    ,我们选择默认的
    false
    设置,该设置将确保JWT尚未过期的责任委托给Passport模块。 这意味着,如果我们的路由提供有过期的JWT,则该请求将被拒绝,并发送
    401 Unauthorized
    响应。 Passport会为我们自动方便地处理此问题。

  • secretOrKey
    : This is our secret key for the token. This will use the secret key in our
    .env
    file.

    secretOrKey
    secretOrKey
    的秘密密钥。 这将使用我们的
    .env
    文件中的密钥。

  • The

    validate(payload: any)
    For the jwt-strategy, Passport first verifies the JWT’s signature and decodes the JSON. It then invokes our
    validate()
    method passing the decoded JSON as its single parameter. Based on the way JWT signing works, we're guaranteed that we're receiving a valid token that we have previously signed and issued to a valid user. We confirm if the user exists with the user payload id. If the user exists, we return the user object, and Passport will attach it as a property on the
    Request
    object. If the user doesn’t exist, we throw an Exception.

    validate(payload: any)
    对于jwt-strategy,Passport首先验证JWT的签名并解码JSON。 然后,它调用我们的
    validate()
    方法,将解码后的JSON作为其单个参数传递。 基于JWT签名的工作方式,我们可以确保我们收到的是先前已签名并颁发给有效用户的有效令牌。 我们用用户有效负载ID确认用户是否存在。 如果用户存在,我们将返回用户对象,Passport会将其作为属性附加到
    Request
    对象上。 如果用户不存在,则抛出异常。

Now, add the

JwtStrategy
and
JwtModule
to the
AuthModule.
:

现在,将

JwtStrategy
JwtModule
添加到
AuthModule.

We configure the

JwtModule
using
register()
, passing in a configuration object.

我们使用

register()
配置
JwtModule
,并传入配置对象。

Let’s add other methods we will need to login and create a new user in

AuthService
:

让我们添加其他需要登录并在

AuthService
创建新用户的
AuthService

Import and inject JwtService.

导入并注入JwtService。

  • login(user):
    This method is used to login the user. This takes the user information, generates a token with it, and then returns the token and user object.

    login(user):
    此方法用于登录用户。 这将获取用户信息,并使用它生成一个令牌,然后返回该令牌和用户对象。

  • create(user):
    This method is used to create a new user. This takes the user information, hash the user password, saves the user to the DB, removes the password from the newly returned user, generates a token with the user object, and then returns the token and user object.

    create(user):
    此方法用于创建新用户。 这将获取用户信息,对用户密码进行哈希处理,将用户保存到DB,从新返回的用户中删除密码,使用用户对象生成令牌,然后返回令牌和用户对象。

  • generateToken(user):
    This private method generates a token and then returns it.

    generateToken(user):
    此私有方法生成一个令牌,然后将其返回。

  • hashPassword(password):
    This private method hashes the user password and returns the hashed password.

    hashPassword(password):
    此私有方法对用户密码进行哈希处理并返回哈希的密码。

We will be using all these functions later.

稍后我们将使用所有这些功能。

AuthController (AuthController)

Now, let’s create our

signup
and
login
methods:

现在,让我们创建我们的

signup
login
方法:

When we hit this endpoint POST

api/v1/auth/login
will call
@UseGuards(AuthGuard('local'))
. This will take the user email/username and password, then run the validate method on our local strategy class. The
login(@Request() req)
will generate a JWT token and return it.

当我们点击该端点时,POST

api/v1/auth/login
将调用
@UseGuards(AuthGuard('local'))
。 这将使用用户的电子邮件/用户名和密码,然后在我们的本地策略类上运行validate方法。
login(@Request() req)
将生成一个JWT令牌并将其返回。

The POST

api/v1/auth/signup
endpoint will call the
this.authService.create(user)
method, create the user, and return a JWT token.

POST

api/v1/auth/signup
端点将调用
this .authService.create(user)
方法,创建用户,并返回JWT令牌。

让我们尝试一下... (Let’s try it out…)

Open your Postman application and make sure it's running. Send a POST request to

http://localhost:3000/api/v1/auth/signup
and input your body data to create a user. You should get a token and the user object returned.

打开您的Postman应用程序,并确保它正在运行。 将POST请求发送到

http://localhost:3000/api/v1/auth/signup
并输入您的正文数据以创建用户。 您应该获得一个令牌,并返回用户对象。

Now that we have a user, let’s log the user in. Send a POST request to

http://localhost:3000/api/v1/auth/login
and input just your username and password. You should get a token and the user object returned.

现在我们有了一个用户,让我们登录该用户。将POST请求发送到

http://localhost:3000/api/v1/auth/login
并仅输入您的用户名和密码。 您应该获得一个令牌,并返回用户对象。

验证方式 (Validation)

Notice how we are not validating any of the user's input. Now, let’s add validation to our application.

注意,我们如何不验证用户的任何输入。 现在,让我们向应用程序添加验证。

Run

npm i class-validator class-transformer --save
.

运行

npm i class-validator class-transformer --save

Inside the core folder, create a pipes folder and then create

validate.pipe.ts
file. Copy and paste the following code:

在核心文件夹中,创建一个管道文件夹,然后创建

validate.pipe.ts
文件。 复制并粘贴以下代码:

Let’s auto-validate all our endpoints with

dto
by binding
ValidateInputPipe
at the application level. Inside the
main.ts
file, add this:

通过在应用程序级别绑定

ValidateInputPipe
,让我们使用
dto
自动验证所有端点。 在
main.ts
文件中,添加以下内容:

Now, let’s update our users

dto
file:

现在,让我们更新用户的

dto
文件:

Here, we are importing these decorators from

class-validator.

在这里,我们从

class-validator.
导入这些装饰
class-validator.

  • @IsNotEmpty():
    ensures the field isn’t empty.

    @IsNotEmpty():
    确保该字段不为空。

  • @IsEmail():
    checks if the email entered is a valid email address.

    @IsEmail():
    检查输入的电子邮件是否为有效的电子邮件地址。

  • @MinLength(6):
    ensures the password character is not less than six.

    @MinLength(6):
    确保密码字符不少于六个。

  • @IsEnum:
    ensures only the specified value is allowed (in this case, male and female).

    @IsEnum:
    确保仅允许指定的值(在这种情况下,是男性和女性)。

class-validator has tons of validation decorators – check them out.

class-validator有大量的验证装饰器–请检查一下。

Let’s try our validation out…

让我们尝试一下验证…

Without passing any value, I got the following validation error. Our validation is working now. This validation is automatic to all endpoints with a dto (data transfer object).

没有传递任何值,我得到了以下验证错误。 我们的验证现已开始。 对于具有dto(数据传输对象)的所有端点,此验证是自动的。

唯一用户帐户 (Unique User account)

Let’s add a guard that prevents users from signing up with the same email twice since email is unique at the schema level.

让我们添加一个保护措施,由于电子邮件在架构级别是唯一的,因此可以防止用户两次使用同一电子邮件进行注册。

Inside the core folder, create a guards folder, then create a

doesUserExist.guard.ts
file. Copy and paste the following code:

在核心文件夹中,创建一个guards文件夹,然后创建一个

doesUserExist.guard.ts
文件。 复制并粘贴以下代码:

Now, let’s add this guard to our signup method in

AuthController.
:

现在,让我们将此防护添加到

AuthController.
的注册方法中
AuthController.

Let’s try to create a user with an email that already exists in our database:

让我们尝试用数据库中已经存在的电子邮件创建一个用户:

帖子模块 (Post Module)

Run

nest g module /modules/posts
.This will automatically add this module to our root module
AppModule
.

运行

nest g module /modules/posts
。这将自动将此模块添加到我们的根模块
AppModule

产生邮政服务 (Generate Post Service)

Run

nest g service /modules/posts
.This will automatically add this service to the Post module.

运行

nest g service /modules/posts
。这将自动将该服务添加到Post模块中。

生成后置控制器 (Generate Post Controller)

Run

nest g co /modules/posts
,This will automatically add this controller to the Post module.

运行

nest g co /modules/posts
,这将自动将此控制器添加到Post模块。

邮政实体 (Post Entity)

Create a

post.entity.ts
file inside the posts folder. Copy and paste the following code:

在posts文件夹中创建一个

post.entity.ts
文件。 复制并粘贴以下代码:

The only new thing here is the

@ForeignKey(() => User)
specifying that the userId column is the id of the User table and
@BelongsTo(() => User)
specifying the relationship between the Post table and User table.

这里唯一的新事物是

@ForeignKey(() => User)
指定userId列是User表的ID,而
@BelongsTo(() => User)
指定Post表和User表之间的关系。

发布DTO(数据传输对象) (Post DTO (Data Transfer Object))

Inside the posts folder, create a

dto
folder then create a
post.dto.ts
file inside it. Copy and paste the following code:

在posts文件夹中,创建一个

dto
文件夹,然后在其中创建一个
post.dto.ts
文件。 复制并粘贴以下代码:

Here, our post body object must have a title, and body and title length must not be less than 4.

在这里,我们的帖子正文对象必须具有标题,并且正文和标题长度不得小于4。

邮政提供者 (Post Provider)

Create a

posts.providers.ts
file inside the posts folder. Copy and paste the following code:

在posts文件夹中创建一个

posts.providers.ts
文件。 复制并粘贴以下代码:

Add this

export const POST_REPOSITORY = 'POST_REPOSITORY';
to the constants
index.ts
file.

添加此

export const POST_REPOSITORY = 'POST_REPOSITORY';
到常量
index.ts
文件。

Add our Post provider to our Post Module file:

将我们的Post提供程序添加到我们的Post Module文件中:

Now, add our Post entity to our database provider. Import the Post entity inside the

database.providers.ts
file, add the Post to this method:

现在,将我们的Post实体添加到我们的数据库提供程序中。 将Post实体导入

database.providers.ts
文件中,然后将Post添加到此方法中:

sequelize.addModels([User, Post]);

sequelize.addModels([User, Post]);

邮政服务方式 (Post Service Methods)

Copy and paste the following inside the Post service file:

将以下内容复制并粘贴到Post服务文件中:

Here, we are injecting our Post repository to communicate with our database.

在这里,我们正在注入Post存储库以与我们的数据库进行通信。

  • create(post: PostDto, userId):
    This accepts post object and the id of the user creating the post. It adds the post to the database and returns the newly created Post. The
    PostDto
    is for validation.

    create(post: PostDto, userId):
    接受发布对象和创建该帖子的用户的ID。 它将帖子添加到数据库并返回新创建的帖子。
    PostDto
    用于验证。

  • findAll():
    This gets all the posts from the database and also includes/eager load the user who created it while excluding the user password.

    findAll():
    这将从数据库中获取所有帖子,还包括/渴望加载创建它的用户,同时排除用户密码。

  • findOne(id):
    This finds and returns the post with the id. It also includes/eager load the user who created it while excluding the user password.

    findOne(id):
    这将查找并返回带有id的帖子。 它还包括/渴望加载创建它的用户,而排除用户密码。

  • delete(id, userId):
    This deletes the post from the database with the id and userId. Only the user who created the post can delete it. This returns the number of rows that were affected.

    delete(id, userId):
    这将从数据库中删除具有id和userId的帖子。 只有创建该帖子的用户才能删除它。 这将返回受影响的行数。

  • update(id, data, userId):
    This updates an existing post where
    id
    is the id of the post,
    data
    is the data to update,
    userId
    is the id of the original creator. This returns the number of rows that were updated and the newly updated object.

    update(id, data, userId):
    这将更新现有帖子,其中
    id
    是帖子的ID,
    data
    是要更新的数据,
    userId
    是原始创建者的ID。 这将返回已更新的行数和新近更新的对象。

后控制器方法 (Post Controller Methods)

Copy and paste the following inside the Post controller file:

将以下内容复制并粘贴到Post控制器文件中:

Most of the CRUD operation functionality is done in our

PostService.

大多数CRUD操作功能都是在我们的

PostService.
完成的
PostService.

  • findAll():
    This handles
    GET
    request to
    api/v1/posts
    endpoint. It returns all the posts in our database.

    findAll():
    这处理对
    api/v1/posts
    端点的
    GET
    请求。 它返回我们数据库中的所有帖子。

  • findOne(@Param(‘id’) id: number):
    This handles
    GET
    request to
    api/v1/posts/1
    endpoint to get a single post, where 1 is the id of the post. This throws a 404 error if it doesn’t find the post and returns the post object if it does find the post.

    findOne(@Param('id') id: number):
    这处理对
    api/v1/posts/1
    端点的
    GET
    请求以获取单个帖子,其中1是该帖子的ID。 如果找不到帖子,则抛出404错误,如果找到帖子,则返回post对象。

  • create(@Body() post: PostDto, @Request() req):
    This handles
    POST
    request to
    api/v1/posts
    endpoint to create a new post.

    create(@Body() post: PostDto, @Request() req):
    这处理对
    api/v1/posts
    端点的
    POST
    请求以创建新帖子。

  • @UseGuards(AuthGuard(‘jwt’))
    is used to protect the route (remember our JWT strategy). Only logged in users can create a post.

    @UseGuards(AuthGuard('jwt'))
    用于保护路由(请记住我们的JWT策略)。 只有登录的用户才能创建帖子。

  • update(@Param(‘id’) id: number, @Body() post: PostDto, @Request() req):
    This handles the
    PUT
    request to
    api/v1/posts
    endpoint to update an existing post. It is also a protected route. If the
    numberOfAffectedRows
    is zero that means no post with the params id was found.

    update(@Param('id') id: number, @Body() post: PostDto, @Request() req):
    这处理对
    api/v1/posts
    端点的
    PUT
    请求以更新现有帖子。 这也是一条受保护的路线。 如果
    numberOfAffectedRows
    为零,则表示未找到带有参数id的帖子。

  • remove(@Param(‘id’) id: number, @Request() req):
    This handles the
    DELETE
    request to delete an existing post.

    remove(@Param('id') id: number, @Request() req):
    Request
    remove(@Param('id') id: number, @Request() req):
    这处理
    DELETE
    请求以删除现有帖子。

让我们尝试一下CRUD操作... (Let’s try our CRUD operation out…)

建立讯息 (Create a Post)

Log in and add your token since creating a post route is a protected route.

登录并添加令牌,因为创建发布路由是受保护的路由。

阅读单个帖子 (Read a single Post)

This route isn’t protected, so it can be accessed without the token.

此路由不受保护,因此无需令牌即可访问。


阅读所有帖子 (
Reading all Posts)

This route isn’t protected, so it can be accessed without the token too.

此路由不受保护,因此也可以在没有令牌的情况下进行访问。

更新单个帖子 (Updating a Single Post)

This route is protected, so we need a token and only the creator can update it.

此路由受保护,因此我们需要一个令牌,只有创建者才能更新它。

删除帖子 (Deleting a Post)

This route is protected, so we need a token and only the creator can delete it.

此路由受保护,因此我们需要一个令牌,只有创建者才能删除它。

结论 (Conclusion)

Nest.js gives you a more structured way of building your server-side applications with Node.

Nest.js为您提供了一种使用Node来构建服务器端应用程序的更结构化的方法。

For more information, check out the official NestJS website here.

有关更多信息,请在此处访问NestJS官方网站

Finally, I hope this article was useful to you! The link to the final project GitHub repo is here.

最后,希望本文对您有所帮助! 最终项目GitHub存储库链接在此处

You can connect with me on LinkedIn and Twitter.

您可以在LinkedInTwitter上与我联系。

翻译自: https://www.freecodecamp.org/news/build-web-apis-with-nestjs-beginners-guide/

node-postgres

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐