田辺  亮介

田辺 亮介

1657551120

使用 Nestjs、SQLite 和 TypeORM 構建電子商務 API

介紹

Nestjs 是一個尖端的 Node.js 框架,用於開發高效、可靠和可擴展的服務器端應用程序。與 NoSQL 和 SQL 數據庫(如 MongoDB、YugabyteSQLitePostgresMySQL等)集成很簡單。它支持流行的對象關係映射器,例如 TypeORM Sequelize 和 Mongoose。

在本教程中,我們將使用 SQLite 和 TypeORM 創建一個電子商務應用程序。我們還將了解Arctype,一個強大的 SQL 客戶端和數據庫管理工具。

讓我們開始吧!

先決條件

本教程是一個動手演示,可幫助您入門。確保您滿足以下要求:

  • 您已安裝Node(>= 10.13.0,v13 除外)
  • 你有 Javascript 的基礎知識
  • 已安裝Arctype

項目設置

要開始使用 Nestjs,請使用以下命令安裝 Nestjs CLI:

npm i -g @nestjs/cli

安裝 NestJS CLI

安裝完成後,使用以下命令創建一個 Nestjs 項目:

nest new ecommerce

創建一個新項目

選擇你的npm作為包管理器,點擊回車按鈕,然後等待 Nest 安裝所需的包來運行這個應用程序。

安裝完成後,使用以下命令將目錄更改為項目文件夾:

cd ecommerce

更改目錄

然後在您喜歡的文本編輯器或 IDE 中打開項目目錄,打開一個新終端,並在開發模式下運行服務器(這將啟用熱重載並允許我們在控制台上查看可能的錯誤),使用以下命令:

npm run start:dev

啟動服務器

安裝依賴項

在服務器啟動並運行後,打開一個新的終端窗口,這樣您就不會退出服務器。這將允許您查看整個教程中對代碼庫所做的更改的效果。

現在安裝以下依賴項:

您可以使用以下命令執行此操作:

npm install --save @nestjs/passport passport passport-local @nestjs/jwt passport-jwt @nestjs/typeorm typeorm sqlite3 bcrypt

安裝依賴項

然後使用以下命令安裝開發依賴項:

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

安裝開發依賴項

在 npm 安裝軟件包時,您可以喝杯咖啡。安裝完成後,讓我們動手吧。

創建應用模塊

有了運行應用程序所需的所有必要包,讓我們繼續創建應用程序模塊。要構建一個乾淨且易於維護的應用程序,您將為該應用程序中實現的所有功能生成單獨的模塊。由於這是一個電子商務應用程序,您將擁有authenticationcartproductsorders。所有這些都將在各自獨立的模塊中。讓我們從身份驗證模塊開始。

創建認證模塊

使用以下命令生成身份驗證模塊:

nest g module auth

認證模塊

上面的命令在項目的src目錄中創建了一個auth文件夾,其中包含必要的樣板文件,並將模塊註冊到項目根模塊(app.module.ts文件)中。

接下來,使用以下命令創建產品、購物車、訂單、模塊:

#Create a product module
nest g module product

#Create cart module
nest g module cart

#Create cart module
nest g module order

附加模塊

以上將在項目的 src 文件夾中創建一個產品、購物車和訂單文件夾,其中包含基本的樣板文件,並將這些模塊註冊到項目的根應用程序模塊中。

設置 TypeORM 和 SQLite 數據庫

安裝應用程序模塊後,設置 TypeORM 以將應用程序連接到 SQLite 數據庫並創建模塊實體。首先,打開app.module.ts並使用以下代碼片段配置您的 SQLite 數據庫:

imports: [
 …
 TypeOrmModule.forRoot({
   type :"sqlite",
   database: "shoppingDB",
   entities: [__dirname + "/**/*.entity{.ts,.js}"],
   synchronize: true
 })
],
…

設置 TypeORM

在上面的代碼片段中,您使用 TypeORM forRoot 將應用程序連接到 SQLite 數據庫,指定數據庫類型、數據庫名稱以及 Nestjs 可以找到模型實體的位置。

服務器刷新後,您應該會看到在該項目的根目錄中創建了一個shoppingDB文件。

創建應用實體模型

通過數據庫設置,讓我們為我們的應用模塊創建實體模型。我們將從auth模塊開始。使用以下命令在 auth 模塊文件夾中生成實體文件:

nest generate class auth/user.entity –flat

創建實體文件

然後添加下面的代碼片段以使用下面的代碼片段定義用戶表屬性:

import { Entity, OneToOne, JoinColumn,Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, OneToMany } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'
import { OrderEntity } from 'src/order/order.entity'

@Entity()
export class Users {
   @PrimaryGeneratedColumn()
   id: number

   @Column()
   username: string

   @Column()
   password: string

   @Column()
   role: string

   @CreateDateColumn()
   createdAt : String

   @UpdateDateColumn()
   updtedAt : String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]

   @OneToOne(type => OrderEntity, order => order.id)
   @JoinColumn()
   order : OrderEntity;
}

定義屬性

在代碼片段中,您導入了設置數據庫表所需的裝飾器。您還導入了您將很快創建的cartEntityorderEntity類。使用typeorm裝飾器,我們定義了用戶模型的數據庫屬性。最後,我們在用戶實體與 cartEntity 和 orderEntity 之間創建了一對一一對多的關係。這樣,您可以將購物車項目與用戶相關聯。這同樣適用於用戶的訂單。

接下來,使用以下命令創建產品實體類:

nest generate class product/product.entity –flat

創建產品實體類

上面的命令會在 products 模塊文件夾中生成一個product.entity.ts文件。

現在使用下面的代碼片段配置產品表屬性:

import { Entity, JoinColumn, OneToMany, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'

@Entity()
export class ProductEntity {
   @PrimaryGeneratedColumn("uuid")
   id!: number

   @Column()
   name: string

   @Column()
   price: number

   @Column()
   quantity: string

   @CreateDateColumn()
   createdAt: String

   @UpdateDateColumn()
   updtedAt: String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]
}

定義屬性

在上面的代碼片段中,我們配置了產品表的屬性,並與購物車實體創建了一對多的關係。

然後使用以下命令創建購物車實體:

nest generate class cart/cart.entity –flat

購物車實體

上述命令將在購物車模塊文件夾中生成一個 cart.entity.ts 文件。現在將下面的代碼片段添加到您創建的文件以配置購物車錶屬性。

import { Entity, OneToOne,ManyToOne, JoinColumn, Column, PrimaryGeneratedColumn } from 'typeorm'
import { OrderEntity } from 'src/order/order.entity'
import { ProductEntity } from 'src/product/product.entity'
import { Users } from 'src/auth/user.entity'

@Entity()
export class CartEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @Column()
   total: number

   @Column()
   quantity: number
  
   @ManyToOne(type => ProductEntity, order => order.id)
   @JoinColumn()
   item: ProductEntity

   @ManyToOne(type => Users, user => user.username)
   @JoinColumn()
   user: Users
}

定義屬性

在上面的代碼片段中,您配置了購物車錶的屬性,創建了購物車實體之間的多對一關係以及與用戶實體的多對一關係。

最後,使用以下命令創建訂單實體:

nest generate class order/order.entity –flat

訂單實體

上述命令將在訂單模塊文件夾中生成一個order.entity.ts文件。打開order.entity.ts並使用以下命令配置數據庫表:

import { Entity, OneToMany, JoinColumn, OneToOne, Column, PrimaryGeneratedColumn } from 'typeorm'
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Entity()
export class OrderEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @OneToMany(type => ProductEntity, item => item.id)
   items: ProductEntity[];

   @OneToOne(type => Users, user => user.username)
   @JoinColumn()
   user : Users;

   @Column()
   subTotal: number

   @Column({ default: false })
   pending: boolean

}

定義屬性

在上面的代碼片段中,您創建了用戶實體之間的一對一關係以及與產品實體的一對多關係。

此時,您的數據庫實體已設置並連接。現在創建您的業務邏輯以在這些實體上存儲記錄。

創建應用服務

現在為這個應用程序中的模塊創建服務。在這裡,您將允許管理員將產品添加到產品表、驗證用戶身份、允許用戶將商店中的產品添加到購物車以及通過他們的購物車訂購產品。

創建身份驗證服務

要創建 auth 服務,請運行以下命令為 auth 模塊生成服務。

nest generate service auth/service/auth --flat

創建身份驗證服務

上面的命令會在src/auth/service文件夾中生成一個auth.service.ts文件。現在打開auth.service.ts文件並添加以下代碼片段:

import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from '../user.entity';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(@InjectRepository(Users) private userRepository: Repository<Users>, private jwt: JwtService) { }

   }

導入模塊

在上面的代碼片段中,您導入了 InjectRepository、 Repository裝飾器、JwtServicebcrypt模塊。然後,使用InjectRepository裝飾器,使User實體類在 auth 服務中可用,提供在 User 實體中執行 CRUD 操作的方法。

然後創建一個註冊方法,允許用戶使用下面的代碼片段在應用程序中註冊:

async signup(user: Users): Promise<Users> {
       const salt = await bcrypt.genSalt();
       const hash = await bcrypt.hash(user.password, salt);
       user.password = hash
       return await this.userRepository.save(user);
   }

報名方式

現在創建validateUser方法來驗證用戶的詳細信息,並創建login方法來為經過身份驗證的用戶生成 jwt 令牌。

 …
 async validateUser(username: string, password: string): Promise<any> {
       const foundUser = await this.userRepository.findOne({ username });
       if (foundUser) {
           if (await bcrypt.compare(password, foundUser.password)) {
               const { password, ...result } = foundUser
               return result;
           }

           return null;
       }
       return null

   }
   async login(user: any) {
       const payload = { username: user.username, sub: user.id, role:user.role };

       return {
           access_token: this.jwt.sign(payload),
       };
   }

驗證用戶

現在我們可以實現我們的 Passport本地身份驗證策略。在 auth 模塊文件夾中創建一個名為local.strategy.ts的文件,並添加以下代碼:

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './service/auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
   constructor(private authService: AuthService) {
       super();
   }

   async validate(username: string, password: string): Promise<any> {
  
       const foundUser = await this.authService.validateUser(username, password);
       if (!foundUser) {
           throw new UnauthorizedException();
       }
       return foundUser;
   }
}

本地認證策略

在上面的代碼片段中,您已經實現了本地護照策略。沒有配置選項,所以我們的構造函數只是調用super()而不帶選項對象。

您還實現了validate()方法。Passport 將使用適當的策略特定參數集為每個策略調用驗證函數。對於本地策略,Passport 需要具有以下簽名的validate()方法: validate(username: string, password:string): any。

然後在 auth 模塊文件夾中創建一個 jwt-auth.guard.ts 文件,並使用下面的代碼片段定義一個自定義 auth 保護:

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

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

定義自定義身份驗證保護

您將使用代碼片段中創建的 AuthGuard 來保護您的 API 路由免受未經授權的用戶的攻擊。

現在在 auth 模塊文件夾中創建一個 jwt-strategy 文件來驗證用戶並使用以下代碼片段為登錄用戶生成 jwt 令牌:

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
 constructor() {
   super({
     jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
     ignoreExpiration: false,
     secretOrKey: jwtConstants.secret,
   });
 }

 async validate(payload: any) {
   return { userId: payload.sub, username: payload.username, role: payload.role };
 }
}

策略文件

然後在auth模塊文件夾的auth.module.ts文件中配置jwt模塊。在此之前,在同一個 auth 模塊文件夾中創建一個 constants.ts 文件來定義一個 jwt 密碼,代碼片段如下:

export const jwtConstants = {
   secret: 'wjeld-djeuedw399e3-uejheuii33-4jrjjejei3-rjdjfjf',
}

秘密(用於測試目的)

您可以在生產環境中生成更安全的 jwt secret,但我們將使用它來進行演示。

現在使用下面的代碼片段在您的 auth.module.ts 文件中導入所有必需的模塊:

…
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './user.entity';
…

導入模塊

然後在導入數組中,使用以下代碼片段配置 jwt:

…
imports: [
   PassportModule,
   JwtModule.register({
     secret: jwtConstants.secret,
     signOptions: { expiresIn: '60m' },
   }),
…

配置 jwt

在上面的代碼片段中,我們添加了PassModule包以允許 Passport 處理用戶的身份驗證並使用JwtModule 註冊方法配置 jwt。我們傳入我們在常量文件中創建的秘密,並指定生成的令牌的過期時間(您可以根據用例減少或增加時間)。

創建產品服務

通過 auth 服務設置,使用以下命令生成產品服務:

nest generate service product/service/product

產品服務

現在在product模塊中打開上述命令生成的product.service.ts文件,添加如下代碼片段:

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ProductEntity } from '../product.entity';
import { Repository, UpdateResult, DeleteResult } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class ProductsService {
   constructor(@InjectRepository(ProductEntity) private productRepository: Repository<ProductEntity>) { }
  
   async getAll(): Promise<ProductEntity[]> {
       return await this.productRepository.find()
   }

   async create(product: ProductEntity, user: Users): Promise<ProductEntity> {
       if (user.role == 'admin') {
           return await this.productRepository.save(product);

       }
       throw new UnauthorizedException();

   }

   async getOne(id: number): Promise<ProductEntity> {
       return this.productRepository.findOne(id);
   }

   async update(id: number, product: ProductEntity, user: Users): Promise<UpdateResult> {
       if (user.role == 'admin') {
           return await this.productRepository.update(id, product);
       }
       throw new UnauthorizedException();
   }

   async delete(id: number, user: Users): Promise<DeleteResult> {
       if (user.role == 'admin') {
           return await this.productRepository.delete(id);
       }
       throw new UnauthorizedException();
   }
  
}

創建 CRUD 服務

在上面的代碼片段中,我們創建了 CRUD 服務。createupdatedelete方法對用戶是有限制的。只有管​​理員可以創建產品、刪除或更新產品。

現在打開 product.module.ts 文件並使用以下代碼片段使產品實體可訪問:

imports: [TypeOrmModule.forFeature([ProductEntity])],

使產品實體可訪問

創建購物車服務

此時,管理員可以將產品添加到數據庫中,並且經過身份驗證的用戶可以看到所有可用的產品。現在讓我們允許用戶將他們喜歡的商品添加到購物車中。首先,使用以下命令生成購物車服務:

nest generate service cart/service/cart –flat

製作購物車服務

然後打開該命令生成的cart.service.ts文件,添加如下代碼片段:

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartEntity } from '../cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class CartService {
   constructor(
       @InjectRepository(CartEntity)
       private cartRepository: Repository<CartEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private productsService: ProductsService,
   ) { }
   …

購物車服務文件

您在上面的代碼片段中導入了創建 Nest.js 服務所需的模塊。由於您在購物車、用戶和產品實體之間創建了關係,因此我們也在此處導入了它們。然後,您創建一個構造方法來將這些實體綁定到 CartService 類。現在創建一個將商品添加到購物車的方法。

async addToCart(productId: number, quantity: number, user: string): Promise<any> {
       const cartItems = await this.cartRepository.find({ relations: ["item",'user'] });
       const product = await this.productsService.getOne(productId);
       const authUser = await this.userRepository.findOne({ username: user })
      
       //Confirm the product exists.
       if (product) {
           //confirm if user has item in cart
           const cart = cartItems.filter(
               (item) => item.item.id === productId && item.user.username === user,
           );
           if (cart.length < 1) {

               const newItem = this.cartRepository.create({ total: product.price * quantity, quantity });
               newItem.user = authUser;
               newItem.item = product;
               this.cartRepository.save(newItem)


               return await this.cartRepository.save(newItem)
           } else {
               //Update the item quantity
               const quantity = (cart[0].quantity += 1);
               const total = cart[0].total * quantity;

               return await this.cartRepository.update(cart[0].id, { quantity, total });
           }
       }
       return null;
   }

將商品添加到購物車

在上面的代碼片段中,您創建了一個addToCart方法,該方法將productId數量和用戶作為參數。然後檢查用戶的購物車中是否已有該商品。如果是這樣,您增加數量並更新該項目的總價。否則,您將商品添加到用戶的購物車。

接下來,通過使用以下代碼片段在cart.module.ts文件中註冊cartEntityproductEntity 用戶實體和productService ,使它們可以在cartService中訪問:

…
import { CartEntity } from './cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Module({
 imports: [TypeOrmModule.forFeature([CartEntity, ProductEntity, Users])],
 providers: [CartService, ProductsService],
 …
})

註冊服務

最後,創建一個 getItemsInCart 方法,該方法將用戶作為參數返回屬於特定用戶的所有購物車。

async getItemsInCard(user: string): Promise<CartEntity[]> {
       const userCart = await this.cartRepository.find({ relations: ["item",'user'] });
       return (await userCart).filter(item => item.user.username === user)
   }

獲取購物車中的物品 

創建訂單服務

當用戶完成購物後,他們可以訂購購物車中的商品。使用以下命令生成訂單服務:

nest generate service order/service/order –flat

創建訂單服務

現在打開運行上述命令生成的 order.service.ts 文件並添加以下代碼片段:

import { OrderEntity } from '../order.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartService } from 'src/cart/service/cart.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class OrderService {
   constructor(@InjectRepository(OrderEntity)
   private orderRepository: Repository<OrderEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private cartService: CartService) { }

訂單服務

您在上面的代碼片段中導入了創建 Nest.js 服務所需的模塊。由於您在購物車、用戶和產品實體之間創建了關係,因此我們也在此處導入了它們。然後,您創建了一個構造方法來將這些實體綁定到 OrderService 類。現在創建一個方法來訂購用戶購物車中的物品。

…
async order(user: string): Promise<any> {
       //find user existing orders
       const usersOrder = await this.orderRepository.find({ relations: ['user'] });
       const userOrder = usersOrder.filter(order => order.user?.username === user && order.pending === false);
       //find user's cart items
       const cartItems = await this.cartService.getItemsInCard(user)
       const subTotal = cartItems.map(item => item.total).reduce((acc, next) => acc + next);
       //get the authenticated user
       const authUser = await this.userRepository.findOne({ username: user })
       //if users has an pending order - add item to the list of order
       const cart = await cartItems.map(item => item.item);

       if (userOrder.length === 0) {
           const newOrder = await this.orderRepository.create({ subTotal });
           newOrder.items = cart
           newOrder.user = authUser;
           return await this.orderRepository.save(newOrder);


       } else {
           const existingOrder = userOrder.map(item => item)
           await this.orderRepository.update(existingOrder[0].id, { subTotal: existingOrder[0].subTotal + cart[0].price });
           return { message: "order modified" }
       }
   }
…

訂購用戶購物車中的商品

然後使用下面的代碼片段創建另一個方法來從數據庫的訂單數組中獲取用戶的訂單:

…
async getOrders(user: string): Promise<OrderEntity[]> {
       const orders = await this.orderRepository.find({ relations: ['user'] });
       return orders.filter(order => order.user?.username === user)
   }
}

獲取用戶訂單

最後,打開 order.module.ts 文件並使用以下代碼片段在 orderService 中訪問用戶、產品和購物車實體:

import { TypeOrmModule } from '@nestjs/typeorm';
import { OrderEntity } from './order.entity';
import { ProductEntity } from 'src/product/product.entity';
import { CartService } from 'src/cart/service/cart.service';
import { CartEntity } from 'src/cart/cart.entity';
import { Users } from 'src/auth/user.entity';
import { ProductsService } from 'src/product/service/products.service';

@Module({
 imports: [TypeOrmModule.forFeature([OrderEntity, ProductEntity, CartEntity, Users])],
 controllers: [OrderController],
 providers: [OrderService, CartService, ProductsService]
})

使實體可訪問

創建應用控制器

成功創建應用服務後,讓我們為應用服務創建 API 路由。

創建身份驗證控制器

使用以下命令生成身份驗證控制器:

nest generate controller auth/controller/auth –flat

現在打開運行上述命令生成的auth.controller.ts文件,並使用以下代碼段配置身份驗證路由:

import { Controller, Request, Post, UseGuards, Body } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '../service/auth.service';
import { Users } from '../user.entity';

@Controller('api/v1/auth/')
export class AuthController {
   constructor(private usersService: AuthService) { }
  
   @Post('signup')
   async signup(@Body() user: Users): Promise<Users> {
       return this.usersService.signup(user);
   }

   @UseGuards(AuthGuard('local'))
   @Post('login')
   async login(@Request() req) {
       return this.usersService.login(req.user)
   }
}

配置認證路由

創建產品控制器

配置 auth 控制器路由後,使用以下命令生成產品控制器:

nest generate controller product/controller/product –flat

產品控制器

打開運行上述命令生成的 product.controller.ts 文件,並使用以下代碼段配置產品路由:

import { Controller, Post, Get, Put, Delete, Param, Request, Body, UseGuards } from '@nestjs/common';
import {UpdateResult, DeleteResult} from 'typeorm';
import { ProductsService } from '../service/products.service';
import { ProductEntity } from '../product.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/products')
export class ProductsController {
 constructor(private productsService: ProductsService) { }

 @UseGuards(JwtAuthGuard)
 @Get()
 async GetAll(): Promise<ProductEntity[]> {
   return await this.productsService.getAll();

 }

 @UseGuards(JwtAuthGuard)
 @Post()
 async Create(@Request() req, @Body() product: ProductEntity): Promise<ProductEntity> {
   return await this.productsService.create(product, req.user);
 }


 @UseGuards(JwtAuthGuard)
 @Get(':id')
 async GetOne(@Param() id: number): Promise<ProductEntity> {
   return await this.productsService.getOne(id);

 }

 @UseGuards(JwtAuthGuard)
 @Put(':id')
 async Update(@Param() id: number, @Body() product: ProductEntity, @Request() req): Promise<UpdateResult> {
   return await this.productsService.update(id, product, req.user);

 }

 @UseGuards(JwtAuthGuard)
 @Delete(':id')
 async Delete(@Param() id: number, @Request() req): Promise<DeleteResult> {
   return await this.productsService.delete(id, req.user);

 }
}

產品路線

在上面的代碼片段中,您為產品服務定義了 CRUD 路由。我們使用UseGuard裝飾器傳遞您的JwtAuthGaurd來保護路由免受未經授權的用戶的攻擊。

創建購物車控制器

現在使用以下命令生成一個購物車控制器:

nest generate controller cart/controller/cart –flat

推車控制器

然後打開運行上述命令生成的cart.controller.ts文件,並使用以下代碼段配置購物車路線:

import { Controller, Post, Get,Request, Delete, Body, UseGuards } from '@nestjs/common';
import { CartService } from '../service/cart.service';
import { CartEntity } from '../cart.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/cart')
export class CartController {
   constructor(private cartService: CartService) { }

   @UseGuards(JwtAuthGuard)
   @Post()
   async AddToCart(@Body() body, @Request() req): Promise<void> {
       const { productId, quantity } = body
       return await this.cartService.addToCart(productId, quantity, req.user.username);
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getItemsInCart(@Request() req): Promise<CartEntity[]> {
       return await this.cartService.getItemsInCard(req.user.username);

   }
}

購物車路線

創建訂單控制器

配置購物車路線後,使用以下命令創建訂單控制器:

nest generate controller order/controller/order –flat

訂單控制器

然後打開運行上述命令生成的order.controlle.ts文件,並使用以下代碼段配置購物車路線:

import { Controller, Post, Get, Request, UseGuards } from '@nestjs/common';
import { OrderService } from '../service/order.service'
import { OrderEntity } from '../order.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/order')
export class OrderController {
   constructor(private orderService: OrderService) { }


   @UseGuards(JwtAuthGuard)
   @Post()
   async order(@Request() req): Promise<any> {
       return this.orderService.order(req.user.username)
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getOrders(@Request() req): Promise<OrderEntity[]> {
       return await this.orderService.getOrders(req.user.username)
   }
}

訂購路線

至此,您的所有 API 路由都已配置完畢。

測試申請

現在讓我們使用 Postman 測試它們。come 測試的結果如下所示。

用戶路線登錄路線產品路線。在添加產品之前複製訪問令牌並將其添加到請求標頭中。

隨意測試其他路線並在 Github 上使用代碼

將 Arctype 連接到 SQLite 數據庫

Arctype是一個使用起來很有趣的 SQL 客戶端和數據庫管理工具。它允許您擁有數據庫表的可視化表示,並且您可以使用 Arctype 在數據庫中執行 CRUD 操作目錄。將 Arctype 連接到 SQLite 數據庫是一個簡單的過程。要開始,請按照以下步驟操作:

首先,啟動 Arctype。您將看到如下所示的屏幕,允許您添加憑據。

單擊 SQLite 選項卡。這將打開下面的屏幕。

單擊選擇 SQLite 文件按鈕並導航到您的項目文件夾。選擇應用程序數據庫文件並按打開按鈕。按保存按鈕,您應該會看到您的數據庫實體,如下面的屏幕截圖所示。

結論

通過構建一個演示項目,我們學習瞭如何使用 Nestjs 和 SQLite 數據庫製作電子商務應用程序。首先,我們從 NestJS 的介紹開始。然後,我們創建了一個 NestJS 應用程序,使用 TypeORM 將應用程序連接到 SQLite 數據庫,並執行 CRUD 操作。現在您已經獲得了您所尋求的知識,可以隨意向應用程序添加額外的功能。

鏈接:https ://arctype.com/blog/sqlite-nestjs-tutorial/

#nestjs  #typeorm #sqlite 

What is GEEK

Buddha Community

使用 Nestjs、SQLite 和 TypeORM 構建電子商務 API

Top 10 API Security Threats Every API Team Should Know

As more and more data is exposed via APIs either as API-first companies or for the explosion of single page apps/JAMStack, API security can no longer be an afterthought. The hard part about APIs is that it provides direct access to large amounts of data while bypassing browser precautions. Instead of worrying about SQL injection and XSS issues, you should be concerned about the bad actor who was able to paginate through all your customer records and their data.

Typical prevention mechanisms like Captchas and browser fingerprinting won’t work since APIs by design need to handle a very large number of API accesses even by a single customer. So where do you start? The first thing is to put yourself in the shoes of a hacker and then instrument your APIs to detect and block common attacks along with unknown unknowns for zero-day exploits. Some of these are on the OWASP Security API list, but not all.

Insecure pagination and resource limits

Most APIs provide access to resources that are lists of entities such as /users or /widgets. A client such as a browser would typically filter and paginate through this list to limit the number items returned to a client like so:

First Call: GET /items?skip=0&take=10 
Second Call: GET /items?skip=10&take=10

However, if that entity has any PII or other information, then a hacker could scrape that endpoint to get a dump of all entities in your database. This could be most dangerous if those entities accidently exposed PII or other sensitive information, but could also be dangerous in providing competitors or others with adoption and usage stats for your business or provide scammers with a way to get large email lists. See how Venmo data was scraped

A naive protection mechanism would be to check the take count and throw an error if greater than 100 or 1000. The problem with this is two-fold:

  1. For data APIs, legitimate customers may need to fetch and sync a large number of records such as via cron jobs. Artificially small pagination limits can force your API to be very chatty decreasing overall throughput. Max limits are to ensure memory and scalability requirements are met (and prevent certain DDoS attacks), not to guarantee security.
  2. This offers zero protection to a hacker that writes a simple script that sleeps a random delay between repeated accesses.
skip = 0
while True:    response = requests.post('https://api.acmeinc.com/widgets?take=10&skip=' + skip),                      headers={'Authorization': 'Bearer' + ' ' + sys.argv[1]})    print("Fetched 10 items")    sleep(randint(100,1000))    skip += 10

How to secure against pagination attacks

To secure against pagination attacks, you should track how many items of a single resource are accessed within a certain time period for each user or API key rather than just at the request level. By tracking API resource access at the user level, you can block a user or API key once they hit a threshold such as “touched 1,000,000 items in a one hour period”. This is dependent on your API use case and can even be dependent on their subscription with you. Like a Captcha, this can slow down the speed that a hacker can exploit your API, like a Captcha if they have to create a new user account manually to create a new API key.

Insecure API key generation

Most APIs are protected by some sort of API key or JWT (JSON Web Token). This provides a natural way to track and protect your API as API security tools can detect abnormal API behavior and block access to an API key automatically. However, hackers will want to outsmart these mechanisms by generating and using a large pool of API keys from a large number of users just like a web hacker would use a large pool of IP addresses to circumvent DDoS protection.

How to secure against API key pools

The easiest way to secure against these types of attacks is by requiring a human to sign up for your service and generate API keys. Bot traffic can be prevented with things like Captcha and 2-Factor Authentication. Unless there is a legitimate business case, new users who sign up for your service should not have the ability to generate API keys programmatically. Instead, only trusted customers should have the ability to generate API keys programmatically. Go one step further and ensure any anomaly detection for abnormal behavior is done at the user and account level, not just for each API key.

Accidental key exposure

APIs are used in a way that increases the probability credentials are leaked:

  1. APIs are expected to be accessed over indefinite time periods, which increases the probability that a hacker obtains a valid API key that’s not expired. You save that API key in a server environment variable and forget about it. This is a drastic contrast to a user logging into an interactive website where the session expires after a short duration.
  2. The consumer of an API has direct access to the credentials such as when debugging via Postman or CURL. It only takes a single developer to accidently copy/pastes the CURL command containing the API key into a public forum like in GitHub Issues or Stack Overflow.
  3. API keys are usually bearer tokens without requiring any other identifying information. APIs cannot leverage things like one-time use tokens or 2-factor authentication.

If a key is exposed due to user error, one may think you as the API provider has any blame. However, security is all about reducing surface area and risk. Treat your customer data as if it’s your own and help them by adding guards that prevent accidental key exposure.

How to prevent accidental key exposure

The easiest way to prevent key exposure is by leveraging two tokens rather than one. A refresh token is stored as an environment variable and can only be used to generate short lived access tokens. Unlike the refresh token, these short lived tokens can access the resources, but are time limited such as in hours or days.

The customer will store the refresh token with other API keys. Then your SDK will generate access tokens on SDK init or when the last access token expires. If a CURL command gets pasted into a GitHub issue, then a hacker would need to use it within hours reducing the attack vector (unless it was the actual refresh token which is low probability)

Exposure to DDoS attacks

APIs open up entirely new business models where customers can access your API platform programmatically. However, this can make DDoS protection tricky. Most DDoS protection is designed to absorb and reject a large number of requests from bad actors during DDoS attacks but still need to let the good ones through. This requires fingerprinting the HTTP requests to check against what looks like bot traffic. This is much harder for API products as all traffic looks like bot traffic and is not coming from a browser where things like cookies are present.

Stopping DDoS attacks

The magical part about APIs is almost every access requires an API Key. If a request doesn’t have an API key, you can automatically reject it which is lightweight on your servers (Ensure authentication is short circuited very early before later middleware like request JSON parsing). So then how do you handle authenticated requests? The easiest is to leverage rate limit counters for each API key such as to handle X requests per minute and reject those above the threshold with a 429 HTTP response. There are a variety of algorithms to do this such as leaky bucket and fixed window counters.

Incorrect server security

APIs are no different than web servers when it comes to good server hygiene. Data can be leaked due to misconfigured SSL certificate or allowing non-HTTPS traffic. For modern applications, there is very little reason to accept non-HTTPS requests, but a customer could mistakenly issue a non HTTP request from their application or CURL exposing the API key. APIs do not have the protection of a browser so things like HSTS or redirect to HTTPS offer no protection.

How to ensure proper SSL

Test your SSL implementation over at Qualys SSL Test or similar tool. You should also block all non-HTTP requests which can be done within your load balancer. You should also remove any HTTP headers scrub any error messages that leak implementation details. If your API is used only by your own apps or can only be accessed server-side, then review Authoritative guide to Cross-Origin Resource Sharing for REST APIs

Incorrect caching headers

APIs provide access to dynamic data that’s scoped to each API key. Any caching implementation should have the ability to scope to an API key to prevent cross-pollution. Even if you don’t cache anything in your infrastructure, you could expose your customers to security holes. If a customer with a proxy server was using multiple API keys such as one for development and one for production, then they could see cross-pollinated data.

#api management #api security #api best practices #api providers #security analytics #api management policies #api access tokens #api access #api security risks #api access keys

Autumn  Blick

Autumn Blick

1601381326

Public ASX100 APIs: The Essential List

We’ve conducted some initial research into the public APIs of the ASX100 because we regularly have conversations about what others are doing with their APIs and what best practices look like. Being able to point to good local examples and explain what is happening in Australia is a key part of this conversation.

Method

The method used for this initial research was to obtain a list of the ASX100 (as of 18 September 2020). Then work through each company looking at the following:

  1. Whether the company had a public API: this was found by googling “[company name] API” and “[company name] API developer” and “[company name] developer portal”. Sometimes the company’s website was navigated or searched.
  2. Some data points about the API were noted, such as the URL of the portal/documentation and the method they used to publish the API (portal, documentation, web page).
  3. Observations were recorded that piqued the interest of the researchers (you will find these below).
  4. Other notes were made to support future research.
  5. You will find a summary of the data in the infographic below.

Data

With regards to how the APIs are shared:

#api #api-development #api-analytics #apis #api-integration #api-testing #api-security #api-gateway

田辺  亮介

田辺 亮介

1657551120

使用 Nestjs、SQLite 和 TypeORM 構建電子商務 API

介紹

Nestjs 是一個尖端的 Node.js 框架,用於開發高效、可靠和可擴展的服務器端應用程序。與 NoSQL 和 SQL 數據庫(如 MongoDB、YugabyteSQLitePostgresMySQL等)集成很簡單。它支持流行的對象關係映射器,例如 TypeORM Sequelize 和 Mongoose。

在本教程中,我們將使用 SQLite 和 TypeORM 創建一個電子商務應用程序。我們還將了解Arctype,一個強大的 SQL 客戶端和數據庫管理工具。

讓我們開始吧!

先決條件

本教程是一個動手演示,可幫助您入門。確保您滿足以下要求:

  • 您已安裝Node(>= 10.13.0,v13 除外)
  • 你有 Javascript 的基礎知識
  • 已安裝Arctype

項目設置

要開始使用 Nestjs,請使用以下命令安裝 Nestjs CLI:

npm i -g @nestjs/cli

安裝 NestJS CLI

安裝完成後,使用以下命令創建一個 Nestjs 項目:

nest new ecommerce

創建一個新項目

選擇你的npm作為包管理器,點擊回車按鈕,然後等待 Nest 安裝所需的包來運行這個應用程序。

安裝完成後,使用以下命令將目錄更改為項目文件夾:

cd ecommerce

更改目錄

然後在您喜歡的文本編輯器或 IDE 中打開項目目錄,打開一個新終端,並在開發模式下運行服務器(這將啟用熱重載並允許我們在控制台上查看可能的錯誤),使用以下命令:

npm run start:dev

啟動服務器

安裝依賴項

在服務器啟動並運行後,打開一個新的終端窗口,這樣您就不會退出服務器。這將允許您查看整個教程中對代碼庫所做的更改的效果。

現在安裝以下依賴項:

您可以使用以下命令執行此操作:

npm install --save @nestjs/passport passport passport-local @nestjs/jwt passport-jwt @nestjs/typeorm typeorm sqlite3 bcrypt

安裝依賴項

然後使用以下命令安裝開發依賴項:

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

安裝開發依賴項

在 npm 安裝軟件包時,您可以喝杯咖啡。安裝完成後,讓我們動手吧。

創建應用模塊

有了運行應用程序所需的所有必要包,讓我們繼續創建應用程序模塊。要構建一個乾淨且易於維護的應用程序,您將為該應用程序中實現的所有功能生成單獨的模塊。由於這是一個電子商務應用程序,您將擁有authenticationcartproductsorders。所有這些都將在各自獨立的模塊中。讓我們從身份驗證模塊開始。

創建認證模塊

使用以下命令生成身份驗證模塊:

nest g module auth

認證模塊

上面的命令在項目的src目錄中創建了一個auth文件夾,其中包含必要的樣板文件,並將模塊註冊到項目根模塊(app.module.ts文件)中。

接下來,使用以下命令創建產品、購物車、訂單、模塊:

#Create a product module
nest g module product

#Create cart module
nest g module cart

#Create cart module
nest g module order

附加模塊

以上將在項目的 src 文件夾中創建一個產品、購物車和訂單文件夾,其中包含基本的樣板文件,並將這些模塊註冊到項目的根應用程序模塊中。

設置 TypeORM 和 SQLite 數據庫

安裝應用程序模塊後,設置 TypeORM 以將應用程序連接到 SQLite 數據庫並創建模塊實體。首先,打開app.module.ts並使用以下代碼片段配置您的 SQLite 數據庫:

imports: [
 …
 TypeOrmModule.forRoot({
   type :"sqlite",
   database: "shoppingDB",
   entities: [__dirname + "/**/*.entity{.ts,.js}"],
   synchronize: true
 })
],
…

設置 TypeORM

在上面的代碼片段中,您使用 TypeORM forRoot 將應用程序連接到 SQLite 數據庫,指定數據庫類型、數據庫名稱以及 Nestjs 可以找到模型實體的位置。

服務器刷新後,您應該會看到在該項目的根目錄中創建了一個shoppingDB文件。

創建應用實體模型

通過數據庫設置,讓我們為我們的應用模塊創建實體模型。我們將從auth模塊開始。使用以下命令在 auth 模塊文件夾中生成實體文件:

nest generate class auth/user.entity –flat

創建實體文件

然後添加下面的代碼片段以使用下面的代碼片段定義用戶表屬性:

import { Entity, OneToOne, JoinColumn,Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn, OneToMany } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'
import { OrderEntity } from 'src/order/order.entity'

@Entity()
export class Users {
   @PrimaryGeneratedColumn()
   id: number

   @Column()
   username: string

   @Column()
   password: string

   @Column()
   role: string

   @CreateDateColumn()
   createdAt : String

   @UpdateDateColumn()
   updtedAt : String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]

   @OneToOne(type => OrderEntity, order => order.id)
   @JoinColumn()
   order : OrderEntity;
}

定義屬性

在代碼片段中,您導入了設置數據庫表所需的裝飾器。您還導入了您將很快創建的cartEntityorderEntity類。使用typeorm裝飾器,我們定義了用戶模型的數據庫屬性。最後,我們在用戶實體與 cartEntity 和 orderEntity 之間創建了一對一一對多的關係。這樣,您可以將購物車項目與用戶相關聯。這同樣適用於用戶的訂單。

接下來,使用以下命令創建產品實體類:

nest generate class product/product.entity –flat

創建產品實體類

上面的命令會在 products 模塊文件夾中生成一個product.entity.ts文件。

現在使用下面的代碼片段配置產品表屬性:

import { Entity, JoinColumn, OneToMany, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
import { CartEntity } from 'src/cart/cart.entity'

@Entity()
export class ProductEntity {
   @PrimaryGeneratedColumn("uuid")
   id!: number

   @Column()
   name: string

   @Column()
   price: number

   @Column()
   quantity: string

   @CreateDateColumn()
   createdAt: String

   @UpdateDateColumn()
   updtedAt: String

   @OneToMany(type => CartEntity, cart => cart.id)
   @JoinColumn()
   cart: CartEntity[]
}

定義屬性

在上面的代碼片段中,我們配置了產品表的屬性,並與購物車實體創建了一對多的關係。

然後使用以下命令創建購物車實體:

nest generate class cart/cart.entity –flat

購物車實體

上述命令將在購物車模塊文件夾中生成一個 cart.entity.ts 文件。現在將下面的代碼片段添加到您創建的文件以配置購物車錶屬性。

import { Entity, OneToOne,ManyToOne, JoinColumn, Column, PrimaryGeneratedColumn } from 'typeorm'
import { OrderEntity } from 'src/order/order.entity'
import { ProductEntity } from 'src/product/product.entity'
import { Users } from 'src/auth/user.entity'

@Entity()
export class CartEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @Column()
   total: number

   @Column()
   quantity: number
  
   @ManyToOne(type => ProductEntity, order => order.id)
   @JoinColumn()
   item: ProductEntity

   @ManyToOne(type => Users, user => user.username)
   @JoinColumn()
   user: Users
}

定義屬性

在上面的代碼片段中,您配置了購物車錶的屬性,創建了購物車實體之間的多對一關係以及與用戶實體的多對一關係。

最後,使用以下命令創建訂單實體:

nest generate class order/order.entity –flat

訂單實體

上述命令將在訂單模塊文件夾中生成一個order.entity.ts文件。打開order.entity.ts並使用以下命令配置數據庫表:

import { Entity, OneToMany, JoinColumn, OneToOne, Column, PrimaryGeneratedColumn } from 'typeorm'
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Entity()
export class OrderEntity {
   @PrimaryGeneratedColumn('uuid')
   id: number

   @OneToMany(type => ProductEntity, item => item.id)
   items: ProductEntity[];

   @OneToOne(type => Users, user => user.username)
   @JoinColumn()
   user : Users;

   @Column()
   subTotal: number

   @Column({ default: false })
   pending: boolean

}

定義屬性

在上面的代碼片段中,您創建了用戶實體之間的一對一關係以及與產品實體的一對多關係。

此時,您的數據庫實體已設置並連接。現在創建您的業務邏輯以在這些實體上存儲記錄。

創建應用服務

現在為這個應用程序中的模塊創建服務。在這裡,您將允許管理員將產品添加到產品表、驗證用戶身份、允許用戶將商店中的產品添加到購物車以及通過他們的購物車訂購產品。

創建身份驗證服務

要創建 auth 服務,請運行以下命令為 auth 模塊生成服務。

nest generate service auth/service/auth --flat

創建身份驗證服務

上面的命令會在src/auth/service文件夾中生成一個auth.service.ts文件。現在打開auth.service.ts文件並添加以下代碼片段:

import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Users } from '../user.entity';
import * as bcrypt from 'bcrypt';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(@InjectRepository(Users) private userRepository: Repository<Users>, private jwt: JwtService) { }

   }

導入模塊

在上面的代碼片段中,您導入了 InjectRepository、 Repository裝飾器、JwtServicebcrypt模塊。然後,使用InjectRepository裝飾器,使User實體類在 auth 服務中可用,提供在 User 實體中執行 CRUD 操作的方法。

然後創建一個註冊方法,允許用戶使用下面的代碼片段在應用程序中註冊:

async signup(user: Users): Promise<Users> {
       const salt = await bcrypt.genSalt();
       const hash = await bcrypt.hash(user.password, salt);
       user.password = hash
       return await this.userRepository.save(user);
   }

報名方式

現在創建validateUser方法來驗證用戶的詳細信息,並創建login方法來為經過身份驗證的用戶生成 jwt 令牌。

 …
 async validateUser(username: string, password: string): Promise<any> {
       const foundUser = await this.userRepository.findOne({ username });
       if (foundUser) {
           if (await bcrypt.compare(password, foundUser.password)) {
               const { password, ...result } = foundUser
               return result;
           }

           return null;
       }
       return null

   }
   async login(user: any) {
       const payload = { username: user.username, sub: user.id, role:user.role };

       return {
           access_token: this.jwt.sign(payload),
       };
   }

驗證用戶

現在我們可以實現我們的 Passport本地身份驗證策略。在 auth 模塊文件夾中創建一個名為local.strategy.ts的文件,並添加以下代碼:

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './service/auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
   constructor(private authService: AuthService) {
       super();
   }

   async validate(username: string, password: string): Promise<any> {
  
       const foundUser = await this.authService.validateUser(username, password);
       if (!foundUser) {
           throw new UnauthorizedException();
       }
       return foundUser;
   }
}

本地認證策略

在上面的代碼片段中,您已經實現了本地護照策略。沒有配置選項,所以我們的構造函數只是調用super()而不帶選項對象。

您還實現了validate()方法。Passport 將使用適當的策略特定參數集為每個策略調用驗證函數。對於本地策略,Passport 需要具有以下簽名的validate()方法: validate(username: string, password:string): any。

然後在 auth 模塊文件夾中創建一個 jwt-auth.guard.ts 文件,並使用下面的代碼片段定義一個自定義 auth 保護:

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

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

定義自定義身份驗證保護

您將使用代碼片段中創建的 AuthGuard 來保護您的 API 路由免受未經授權的用戶的攻擊。

現在在 auth 模塊文件夾中創建一個 jwt-strategy 文件來驗證用戶並使用以下代碼片段為登錄用戶生成 jwt 令牌:

import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
import { jwtConstants } from './constants';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
 constructor() {
   super({
     jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
     ignoreExpiration: false,
     secretOrKey: jwtConstants.secret,
   });
 }

 async validate(payload: any) {
   return { userId: payload.sub, username: payload.username, role: payload.role };
 }
}

策略文件

然後在auth模塊文件夾的auth.module.ts文件中配置jwt模塊。在此之前,在同一個 auth 模塊文件夾中創建一個 constants.ts 文件來定義一個 jwt 密碼,代碼片段如下:

export const jwtConstants = {
   secret: 'wjeld-djeuedw399e3-uejheuii33-4jrjjejei3-rjdjfjf',
}

秘密(用於測試目的)

您可以在生產環境中生成更安全的 jwt secret,但我們將使用它來進行演示。

現在使用下面的代碼片段在您的 auth.module.ts 文件中導入所有必需的模塊:

…
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { JwtStrategy } from './jwt.strategy';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Users } from './user.entity';
…

導入模塊

然後在導入數組中,使用以下代碼片段配置 jwt:

…
imports: [
   PassportModule,
   JwtModule.register({
     secret: jwtConstants.secret,
     signOptions: { expiresIn: '60m' },
   }),
…

配置 jwt

在上面的代碼片段中,我們添加了PassModule包以允許 Passport 處理用戶的身份驗證並使用JwtModule 註冊方法配置 jwt。我們傳入我們在常量文件中創建的秘密,並指定生成的令牌的過期時間(您可以根據用例減少或增加時間)。

創建產品服務

通過 auth 服務設置,使用以下命令生成產品服務:

nest generate service product/service/product

產品服務

現在在product模塊中打開上述命令生成的product.service.ts文件,添加如下代碼片段:

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { ProductEntity } from '../product.entity';
import { Repository, UpdateResult, DeleteResult } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class ProductsService {
   constructor(@InjectRepository(ProductEntity) private productRepository: Repository<ProductEntity>) { }
  
   async getAll(): Promise<ProductEntity[]> {
       return await this.productRepository.find()
   }

   async create(product: ProductEntity, user: Users): Promise<ProductEntity> {
       if (user.role == 'admin') {
           return await this.productRepository.save(product);

       }
       throw new UnauthorizedException();

   }

   async getOne(id: number): Promise<ProductEntity> {
       return this.productRepository.findOne(id);
   }

   async update(id: number, product: ProductEntity, user: Users): Promise<UpdateResult> {
       if (user.role == 'admin') {
           return await this.productRepository.update(id, product);
       }
       throw new UnauthorizedException();
   }

   async delete(id: number, user: Users): Promise<DeleteResult> {
       if (user.role == 'admin') {
           return await this.productRepository.delete(id);
       }
       throw new UnauthorizedException();
   }
  
}

創建 CRUD 服務

在上面的代碼片段中,我們創建了 CRUD 服務。createupdatedelete方法對用戶是有限制的。只有管​​理員可以創建產品、刪除或更新產品。

現在打開 product.module.ts 文件並使用以下代碼片段使產品實體可訪問:

imports: [TypeOrmModule.forFeature([ProductEntity])],

使產品實體可訪問

創建購物車服務

此時,管理員可以將產品添加到數據庫中,並且經過身份驗證的用戶可以看到所有可用的產品。現在讓我們允許用戶將他們喜歡的商品添加到購物車中。首先,使用以下命令生成購物車服務:

nest generate service cart/service/cart –flat

製作購物車服務

然後打開該命令生成的cart.service.ts文件,添加如下代碼片段:

import { Injectable } from '@nestjs/common';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartEntity } from '../cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class CartService {
   constructor(
       @InjectRepository(CartEntity)
       private cartRepository: Repository<CartEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private productsService: ProductsService,
   ) { }
   …

購物車服務文件

您在上面的代碼片段中導入了創建 Nest.js 服務所需的模塊。由於您在購物車、用戶和產品實體之間創建了關係,因此我們也在此處導入了它們。然後,您創建一個構造方法來將這些實體綁定到 CartService 類。現在創建一個將商品添加到購物車的方法。

async addToCart(productId: number, quantity: number, user: string): Promise<any> {
       const cartItems = await this.cartRepository.find({ relations: ["item",'user'] });
       const product = await this.productsService.getOne(productId);
       const authUser = await this.userRepository.findOne({ username: user })
      
       //Confirm the product exists.
       if (product) {
           //confirm if user has item in cart
           const cart = cartItems.filter(
               (item) => item.item.id === productId && item.user.username === user,
           );
           if (cart.length < 1) {

               const newItem = this.cartRepository.create({ total: product.price * quantity, quantity });
               newItem.user = authUser;
               newItem.item = product;
               this.cartRepository.save(newItem)


               return await this.cartRepository.save(newItem)
           } else {
               //Update the item quantity
               const quantity = (cart[0].quantity += 1);
               const total = cart[0].total * quantity;

               return await this.cartRepository.update(cart[0].id, { quantity, total });
           }
       }
       return null;
   }

將商品添加到購物車

在上面的代碼片段中,您創建了一個addToCart方法,該方法將productId數量和用戶作為參數。然後檢查用戶的購物車中是否已有該商品。如果是這樣,您增加數量並更新該項目的總價。否則,您將商品添加到用戶的購物車。

接下來,通過使用以下代碼片段在cart.module.ts文件中註冊cartEntityproductEntity 用戶實體和productService ,使它們可以在cartService中訪問:

…
import { CartEntity } from './cart.entity';
import { ProductsService } from 'src/product/service/products.service';
import { ProductEntity } from 'src/product/product.entity';
import { Users } from 'src/auth/user.entity';

@Module({
 imports: [TypeOrmModule.forFeature([CartEntity, ProductEntity, Users])],
 providers: [CartService, ProductsService],
 …
})

註冊服務

最後,創建一個 getItemsInCart 方法,該方法將用戶作為參數返回屬於特定用戶的所有購物車。

async getItemsInCard(user: string): Promise<CartEntity[]> {
       const userCart = await this.cartRepository.find({ relations: ["item",'user'] });
       return (await userCart).filter(item => item.user.username === user)
   }

獲取購物車中的物品 

創建訂單服務

當用戶完成購物後,他們可以訂購購物車中的商品。使用以下命令生成訂單服務:

nest generate service order/service/order –flat

創建訂單服務

現在打開運行上述命令生成的 order.service.ts 文件並添加以下代碼片段:

import { OrderEntity } from '../order.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { CartService } from 'src/cart/service/cart.service';
import { Users } from 'src/auth/user.entity';

@Injectable()
export class OrderService {
   constructor(@InjectRepository(OrderEntity)
   private orderRepository: Repository<OrderEntity>,
       @InjectRepository(Users)
       private userRepository: Repository<Users>,
       private cartService: CartService) { }

訂單服務

您在上面的代碼片段中導入了創建 Nest.js 服務所需的模塊。由於您在購物車、用戶和產品實體之間創建了關係,因此我們也在此處導入了它們。然後,您創建了一個構造方法來將這些實體綁定到 OrderService 類。現在創建一個方法來訂購用戶購物車中的物品。

…
async order(user: string): Promise<any> {
       //find user existing orders
       const usersOrder = await this.orderRepository.find({ relations: ['user'] });
       const userOrder = usersOrder.filter(order => order.user?.username === user && order.pending === false);
       //find user's cart items
       const cartItems = await this.cartService.getItemsInCard(user)
       const subTotal = cartItems.map(item => item.total).reduce((acc, next) => acc + next);
       //get the authenticated user
       const authUser = await this.userRepository.findOne({ username: user })
       //if users has an pending order - add item to the list of order
       const cart = await cartItems.map(item => item.item);

       if (userOrder.length === 0) {
           const newOrder = await this.orderRepository.create({ subTotal });
           newOrder.items = cart
           newOrder.user = authUser;
           return await this.orderRepository.save(newOrder);


       } else {
           const existingOrder = userOrder.map(item => item)
           await this.orderRepository.update(existingOrder[0].id, { subTotal: existingOrder[0].subTotal + cart[0].price });
           return { message: "order modified" }
       }
   }
…

訂購用戶購物車中的商品

然後使用下面的代碼片段創建另一個方法來從數據庫的訂單數組中獲取用戶的訂單:

…
async getOrders(user: string): Promise<OrderEntity[]> {
       const orders = await this.orderRepository.find({ relations: ['user'] });
       return orders.filter(order => order.user?.username === user)
   }
}

獲取用戶訂單

最後,打開 order.module.ts 文件並使用以下代碼片段在 orderService 中訪問用戶、產品和購物車實體:

import { TypeOrmModule } from '@nestjs/typeorm';
import { OrderEntity } from './order.entity';
import { ProductEntity } from 'src/product/product.entity';
import { CartService } from 'src/cart/service/cart.service';
import { CartEntity } from 'src/cart/cart.entity';
import { Users } from 'src/auth/user.entity';
import { ProductsService } from 'src/product/service/products.service';

@Module({
 imports: [TypeOrmModule.forFeature([OrderEntity, ProductEntity, CartEntity, Users])],
 controllers: [OrderController],
 providers: [OrderService, CartService, ProductsService]
})

使實體可訪問

創建應用控制器

成功創建應用服務後,讓我們為應用服務創建 API 路由。

創建身份驗證控制器

使用以下命令生成身份驗證控制器:

nest generate controller auth/controller/auth –flat

現在打開運行上述命令生成的auth.controller.ts文件,並使用以下代碼段配置身份驗證路由:

import { Controller, Request, Post, UseGuards, Body } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { AuthService } from '../service/auth.service';
import { Users } from '../user.entity';

@Controller('api/v1/auth/')
export class AuthController {
   constructor(private usersService: AuthService) { }
  
   @Post('signup')
   async signup(@Body() user: Users): Promise<Users> {
       return this.usersService.signup(user);
   }

   @UseGuards(AuthGuard('local'))
   @Post('login')
   async login(@Request() req) {
       return this.usersService.login(req.user)
   }
}

配置認證路由

創建產品控制器

配置 auth 控制器路由後,使用以下命令生成產品控制器:

nest generate controller product/controller/product –flat

產品控制器

打開運行上述命令生成的 product.controller.ts 文件,並使用以下代碼段配置產品路由:

import { Controller, Post, Get, Put, Delete, Param, Request, Body, UseGuards } from '@nestjs/common';
import {UpdateResult, DeleteResult} from 'typeorm';
import { ProductsService } from '../service/products.service';
import { ProductEntity } from '../product.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/products')
export class ProductsController {
 constructor(private productsService: ProductsService) { }

 @UseGuards(JwtAuthGuard)
 @Get()
 async GetAll(): Promise<ProductEntity[]> {
   return await this.productsService.getAll();

 }

 @UseGuards(JwtAuthGuard)
 @Post()
 async Create(@Request() req, @Body() product: ProductEntity): Promise<ProductEntity> {
   return await this.productsService.create(product, req.user);
 }


 @UseGuards(JwtAuthGuard)
 @Get(':id')
 async GetOne(@Param() id: number): Promise<ProductEntity> {
   return await this.productsService.getOne(id);

 }

 @UseGuards(JwtAuthGuard)
 @Put(':id')
 async Update(@Param() id: number, @Body() product: ProductEntity, @Request() req): Promise<UpdateResult> {
   return await this.productsService.update(id, product, req.user);

 }

 @UseGuards(JwtAuthGuard)
 @Delete(':id')
 async Delete(@Param() id: number, @Request() req): Promise<DeleteResult> {
   return await this.productsService.delete(id, req.user);

 }
}

產品路線

在上面的代碼片段中,您為產品服務定義了 CRUD 路由。我們使用UseGuard裝飾器傳遞您的JwtAuthGaurd來保護路由免受未經授權的用戶的攻擊。

創建購物車控制器

現在使用以下命令生成一個購物車控制器:

nest generate controller cart/controller/cart –flat

推車控制器

然後打開運行上述命令生成的cart.controller.ts文件,並使用以下代碼段配置購物車路線:

import { Controller, Post, Get,Request, Delete, Body, UseGuards } from '@nestjs/common';
import { CartService } from '../service/cart.service';
import { CartEntity } from '../cart.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/cart')
export class CartController {
   constructor(private cartService: CartService) { }

   @UseGuards(JwtAuthGuard)
   @Post()
   async AddToCart(@Body() body, @Request() req): Promise<void> {
       const { productId, quantity } = body
       return await this.cartService.addToCart(productId, quantity, req.user.username);
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getItemsInCart(@Request() req): Promise<CartEntity[]> {
       return await this.cartService.getItemsInCard(req.user.username);

   }
}

購物車路線

創建訂單控制器

配置購物車路線後,使用以下命令創建訂單控制器:

nest generate controller order/controller/order –flat

訂單控制器

然後打開運行上述命令生成的order.controlle.ts文件,並使用以下代碼段配置購物車路線:

import { Controller, Post, Get, Request, UseGuards } from '@nestjs/common';
import { OrderService } from '../service/order.service'
import { OrderEntity } from '../order.entity';
import { JwtAuthGuard } from 'src/auth/jwt-auth.guard';

@Controller('api/v1/order')
export class OrderController {
   constructor(private orderService: OrderService) { }


   @UseGuards(JwtAuthGuard)
   @Post()
   async order(@Request() req): Promise<any> {
       return this.orderService.order(req.user.username)
   }

   @UseGuards(JwtAuthGuard)
   @Get()
   async getOrders(@Request() req): Promise<OrderEntity[]> {
       return await this.orderService.getOrders(req.user.username)
   }
}

訂購路線

至此,您的所有 API 路由都已配置完畢。

測試申請

現在讓我們使用 Postman 測試它們。come 測試的結果如下所示。

用戶路線登錄路線產品路線。在添加產品之前複製訪問令牌並將其添加到請求標頭中。

隨意測試其他路線並在 Github 上使用代碼

將 Arctype 連接到 SQLite 數據庫

Arctype是一個使用起來很有趣的 SQL 客戶端和數據庫管理工具。它允許您擁有數據庫表的可視化表示,並且您可以使用 Arctype 在數據庫中執行 CRUD 操作目錄。將 Arctype 連接到 SQLite 數據庫是一個簡單的過程。要開始,請按照以下步驟操作:

首先,啟動 Arctype。您將看到如下所示的屏幕,允許您添加憑據。

單擊 SQLite 選項卡。這將打開下面的屏幕。

單擊選擇 SQLite 文件按鈕並導航到您的項目文件夾。選擇應用程序數據庫文件並按打開按鈕。按保存按鈕,您應該會看到您的數據庫實體,如下面的屏幕截圖所示。

結論

通過構建一個演示項目,我們學習瞭如何使用 Nestjs 和 SQLite 數據庫製作電子商務應用程序。首先,我們從 NestJS 的介紹開始。然後,我們創建了一個 NestJS 應用程序,使用 TypeORM 將應用程序連接到 SQLite 數據庫,並執行 CRUD 操作。現在您已經獲得了您所尋求的知識,可以隨意向應用程序添加額外的功能。

鏈接:https ://arctype.com/blog/sqlite-nestjs-tutorial/

#nestjs  #typeorm #sqlite 

An API-First Approach For Designing Restful APIs | Hacker Noon

I’ve been working with Restful APIs for some time now and one thing that I love to do is to talk about APIs.

So, today I will show you how to build an API using the API-First approach and Design First with OpenAPI Specification.

First thing first, if you don’t know what’s an API-First approach means, it would be nice you stop reading this and check the blog post that I wrote to the Farfetchs blog where I explain everything that you need to know to start an API using API-First.

Preparing the ground

Before you get your hands dirty, let’s prepare the ground and understand the use case that will be developed.

Tools

If you desire to reproduce the examples that will be shown here, you will need some of those items below.

  • NodeJS
  • OpenAPI Specification
  • Text Editor (I’ll use VSCode)
  • Command Line

Use Case

To keep easy to understand, let’s use the Todo List App, it is a very common concept beyond the software development community.

#api #rest-api #openai #api-first-development #api-design #apis #restful-apis #restful-api

Marcelle  Smith

Marcelle Smith

1598083582

What Are Good Traits That Make Great API Product Managers

As more companies realize the benefits of an API-first mindset and treating their APIs as products, there is a growing need for good API product management practices to make a company’s API strategy a reality. However, API product management is a relatively new field with little established knowledge on what is API product management and what a PM should be doing to ensure their API platform is successful.

Many of the current practices of API product management have carried over from other products and platforms like web and mobile, but API products have their own unique set of challenges due to the way they are marketed and used by customers. While it would be rare for a consumer mobile app to have detailed developer docs and a developer relations team, you’ll find these items common among API product-focused companies. A second unique challenge is that APIs are very developer-centric and many times API PMs are engineers themselves. Yet, this can cause an API or developer program to lose empathy for what their customers actually want if good processes are not in place. Just because you’re an engineer, don’t assume your customers will want the same features and use cases that you want.

This guide lays out what is API product management and some of the things you should be doing to be a good product manager.

#api #analytics #apis #product management #api best practices #api platform #api adoption #product managers #api product #api metrics