API with NestJS. Creating relationships with Postgres and TypeORM

When we build an application, we create many entities. They often somehow relate to each other, and defining such relationships is an essential part of designing a database. In this article, we go through what is a relationship in the context of a Postgres database and how do we work with them using TypeORM and NestJS.

The relational databases have been around for quite some time and work great with structured data. They do so by organizing the data into tables and linking them to each other. When running various SQL queries, we can join the tables and extract meaningful information. There are a few different types of relationships, and today we go through them with the use of examples.

#javascript #nestjs #typescript

What is GEEK

Buddha Community

API with NestJS. Creating relationships with Postgres and TypeORM
Easter  Deckow

Easter Deckow

1655630160

PyTumblr: A Python Tumblr API v2 Client

PyTumblr

Installation

Install via pip:

$ pip install pytumblr

Install from source:

$ git clone https://github.com/tumblr/pytumblr.git
$ cd pytumblr
$ python setup.py install

Usage

Create a client

A pytumblr.TumblrRestClient is the object you'll make all of your calls to the Tumblr API through. Creating one is this easy:

client = pytumblr.TumblrRestClient(
    '<consumer_key>',
    '<consumer_secret>',
    '<oauth_token>',
    '<oauth_secret>',
)

client.info() # Grabs the current user information

Two easy ways to get your credentials to are:

  1. The built-in interactive_console.py tool (if you already have a consumer key & secret)
  2. The Tumblr API console at https://api.tumblr.com/console
  3. Get sample login code at https://api.tumblr.com/console/calls/user/info

Supported Methods

User Methods

client.info() # get information about the authenticating user
client.dashboard() # get the dashboard for the authenticating user
client.likes() # get the likes for the authenticating user
client.following() # get the blogs followed by the authenticating user

client.follow('codingjester.tumblr.com') # follow a blog
client.unfollow('codingjester.tumblr.com') # unfollow a blog

client.like(id, reblogkey) # like a post
client.unlike(id, reblogkey) # unlike a post

Blog Methods

client.blog_info(blogName) # get information about a blog
client.posts(blogName, **params) # get posts for a blog
client.avatar(blogName) # get the avatar for a blog
client.blog_likes(blogName) # get the likes on a blog
client.followers(blogName) # get the followers of a blog
client.blog_following(blogName) # get the publicly exposed blogs that [blogName] follows
client.queue(blogName) # get the queue for a given blog
client.submission(blogName) # get the submissions for a given blog

Post Methods

Creating posts

PyTumblr lets you create all of the various types that Tumblr supports. When using these types there are a few defaults that are able to be used with any post type.

The default supported types are described below.

  • state - a string, the state of the post. Supported types are published, draft, queue, private
  • tags - a list, a list of strings that you want tagged on the post. eg: ["testing", "magic", "1"]
  • tweet - a string, the string of the customized tweet you want. eg: "Man I love my mega awesome post!"
  • date - a string, the customized GMT that you want
  • format - a string, the format that your post is in. Support types are html or markdown
  • slug - a string, the slug for the url of the post you want

We'll show examples throughout of these default examples while showcasing all the specific post types.

Creating a photo post

Creating a photo post supports a bunch of different options plus the described default options * caption - a string, the user supplied caption * link - a string, the "click-through" url for the photo * source - a string, the url for the photo you want to use (use this or the data parameter) * data - a list or string, a list of filepaths or a single file path for multipart file upload

#Creates a photo post using a source URL
client.create_photo(blogName, state="published", tags=["testing", "ok"],
                    source="https://68.media.tumblr.com/b965fbb2e501610a29d80ffb6fb3e1ad/tumblr_n55vdeTse11rn1906o1_500.jpg")

#Creates a photo post using a local filepath
client.create_photo(blogName, state="queue", tags=["testing", "ok"],
                    tweet="Woah this is an incredible sweet post [URL]",
                    data="/Users/johnb/path/to/my/image.jpg")

#Creates a photoset post using several local filepaths
client.create_photo(blogName, state="draft", tags=["jb is cool"], format="markdown",
                    data=["/Users/johnb/path/to/my/image.jpg", "/Users/johnb/Pictures/kittens.jpg"],
                    caption="## Mega sweet kittens")

Creating a text post

Creating a text post supports the same options as default and just a two other parameters * title - a string, the optional title for the post. Supports markdown or html * body - a string, the body of the of the post. Supports markdown or html

#Creating a text post
client.create_text(blogName, state="published", slug="testing-text-posts", title="Testing", body="testing1 2 3 4")

Creating a quote post

Creating a quote post supports the same options as default and two other parameter * quote - a string, the full text of the qote. Supports markdown or html * source - a string, the cited source. HTML supported

#Creating a quote post
client.create_quote(blogName, state="queue", quote="I am the Walrus", source="Ringo")

Creating a link post

  • title - a string, the title of post that you want. Supports HTML entities.
  • url - a string, the url that you want to create a link post for.
  • description - a string, the desciption of the link that you have
#Create a link post
client.create_link(blogName, title="I like to search things, you should too.", url="https://duckduckgo.com",
                   description="Search is pretty cool when a duck does it.")

Creating a chat post

Creating a chat post supports the same options as default and two other parameters * title - a string, the title of the chat post * conversation - a string, the text of the conversation/chat, with diablog labels (no html)

#Create a chat post
chat = """John: Testing can be fun!
Renee: Testing is tedious and so are you.
John: Aw.
"""
client.create_chat(blogName, title="Renee just doesn't understand.", conversation=chat, tags=["renee", "testing"])

Creating an audio post

Creating an audio post allows for all default options and a has 3 other parameters. The only thing to keep in mind while dealing with audio posts is to make sure that you use the external_url parameter or data. You cannot use both at the same time. * caption - a string, the caption for your post * external_url - a string, the url of the site that hosts the audio file * data - a string, the filepath of the audio file you want to upload to Tumblr

#Creating an audio file
client.create_audio(blogName, caption="Rock out.", data="/Users/johnb/Music/my/new/sweet/album.mp3")

#lets use soundcloud!
client.create_audio(blogName, caption="Mega rock out.", external_url="https://soundcloud.com/skrillex/sets/recess")

Creating a video post

Creating a video post allows for all default options and has three other options. Like the other post types, it has some restrictions. You cannot use the embed and data parameters at the same time. * caption - a string, the caption for your post * embed - a string, the HTML embed code for the video * data - a string, the path of the file you want to upload

#Creating an upload from YouTube
client.create_video(blogName, caption="Jon Snow. Mega ridiculous sword.",
                    embed="http://www.youtube.com/watch?v=40pUYLacrj4")

#Creating a video post from local file
client.create_video(blogName, caption="testing", data="/Users/johnb/testing/ok/blah.mov")

Editing a post

Updating a post requires you knowing what type a post you're updating. You'll be able to supply to the post any of the options given above for updates.

client.edit_post(blogName, id=post_id, type="text", title="Updated")
client.edit_post(blogName, id=post_id, type="photo", data="/Users/johnb/mega/awesome.jpg")

Reblogging a Post

Reblogging a post just requires knowing the post id and the reblog key, which is supplied in the JSON of any post object.

client.reblog(blogName, id=125356, reblog_key="reblog_key")

Deleting a post

Deleting just requires that you own the post and have the post id

client.delete_post(blogName, 123456) # Deletes your post :(

A note on tags: When passing tags, as params, please pass them as a list (not a comma-separated string):

client.create_text(blogName, tags=['hello', 'world'], ...)

Getting notes for a post

In order to get the notes for a post, you need to have the post id and the blog that it is on.

data = client.notes(blogName, id='123456')

The results include a timestamp you can use to make future calls.

data = client.notes(blogName, id='123456', before_timestamp=data["_links"]["next"]["query_params"]["before_timestamp"])

Tagged Methods

# get posts with a given tag
client.tagged(tag, **params)

Using the interactive console

This client comes with a nice interactive console to run you through the OAuth process, grab your tokens (and store them for future use).

You'll need pyyaml installed to run it, but then it's just:

$ python interactive-console.py

and away you go! Tokens are stored in ~/.tumblr and are also shared by other Tumblr API clients like the Ruby client.

Running tests

The tests (and coverage reports) are run with nose, like this:

python setup.py test

Author: tumblr
Source Code: https://github.com/tumblr/pytumblr
License: Apache-2.0 license

#python #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 

August  Larson

August Larson

1657644540

Building an E-Commerce API using Nestjs, SQLite, and TypeORM

Introduction

Nestjs is a cutting-edge Node.js framework for developing server-side applications that are efficient, dependable, and scalable. It is simple to integrate with NoSQL and SQL databases such as MongoDB, Yugabyte, SQLite, Postgres, MySQL, and others. It supports popular object-relational mappers such as TypeORM Sequelize and Mongoose.

In this tutorial, we'll create an e-commerce application with SQLite and TypeORM. We'll also look at Arctype, a powerful SQL client and database management tool.

Let's get started!

Prerequisites

This tutorial is a hands-on demonstration to get you started. Ensure you've met the following requirements:

  • You have Node (>= 10.13.0, except for v13) installed
  • You have a foundational knowledge of Javascript
  • Arctype installed

Project setup

To get started with Nestjs, install the Nestjs CLI with the command below:

npm i -g @nestjs/cli

Installing the NestJS CLI

Once the installation is completed, create a Nestjs project with the command below:

nest new ecommerce

Creating a new project

Choose your npm as the package manager, hit the enter button, and wait for Nest to install the required packages to run this application.

Once the installation is completed, change the directory to the project folder with the command below:

cd ecommerce

Change the directory

Then open the project directory in your favorite text editor or IDE, open a new terminal, and run the server on development mode (This will enable hot reload and allows us to see possible errors on the console) with the command below:

npm run start:dev

Starting the server

Install Dependencies

With the server up and running, open a new terminal window so you don't quit the server. This will allow you to see the effect of the changes made to the codebase throughout this tutorial.

Now install the following dependencies:

You can do this with the command below:

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

Installing dependencies

Then install the dev dependencies with the command below:

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

Installing dev dependencies

You can grab a cup of coffee while the npm installs the packages. Once the installation is completed, let's get our hands dirty.

Create App Modules

With all the necessary packages required to run the application, let's proceed to create the application modules. To build a clean and easily-maintained application, you'll generate separate modules for all the features implemented in this application. Since this is an eCommerce application, you'll have authentication, cart, products, and orders. All these will be in their own separate modules. Let's start with the authentication module.

Create Authentication Module

Generate an authentication module with the command below:

nest g module auth

The authentication module

The above command creates an auth folder in the project's src directory with the necessary boilerplates and registers the module in the project root module(app.module.ts file).

Next, create a product, cart, order, module with the command below:

#Create a product module
nest g module product

#Create cart module
nest g module cart

#Create cart module
nest g module order

Additional modules

The above will create a product, cart, and order folder in the project's src folder with the basic boilerplates and registers these modules in the project's root app module.

Setting up TypeORM and SQLite Databases

With the app modules installed, set up TypeORM to connect your application to SQLite Database and create your module entities. To get started, open the app.module.ts and configure your SQLite database with the code snippets below:

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

Setting up TypeORM

In the above code snippet, you connected the application to an SQLite database using the TypeORM forRoot, specifying the database type, database name, and the location where Nestjs can find the model entities.

Once the server refreshes, you should see a shoppingDB file created in the root directory of this project.

Create App Entity Models

With the database setup, let's create the entity models for our app modules. We'll start with the auth module. Generate an entity file in the auth module folder with the command below:

nest generate class auth/user.entity –flat

Create the entity file

Then add the code snippet below to define the user table properties with the code snippet below:

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

Defining properties

In the code snippet, you imported the decorators required to set up your database table. You also imported the cartEntity and orderEntity class which you'll create shortly. Using the typeorm decorator, we defined the database properties of the user's model. Finally, we created one-to-one and one-to-many relationships between the users entity and the cartEntity and orderEntity. This way, you can associate a cart item with a user. The same applies to the user's order.

Next, create the product entity class with the command below:

nest generate class product/product.entity –flat

Creating the product entity class

The above command will generate a product.entity.ts file in the products module folder.

Now configure the product table properties with the code snippet below:

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[]
}

Defining the properties

In the above code snippet, we configured the properties of the product table and created a one-to-many relationship with the cart entity.

Then create the cart entity with the command below:

nest generate class cart/cart.entity –flat

The cart entity

The above command will generate a cart.entity.ts file in the cart module folder. Now add the code snippet below to the file you created to configure the cart table properties.

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
}

Defining properties

In the above code snippet, you configured the properties of the cart table, created a many-to-one relationship between the cart entity and a many-to-one relationship with the user's entity.

Lastly, create the order entity with the command below:

nest generate class order/order.entity –flat

The order entity

The above command will generate a order.entity.ts file in the order module folder. Open the order.entity.ts and configure the database table with the command below:

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

}

Defining properties

In the above code snippet, you created a one-to-one relationship between the users entity and a one-to-many relationship with the products entity.

At this point, your database entities are set and connected. Now create your business logic to store records on these entities.

Create App Services

Now create the services for the modules in this application. Here you'll allow the admin to add products to the product table, authenticate users, allow users to add the products in the store to the cart, and order the product via their cart.

Create the Auth Service

To create the auth service, run the command below to generate the service for the auth module.

nest generate service auth/service/auth --flat

Creating the auth service

The above command will generate an auth.service.ts file in the src/auth/service folder. Now open the auth.service.ts file and add the code snippet below:

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) { }

   }

Importing modules

In the above code snippet, you imported InjectRepository, Repository decorator, JwtService, and bcrypt modules. Then, using the InjectRepository decorator, you made the User entity class available in the auth service, providing the method to perform CRUD operations in your User entity.

Then create a signup method to allow users to register in the application with the code snippet below:

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

The signup method

Now create the validateUser method to validate the users' details and the login method to generate a jwt token for the authenticated user.

 …
 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),
       };
   }

Validating the user

Now we can implement our Passport local authentication strategy. Create a file called local.strategy.ts in the auth module folder, and add the following code:

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

Local auth strategy

In the above code snippet, you've implemented a passport-local strategy. There are no configuration options, so our constructor simply calls super() without an options object.

You've also implemented the validate() method. Passport will call the verify function for each strategy using an appropriate strategy-specific set of parameters. For the local-strategy, Passport expects a validate() method with the following signature: validate(username: string, password:string): any.

Then create a jwt-auth.guard.ts file in the auth module folder and define a custom auth guard with the code snippet below:

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

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

Defining a custom auth guard

You'll use the AuthGuard created in the code snippet to protect your API routes from unauthorized users.

Now create a jwt-strategy file in the auth module folder to authenticate users and generate jwt tokens for logged in users with the code snippet below:

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

The strategy file

Then configure the jwt module in the auth.module.ts file in the auth module folder. Before that, create a constants.ts file in the same auth module folder to define a jwt secret with the  code snippet below:

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

The secret (used for testing purposes)

You can generate a more secured jwt secret on production, but we'll use the one for demonstration sake.

Now import all the required modules in your auth.module.ts file with the code snippet below:

…
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';
…

Importing modules

Then in the array of imports, configure jwt with the code snippet below:

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

Configuring jwt

In the above code snippet, we add the PassModule package to allow passport to handle the users' authentication and configured jwt using the JwtModule register method. We pass in the secret we created in the constants file and specify the generated token's expiration time (You can reduce or increase the time depending on the use case).

Create the Product Service

With the auth service setup, generate a product service with the command below:

nest generate service product/service/product

The product service

Now open the product.service.ts file generated by the above command in the product module and add the code snippets below:

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

Creating the CRUD services

In the above snippet, we created our CRUD services. The create, update, and delete methods are restricted from the users. Only the admin can create a product, delete or update a product.

Now open the product.module.ts file and make the product entity accessible with the code snippet below:

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

Making the product entity accessible

Create the Cart Service

At this point, the admin can add products to the database, and authenticated users can see all available products. Now let's allow the users to add the items they like to the cart. To get started, generate a Cart service with the command below:

nest generate service cart/service/cart –flat

Making the cart service

Then open the cart.service.ts file generated by the command and add the code snippet below:

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,
   ) { }
   …

The cart service file

You imported the modules required to create a Nest.js service in the above code snippet. We also imported them here since you created a relationship between the cart, user, and product entities. Then, you make a constructor method to bind these entities to the CartService class. Now create a method to add an item to the cart.

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

Adding an item to the cart

In the above code snippet, you created an addToCart method that takes the productId, quantity, and user as arguments. Then check if the user has the item already in their cart. If so, you increment the quantity and update the total price of that item. Else, you add the item to the user's cart.

Next, make the cartEntity, productEntity User entity, and the productService accessible in the cartService by registering them in the cart.module.ts file with the code snippet below:

…
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],
 …
})

Registering the services

Lastly, create a getItemsInCart method that takes a user as an argument to return all the carts belonging to a particular user.

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

Getting items in the cart 

Create the Order Service

When users are done shopping, they can order the items in their cart. Generate an order service with the command below:

nest generate service order/service/order –flat

Creating the order service

Now open the order.service.ts file generated from running the above command and add the code snippet below:

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) { }

The order service

You imported the modules required to create a Nest.js service in the above code snippet. We also imported them here since you created a relationship between the cart, user, and product entities. Then, you created a constructor method to bind these entities to the OrderService class. Now create a method to order the items in the user's cart.

…
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" }
       }
   }
…

Ordering the items in the user's cart

Then create another method to get a user's order from the array of orders from the database with the code snippet below:

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

Getting a user's orders

Lastly, open the order.module.ts file and make the users, product, and cart entities accessible in the orderService with the code snippet below:

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]
})

Making the entities accessible

Create App Controllers

With the application services successfully created, let's create the API routes for the app services.

Create the Auth Controller

Generate an auth controller with the command below:

nest generate controller auth/controller/auth –flat

Now open the auth.controller.ts file generated from running the above command and configure the auth routes with the code snippet below:

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

Configuring the auth routes

Create the Product Controller

With the auth controller routes configured, generate a product controller with the command below:

nest generate controller product/controller/product –flat

The product controller

Open the product.controller.ts file generated from running the above command and configure the product routes with the code snippet below:

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

 }
}

Product routes

In the above code snippet, you defined the CRUD routes for the product service. We used the UseGuard decorator passing your JwtAuthGaurd to protect the routes from unauthorized users.

Create the Cart Controller

Now generate a cart controller with the command below:

nest generate controller cart/controller/cart –flat

The cart controller

Then open the cart.controller.ts file generated from running the above command and configure the cart routes with the code snippet below:

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

   }
}

Cart routes

Create the Order Controller

With the cart routes configured, create an order controller with the command below:

nest generate controller order/controller/order –flat

Order controller

Then open the order.controlle.ts file generated from running the above command and configure the cart routes with the code snippet below:

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

Order routes

At this point, all your API routes have been configured.

Test Application

Now let's test them out using Postman. The results of come tests are shown below.

User routeLogin routeProduct route. Copy the access token and add it to the request header before adding a product.

Feel free to test the other routes and play around with the code here on Github.

Connecting Arctype to SQLite Database

Arctype is an SQL client and database management tool that's fun to use. It allows you to have a visual representation of your database tables, and you can perform the CRUD operations directory in your database using Arctype. Connecting Arctype to an SQLite database is a straightforward process. To get started, follow the steps below:

First, launch Arctype. You'll see a screen like the one below, allowing you to add your credentials.

Click on the SQLite tab. This will bring up the screen below.

Click on the Choose SQLite File button and navigate to your project folder. Select the application database file and press the open button. Press the save button, and you should see your database entities, as shown in the screenshot below.

Conclusion

By building a demo project, we've learned how to make an e-commerce application using Nestjs and SQLite Database. First, we started with the introduction of NestJS. Then, we created a NestJS application, connected the application to an SQLite database using TypeORM, and performed CRUD operations. Now that you've gotten the knowledge you seek feel free to add extra functionality to the application.

Link: https://arctype.com/blog/sqlite-nestjs-tutorial/

#nestjs  #typeorm #sqlite