$ nest new superb-api

它將提示一個問題來選擇包管理器(npm、yarn 或 pnpm)。選擇您想要的任何一個。

項目初始化完成后,轉到項目文件夾,打開代碼編輯器并運行它。

$ cd superb-api
$ npm run start

首次應用程序運行

默認情況下,Nest JS 使用 3000 作為默認端口。

世界您好

如果要更改端口號,只需打開并更新端口號 .src/main.tsawait app.listen(ANY_AVAILABLE_PORT_NUMBER);

Nest JS 概述

現在,讓我們快速了解一下項目文件夾結構。

我們的大部分代碼都將寫入該文件夾。讓我們看一下文件夾。共有 5 個文件:srcsrc

控制器

控制器負責處理傳入請求和返回響應。根據 Nest JS 規則,控制器將在帶有 decorator 的類中編寫。我們還可以在 controller 裝飾器中添加一個字符串作為前綴,那么 URL 將是 .在控制器類中,將有具有 HTTP 方法裝飾器、、 的函數。與控制器類似,HTTP 方法裝飾器也可以接收字符串,例如 ,然后端點 URL 將為 .@Controller()@Controller('some-prefix')domain.com/some-prefix@GET()@POST()@PUT()@PATCH()@DELETE()@GET('test')domain.com/some-prefix/test

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

// Import the AppService class, which is defined in the app.service.ts file.
import { AppService } from './app.service';

// Controller decorator, which is used to define a basic route.
@Controller()
export class AppController {

// Get decorator, which is a method decorator that defines a basic GET route.
@Get()
getHello(): string {
// controller process goes here...
}
}

服務/提供商

服務或提供商負責處理主要流程。我們將在此處編寫所有端點邏輯。根據 Nest JS 規則,provider 將在帶有 decorator 的類中編寫。@Injectable()

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

在上面的代碼中,我們有負責返回字符串 ‘Hello World!’ 的方法。然后我們可以將這個 provider 注入到控制器中getHello

import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
// inject the AppService into the AppController
constructor(private readonly appService: AppService) {}

@Get('test')
getHello(): string {
// call the AppService's getHello method
return this.appService.getHello();
}
}

模塊

在 NestJS 中,模塊是一個帶有裝飾器注解的類。它用于將代碼組織成內聚的功能塊。模塊有幾個關鍵職責,例如定義提供者(服務、工廠、倉庫等)、控制器和其他相關內容。它們還允許定義導入和導出,用于管理應用程序不同部分之間的依賴關系。@Module()

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

讓我們構建我們出色的 API

Prisma 安裝

我們將使用 Prisma 作為數據庫 ORM。Prisma 是一個用于處理數據庫的出色庫。它支持 PostgreSQL、MySQL、SQL Server、SQLite、MongoDB 和 CockroachDB。

首先,讓我們在我們的項目中安裝 Prisma 包

$ npm install prisma --save-dev

安裝 prisma 后,運行以下命令

$ npx prisma init

它將在文件夾內創建文件schema.prismaprisma

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

和根文件夾中的文件.env

DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

確保在文件中設置正確的。DATABASE_URL.env

接下來,打開 和 讓我們定義模型架構schema.prisma

model User {
id Int @id @default(autoincrement())
email String @unique
password String
name String?
posts Post[]
}

model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean? @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}

創建 Schema 后,通過執行

npx prisma migrate dev --name init

并檢查數據庫。它將創建以下表:

新表

安裝 prisma 客戶端

npm install @prisma/client

請注意,在安裝過程中,Prisma 會自動為您調用該命令。將來,您需要在每次更改 Prisma 模型后運行此命令,以更新生成的 Prisma Client。prisma generate

該命令讀取您的 Prisma 架構并更新其中生成的 Prisma 客戶端庫prisma generatenode_modules/@prisma/client

接下來,讓我們創建一個名為 within directory 的新文件:prisma.service.tssrc/core/services/

// src/core/services/prisma.service.ts

import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}

對于 Prisma 的最后一個配置,讓我們注冊為全局模塊,以便它可以在任何其他模塊上使用。創建一個名為 inside 的新文件PrismaServicecore.module.tssrc/core/

// src/core/core.module.ts

import { Global, Module } from '@nestjs/common';
import { PrismaService } from './services/prisma.service';

@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class CoreModule {}

然后通過添加 :app.module.tsCoreModule

// src/app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './modules/users/users.module';
import { CoreModule } from './core/core.module';

@Module({
imports: [UsersModule, CoreModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

現在,我們已準備好與數據庫進行交互:)


驗證配置

Nest JS 可以很好地進行請求體和查詢驗證。
首先,安裝軟件包:

$ npm i --save class-validator class-transformer
$ npm install @nestjs/mapped-types

并更新我們的main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create(AppModule);

// Global pipe that will be applied to all routes
// This will validate the request body against the DTO
app.useGlobalPipes(new ValidationPipe());

await app.listen(3000);
}
bootstrap();

我們添加了以使其全局應用。app.useGlobalPipes(new ValidationPipe());main.ts


創建用戶終端節點

讓我們繼續創建終端節點。
在該文件夾中,創建一個名為 的新文件夾。srcmodules/users

在 中,我們將創建處理與用戶模型相關的任何邏輯的端點,例如;Create User、Update User 和 Delete User。modules/users

在深入研究代碼之前,讓我們安裝一些包:

bcrypt
我們將在創建用戶時加密用戶的密碼。

$ npm i bcrypt
$ npm install --save @types/bcrypt

現在讓我們創建一個新文件夾并創建這些文件:src/modules/users/dtos

import { IsEmail, IsNotEmpty } from 'class-validator';

export class CreateUserDto {
@IsNotEmpty()
@IsEmail()
email: string;

@IsNotEmpty()
password: string;

@IsNotEmpty()
name: string;
}

import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUsertDto extends PartialType(CreateUserDto) {}
import { IsEmail, IsNotEmpty } from 'class-validator';

export class LoginUserDto {
@IsNotEmpty()
@IsEmail()
email: string;

@IsNotEmpty()
password: string;
}

讓我們從創建一個名為users.controller.ts

import {
  Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
} from '@nestjs/common';
import { CreateUserDto } from './dtos/create-user.dto';
import { UpdateUsertDto } from './dtos/update-user.dto';
import { LoginUserDto } from './dtos/login-user.dto';

@Controller('users')
export class UsersController {
@Post('register')
registerUser(@Body() createUserDto: CreateUserDto): string {
console.log(createUserDto);
return 'Post User!';
}

@Post('login')
loginUser(@Body() loginUserDto: LoginUserDto): string {
console.log(loginUserDto);
return 'Login User!';
}

@Get('me')
me(): string {
return 'Get my Profile!';
}

@Patch(':id')
updateUser(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUsertDto,
): string {
console.log(updateUserDto);
return Update User </span><span class="p" style="color: var(--syntax-text-color)">${</span><span class="nx" style="color: var(--syntax-name-color)">id</span><span class="p" style="color: var(--syntax-text-color)">}</span><span class="s2" style="color: var(--syntax-string-color)">!; } @Delete(':id') deleteUser(@Param('id', ParseIntPipe) id: number): string { return Delete User </span><span class="p" style="color: var(--syntax-text-color)">${</span><span class="nx" style="color: var(--syntax-name-color)">id</span><span class="p" style="color: var(--syntax-text-color)">}</span><span class="s2" style="color: var(--syntax-string-color)">!; } }

users.controller.ts有 5 個端點:

上述每個端點都已根據 DTO 實施了驗證,例如:

現在讓我們跳轉到 user 服務。在該服務中,我們將創建函數以通過 Prisma 與數據庫交互,它們將代表上述每個終端節點。

以下是樣板:users.service

import { ConflictException, HttpException, Injectable } from '@nestjs/common';
import { User } from '@prisma/client';
import { PrismaService } from 'src/core/services/prisma.service';
import { CreateUserDto } from './dtos/create-user.dto';
import { hash } from 'bcrypt';

@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}

// async registerUser

// async loginUser

// async updateUser

// async deleteUser
}

讓我們先從?register?函數開始:

async registerUser(createUserDto: CreateUserDto): Promise<User> {
  try {
// create new user using prisma client
const newUser = await this.prisma.user.create({
data: {
email: createUserDto.email,
password: await hash(createUserDto.password, 10), // hash user's password
name: createUserDto.name,
},
});

// remove password from response
delete newUser.password;

return newUser;
} catch (error) {
// check if email already registered and throw error
if (error.code === 'P2002') {
throw new ConflictException('Email already registered');
}

// throw error if any
throw new HttpException(error, 500);
}
}

registerUser將負責處理 register logic。我們可以看到,現在我們使用 prisma 與數據庫交互。我們還對密碼進行加密,以確保我們的 API 安全,并將其從響應 API 中刪除。this.prisma.user.createhash(createUserDto.password, 10)delete newUser.password

我們還使用 如果存在重復的電子郵件或任何其他錯誤 來處理錯誤。throw new ConflictExceptionthrow new HttpException

現在讓我們繼續登錄函數
登錄響應將返回一個具有名為 () 的單個屬性的對象,其中包含一些使用 生成的用戶數據 (),因此我們需要先安裝:access_tokeninterface LoginResponseinterface UserPaylodjwt@nestjs/jwt

$ npm install --save @nestjs/jwt

安裝后,通過添加@nestjs/jwtconstructorprivate jwtService: JwtService,

constructor(
  private prisma: PrismaService,
private jwtService: JwtService,
) {}

最后但并非最不重要的一點是,通過將 jwt 模塊添加為全局來更新我們的:app.module.ts

// src/app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './modules/users/users.module';
import { CoreModule } from './core/core.module';
import { JwtModule } from '@nestjs/jwt';

@Module({
imports: [
UsersModule,
CoreModule,
// add jwt module
JwtModule.register({
global: true,
secret: 'super_secret_key',
signOptions: { expiresIn: '12h' },
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

請注意,和 應該存儲在一個安全的地方,比如 file.

配置完成后,讓我們創建一個名為 :jwtsrc/modules/users/interfaces/users-login.interface.ts

export interface UserPayload {
  sub: number;
name: string;
email: string;
}

export interface LoginResponse {
access_token: string;
}

這是代碼:loginUser

async loginUser(loginUserDto: LoginUserDto): Promise<LoginResponse> {
    try {
// find user by email
const user = await this.prisma.user.findUnique({
where: { email: loginUserDto.email },
});

// check if user exists
if (!user) {
throw new NotFoundException('User not found');
}

// check if password is correct by comparing it with the hashed password in the database
if (!(await compare(loginUserDto.password, user.password))) {
throw new UnauthorizedException('Invalid credentials');
}

const payload: UserPayload = {
// create payload for JWT
sub: user.id, // sub is short for subject. It is the user id
email: user.email,
name: user.name,
};

return {
// return access token
access_token: await this.jwtService.signAsync(payload),
};
} catch (error) {
// throw error if any
throw new HttpException(error, 500);
}
}

到目前為止,我們已經創建了兩個函數,和 .現在讓我們繼續 和 。registerUserloginUserupdateUserdeleteUser

這是函數:updateUser

async updateUser(id: number, updateUserDto: UpdateUsertDto): Promise<User> {
  try {
// find user by id. If not found, throw error
await this.prisma.user.findUniqueOrThrow({
where: { id },
});

// update user using prisma client
const updatedUser = await this.prisma.user.update({
where: { id },
data: {
...updateUserDto,
// if password is provided, hash it
...(updateUserDto.password && {
password: await hash(updateUserDto.password, 10),
}),
},
});

// remove password from response
delete updatedUser.password;

return updatedUser;
} catch (error) {
// check if user not found and throw error
if (error.code === 'P2025') {
throw new NotFoundException(User with id </span><span class="p" style="color: var(--syntax-text-color)">${</span><span class="nx" style="color: var(--syntax-name-color)">id</span><span class="p" style="color: var(--syntax-text-color)">}</span><span class="s2" style="color: var(--syntax-string-color)"> not found); } // check if email already registered and throw error if (error.code === 'P2002') { throw new ConflictException('Email already registered'); } // throw error if any throw new HttpException(error, 500); } }

和最后一個函數 :deleteUser

async deleteUser(id: number): Promise<string> {
  try {
// find user by id. If not found, throw error
const user = await this.prisma.user.findUniqueOrThrow({
where: { id },
});

// delete user using prisma client
await this.prisma.user.delete({
where: { id },
});

return User with id </span><span class="p" style="color: var(--syntax-text-color)">${</span><span class="nx" style="color: var(--syntax-name-color)">user</span><span class="p" style="color: var(--syntax-text-color)">.</span><span class="nx" style="color: var(--syntax-name-color)">id</span><span class="p" style="color: var(--syntax-text-color)">}</span><span class="s2" style="color: var(--syntax-string-color)"> deleted; } catch (error) { // check if user not found and throw error if (error.code === 'P2025') { throw new NotFoundException(User with id </span><span class="p" style="color: var(--syntax-text-color)">${</span><span class="nx" style="color: var(--syntax-name-color)">id</span><span class="p" style="color: var(--syntax-text-color)">}</span><span class="s2" style="color: var(--syntax-string-color)"> not found); } // throw error if any throw new HttpException(error, 500); } }

現在 已完成,這是最終的完整代碼:UsersService

// src/modules/users/users.service.ts

import {
ConflictException,
HttpException,
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { User } from '@prisma/client';
import { PrismaService } from 'src/core/services/prisma.service';
import { CreateUserDto } from './dtos/create-user.dto';
import { compare, hash } from 'bcrypt';
import { LoginUserDto } from './dtos/login-user.dto';
import { JwtService } from '@nestjs/jwt';
import { LoginResponse, UserPayload } from './interfaces/users-login.interface';
import { UpdateUsertDto } from './dtos/update-user.dto';

@Injectable()
export class UsersService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService,
) {}

async registerUser(createUserDto: CreateUserDto): Promise<User> {
try {
// create new user using prisma client
const newUser = await this.prisma.user.create({
data: {
email: createUserDto.email,
password: await hash(createUserDto.password, 10), // hash user's password
name: createUserDto.name,
},
});

// remove password from response
delete newUser.password;

return newUser;
} catch (error) {
// check if email already registered and throw error
if (error.code === 'P2002') {
throw new ConflictException('Email already registered');
}

// throw error if any
throw new HttpException(error, 500);
}
}

async loginUser(loginUserDto: LoginUserDto): Promise<LoginResponse> {
try {
// find user by email
const user = await this.prisma.user.findUnique({
where: { email: loginUserDto.email },
});

// check if user exists
if (!user) {
throw new NotFoundException('User not found');
}

// check if password is correct by comparing it with the hashed password in the database
if (!(await compare(loginUserDto.password, user.password))) {
throw new UnauthorizedException('Invalid credentials');
}

const payload: UserPayload = {
// create payload for JWT
sub: user.id, // sub is short for subject. It is the user id
email: user.email,
name: user.name,
};

return {
// return access token
access_token: await this.jwtService.signAsync(payload),
};
} catch (error) {
// throw error if any
throw new HttpException(error, 500);
}
}

async updateUser(id: number, updateUserDto: UpdateUsertDto): Promise<User> {
try {
// find user by id. If not found, throw error
await this.prisma.user.findUniqueOrThrow({
where: { id },
});

// update user using prisma client
const updatedUser = await this.prisma.user.update({
where: { id },
data: {
...updateUserDto,
// if password is provided, hash it
...(updateUserDto.password && {
password: await hash(updateUserDto.password, 10),
}),
},
});

// remove password from response
delete updatedUser.password;

return updatedUser;
} catch (error) {
// check if user not found and throw error
if (error.code === 'P2025') {
throw new NotFoundException(User with id </span><span class="p" style="color: var(--syntax-text-color)">${</span><span class="nx" style="color: var(--syntax-name-color)">id</span><span class="p" style="color: var(--syntax-text-color)">}</span><span class="s2" style="color: var(--syntax-string-color)"> not found); } // check if email already registered and throw error if (error.code === 'P2002') { throw new ConflictException('Email already registered'); } // throw error if any throw new HttpException(error, 500); } } async deleteUser(id: number): Promise<string> { try { // find user by id. If not found, throw error const user = await this.prisma.user.findUniqueOrThrow({ where: { id }, }); // delete user using prisma client await this.prisma.user.delete({ where: { id }, }); return User with id </span><span class="p" style="color: var(--syntax-text-color)">${</span><span class="nx" style="color: var(--syntax-name-color)">user</span><span class="p" style="color: var(--syntax-text-color)">.</span><span class="nx" style="color: var(--syntax-name-color)">id</span><span class="p" style="color: var(--syntax-text-color)">}</span><span class="s2" style="color: var(--syntax-string-color)"> deleted; } catch (error) { // check if user not found and throw error if (error.code === 'P2025') { throw new NotFoundException(User with id </span><span class="p" style="color: var(--syntax-text-color)">${</span><span class="nx" style="color: var(--syntax-name-color)">id</span><span class="p" style="color: var(--syntax-text-color)">}</span><span class="s2" style="color: var(--syntax-string-color)"> not found); } // throw error if any throw new HttpException(error, 500); } } }

下一步是我們需要更新我們的 to interact with,這是完整的代碼:UsersControllerusersService

// src/modules/users/users.controller.ts

import {
Body,
Controller,
Delete,
Get,
Param,
ParseIntPipe,
Patch,
Post,
} from '@nestjs/common';
import { CreateUserDto } from './dtos/create-user.dto';
import { UpdateUsertDto } from './dtos/update-user.dto';
import { LoginUserDto } from './dtos/login-user.dto';
import { UsersService } from './users.service';
import { User } from '@prisma/client';
import { LoginResponse } from './interfaces/users-login.interface';

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

@Post('register')
async registerUser(@Body() createUserDto: CreateUserDto): Promise<User> {
// call users service method to register new user
return this.usersService.registerUser(createUserDto);
}

@Post('login')
loginUser(@Body() loginUserDto: LoginUserDto): Promise<LoginResponse> {
// call users service method to login user
return this.usersService.loginUser(loginUserDto);
}

@Get('me')
me(): string {
return 'Get my Profile!';
}

@Patch(':id')
async updateUser(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUsertDto,
): Promise<User> {
// call users service method to update user
return this.usersService.updateUser(+id, updateUserDto);
}

@Delete(':id')
async deleteUser(@Param('id', ParseIntPipe) id: number): Promise<string> {
// call users service method to delete user
return this.usersService.deleteUser(+id);
}
}

我們尚未更新終端節點。以后再做me

我們快完成了。我們需要注冊并:UsersControllerUsersServiceUsersModule

// src/modules/users/users.module.ts

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

@Module({
imports: [],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}

最后,我們已經完成了 register、login、update 和 delete。現在我們可以測試這些端點:users

上一篇:

基于.NetCore3.1搭建項目系列 —— 使用Swagger做Api文檔

下一篇:

macOS 查看監聽端口:深入解析與實操指南
#你可能也喜歡這些API文章!

我們有何不同?

API服務商零注冊

多API并行試用

數據驅動選型,提升決策效率

查看全部API→
??

熱門場景實測,選對API

#AI文本生成大模型API

對比大模型API的內容創意新穎性、情感共鳴力、商業轉化潛力

25個渠道
一鍵對比試用API 限時免費

#AI深度推理大模型API

對比大模型API的邏輯推理準確性、分析深度、可視化建議合理性

10個渠道
一鍵對比試用API 限時免費