Node.js和TypeScript教程:使用Typescript、NodeJS和基于文件的存儲(chǔ)系統(tǒng)構(gòu)建REST API
歡迎到我的博客!在本教程中,我將指導(dǎo)您完成使用 Node.js、Express 和 TypeScript 構(gòu)建強(qiáng)大的微型電子商務(wù) API 的過(guò)程。我們將共同探索各種功能和技術(shù),使您能夠?yàn)殡娮由虅?wù)應(yīng)用程序創(chuàng)建強(qiáng)大的 API。
我們?cè)谶@個(gè)項(xiàng)目中的關(guān)鍵決策之一是實(shí)現(xiàn)基于文件的存儲(chǔ)系統(tǒng),而不是依賴 MongoDB 等傳統(tǒng)數(shù)據(jù)庫(kù)。這種方法簡(jiǎn)單且易于實(shí)施,非常適合小型應(yīng)用程序或可能不需要成熟的數(shù)據(jù)庫(kù)管理系統(tǒng)的場(chǎng)景。
本教程將涵蓋用戶管理、產(chǎn)品處理和身份驗(yàn)證等基本主題。
(更|多優(yōu)質(zhì)內(nèi)|容:java567 點(diǎn) c0m)
您將獲得使用涵蓋用戶和產(chǎn)品數(shù)據(jù)的功能的實(shí)踐經(jīng)驗(yàn),演示這些實(shí)體如何在電子商務(wù) API 中進(jìn)行交互。在本教程結(jié)束時(shí),您將全面了解如何構(gòu)建強(qiáng)大的 API,以實(shí)現(xiàn)與用戶和產(chǎn)品資源的無(wú)縫交互。
因此,請(qǐng)與我一起踏上這個(gè)激動(dòng)人心的旅程,我們將深入研究使用 Node.js、Express 和 TypeScript 創(chuàng)建微型電子商務(wù) API。
在 Node.js 中開(kāi)始使用 TypeScript 首先創(chuàng)建一個(gè)如下所示的項(xiàng)目目錄。
接下來(lái),使用以下命令創(chuàng)建具有默認(rèn)設(shè)置的 package.json 文件,在項(xiàng)目目錄中初始化 Node.js 項(xiàng)目:
?npm init -y
安裝項(xiàng)目依賴項(xiàng)
您的 Node.js 項(xiàng)目需要幾個(gè)依賴項(xiàng)才能使用 TypeScript 創(chuàng)建安全的 Express 服務(wù)器。像這樣安裝它們:
npm i express dotenv helmet cors http-status-codes uuid bcryptjs 要使用 TypeScript,您還需要安裝穩(wěn)定版本的 TypeScript 作為開(kāi)發(fā)人員依賴項(xiàng):
?npm i -D typescript
要有效地使用 TypeScript,您需要為之前安裝的包安裝類型定義:
?npm i -D @types/express @types/dotenv @types/helmet @types/cors @types/http-status-codes @types/uuid @types/bcryptjs
使用以下變量填充 .env 隱藏文件,該變量定義服務(wù)器可用于偵聽(tīng)請(qǐng)求的端口:
?PORT=7000
接下來(lái),找到 src 文件夾根目錄中的 app.js 文件,導(dǎo)入之前安裝的項(xiàng)目依賴項(xiàng),并使用 dotenv.config() 方法從本地 .env 文件加載任何環(huán)境變量:
?import express from "express"
?import * as dotevnv from "dotenv"
?import cors from "cors"
?import helmet from "helmet"
?
?dotevnv.config()
?
?if (!process.env.PORT) {
? ? ?console.log(`No port value specified...`)
?}
?
?const PORT = parseInt(process.env.PORT as string, 10)
?
?const app = express()
?
?app.use(express.json())
?app.use(express.urlencoded({extended : true}))
?app.use(cors())
?app.use(helmet())
?
?app.listen(PORT, () => {
? ? ?console.log(`Server is listening on port ${PORT}`)
?})
在此代碼片段中,使用 Express 框架設(shè)置 Node.js 應(yīng)用程序。以下是所發(fā)生情況的詳細(xì)說(shuō)明:
導(dǎo)入所需的模塊:
Express被導(dǎo)入作為構(gòu)建 Web 應(yīng)用程序的主要框架。
導(dǎo)入dotenv來(lái)處理環(huán)境變量。
導(dǎo)入cors是為了實(shí)現(xiàn)跨源資源共享。
導(dǎo)入頭盔是為了向 HTTP 響應(yīng)添加安全標(biāo)頭。
該代碼檢查 PORT 環(huán)境變量是否已定義。如果沒(méi)有,一條消息會(huì)記錄到控制臺(tái)。
使用 parseInt() 將 PORT 變量從字符串解析為整數(shù)。
Express 應(yīng)用程序的實(shí)例是使用express() 創(chuàng)建的,并分配給app 變量。
Express應(yīng)用程序中添加了中間件功能:
express.json()用于解析傳入請(qǐng)求的 JSON 正文。
express.urlencoded({extended : true})用于解析傳入請(qǐng)求的 URL 編碼正文。
cors()用于啟用跨源資源共享。
頭盔()用于通過(guò)設(shè)置各種HTTP標(biāo)頭來(lái)增強(qiáng)應(yīng)用程序的安全性。
Express 應(yīng)用程序通過(guò)調(diào)用 app.listen() 開(kāi)始偵聽(tīng)指定的端口。服務(wù)器運(yùn)行后,一條指示端口號(hào)的消息將記錄到控制臺(tái)。
改進(jìn) TypeScript 開(kāi)發(fā)工作流程
TypeScript 編譯過(guò)程會(huì)增加應(yīng)用程序的引導(dǎo)時(shí)間。但是,只要源代碼發(fā)生更改,您就不需要重新編譯整個(gè)項(xiàng)目。您可以設(shè)置 ts-node-dev 以顯著減少進(jìn)行更改時(shí)重新啟動(dòng)應(yīng)用程序所需的時(shí)間。
首先安裝此軟件包以增強(qiáng)您的開(kāi)發(fā)工作流程:
?npm i -D ts-node-dev
當(dāng)任何所需文件發(fā)生更改時(shí),ts-node-dev 會(huì)重新啟動(dòng)目標(biāo) Node.js 進(jìn)程。但是,它在重新啟動(dòng)之間共享 Typescript 編譯過(guò)程,這可以顯著提高重新啟動(dòng)速度。
您可以在 package.json 中創(chuàng)建 dev npm 腳本來(lái)運(yùn)行服務(wù)器。像這樣更新你的 package.json 文件。
?{"name": "typescript-nodejs","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1","dev": "ts-node-dev --pretty --respawn ./src/app.ts"},"keywords": [],"author": "","license": "ISC","dependencies": {"@types/nanoid": "^3.0.0","@types/uuid": "^9.0.2","bcryptjs": "^2.4.3","cors": "^2.8.5","dotenv": "^16.3.0","express": "^4.18.2","helmet": "^7.0.0","http-status-codes": "^2.2.0","nanoid": "^4.0.2","uuid": "^9.0.0"},"devDependencies": {"@types/bcryptjs": "^2.4.2","@types/cors": "^2.8.13","@types/dotenv": "^8.2.0","@types/express": "^4.17.17","@types/helmet": "^4.0.0","@types/http-status-codes": "^1.2.0","ts-node-dev": "^2.0.0"}}
讓我們簡(jiǎn)單地分解一下 ts-node-dev 采用的選項(xiàng):
--respawn:腳本退出后繼續(xù)觀察變化。
--pretty:使用漂亮的診斷格式化程序(TS_NODE_PRETTY)。
./src/app.ts:這是應(yīng)用程序的入口文件。
現(xiàn)在,只需運(yùn)行開(kāi)發(fā)腳本即可啟動(dòng)您的項(xiàng)目:
?npm run dev
如果一切正常,您將看到一條消息,指示服務(wù)器正在偵聽(tīng)端口 7000 上的請(qǐng)求。
使用 TypeScript 接口對(duì)數(shù)據(jù)進(jìn)行建模
在創(chuàng)建任何路由之前,定義要管理的數(shù)據(jù)的結(jié)構(gòu)。我們的用戶數(shù)據(jù)庫(kù)將具有以下屬性:
id:(字符串)項(xiàng)目記錄的唯一標(biāo)識(shí)符。 用戶名:(字符串)項(xiàng)目的名稱。 電子郵件:(數(shù)字)商品價(jià)格(以美分為單位)。 密碼:(字符串)項(xiàng)目的描述。
使用以下定義填充 src/users/user.interface.ts:
?export interface User {
? ? ?username : string,
? ? ?email : string,
? ? ?password : string
?}
?
?export interface UnitUser extends User {
? ? ?id : string
?}
?
?export interface Users {
? ? ?[key : string] : UnitUser
?}
此代碼定義了三個(gè) TypeScript 接口:
用戶界面代表具有三個(gè)屬性的基本用戶對(duì)象:
username,這是表示用戶的用戶名的字符串。 email,它是表示用戶的電子郵件地址的字符串。 password,它是表示用戶密碼的字符串。
UnitUser 接口擴(kuò)展了 User 接口并添加了 id 屬性:
id,是一個(gè)字符串,代表用戶的唯一標(biāo)識(shí)符。
Users 接口表示具有動(dòng)態(tài)鍵的用戶對(duì)象的集合:
[key: string]表示Users對(duì)象的鍵可以是任意字符串。 Users 對(duì)象的值屬于 UnitUser 類型,這意味著集合中的每個(gè)用戶對(duì)象都應(yīng)符合 UnitUser 接口。 簡(jiǎn)單來(lái)說(shuō),這些接口定義了用戶對(duì)象的結(jié)構(gòu)和類型。User 接口定義了用戶的基本屬性,而 UnitUser 接口添加了 id 屬性來(lái)表示具有唯一標(biāo)識(shí)符的用戶。Users 接口表示用戶對(duì)象的集合,其中鍵是字符串,值是 UnitUser 對(duì)象。
接下來(lái),我們將為數(shù)據(jù)存儲(chǔ)創(chuàng)建邏輯。如果您愿意,您可以將其稱為數(shù)據(jù)庫(kù)。 使用以下代碼填充 src/users/user.database.ts:
?import { User, UnitUser, Users } from "./user.interface";
?import bcrypt from "bcryptjs"
?import {v4 as random} from "uuid"
?import fs from "fs"
?
?let users: Users = loadUsers()
?
?function loadUsers () : Users {
? ?try {
? ? ?const data = fs.readFileSync("./users.json", "utf-8")
? ? ?return JSON.parse(data)
? ?} catch (error) {
? ? ?console.log(`Error ${error}`)
? ? ?return {}
? ?}
?}
?
?function saveUsers () {
? ?try {
? ? ?fs.writeFileSync("./users.json", JSON.stringify(users), "utf-8")
? ? ?console.log(`User saved successfully!`)
? ?} catch (error) {
? ? ?console.log(`Error : ${error}`)
? ?}
?}
?
?export const findAll = async (): Promise<UnitUser[]> => Object.values(users);
?
?export const findOne = async (id: string): Promise<UnitUser> => users[id];
?
?export const create = async (userData: UnitUser): Promise<UnitUser | null> => {
?
? ?let id = random()
?
? ?let check_user = await findOne(id);
?
? ?while (check_user) {
? ? ?id = random()
? ? ?check_user = await findOne(id)
? ?}
?
? ?const salt = await bcrypt.genSalt(10);
?
? ?const hashedPassword = await bcrypt.hash(userData.password, salt);
?
? ?const user : UnitUser = {
? ? ?id : id,
? ? ?username : userData.username,
? ? ?email : userData.email,
? ? ?password: hashedPassword
? ?};
?
? ?users[id] = user;
?
? ?saveUsers()
?
? ?return user;
?};
?
?export const findByEmail = async (user_email: string): Promise<null | UnitUser> => {
?
? ?const allUsers = await findAll();
?
? ?const getUser = allUsers.find(result => user_email === result.email);
?
? ?if (!getUser) {
? ? ?return null;
? ?}
?
? ?return getUser;
?};
?
?export const comparePassword ?= async (email : string, supplied_password : string) : Promise<null | UnitUser> => {
?
? ? ?const user = await findByEmail(email)
?
? ? ?const decryptPassword = await bcrypt.compare(supplied_password, user!.password)
?
? ? ?if (!decryptPassword) {
? ? ? ? ?return null
? ? ?}
?
? ? ?return user
?}
?
?export const update = async (id : string, updateValues : User) : Promise<UnitUser | null> => {
?
? ? ?const userExists = await findOne(id)
?
? ? ?if (!userExists) {
? ? ? ? ?return null
? ? ?}
?
? ? ?if(updateValues.password) {
? ? ? ? ?const salt = await bcrypt.genSalt(10)
? ? ? ? ?const newPass = await bcrypt.hash(updateValues.password, salt)
?
? ? ? ? ?updateValues.password = newPass
? ? ?}
?
? ? ?users[id] = {
? ? ? ? ?...userExists,
? ? ? ? ?...updateValues
? ? ?}
?
? ? ?saveUsers()
?
? ? ?return users[id]
?}
?
?export const remove = async (id : string) : Promise<null | void> => {
?
? ? ?const user = await findOne(id)
?
? ? ?if (!user) {
? ? ? ? ?return null
? ? ?}
?
? ? ?delete users[id]
?
? ? ?saveUsers()
?}
讓我解釋一下上面代碼中的每個(gè)函數(shù):
loadUsers:此函數(shù)使用 fs 模塊從名為“users.json”的文件中讀取數(shù)據(jù)。它嘗試將數(shù)據(jù)解析為 JSON 并將其作為用戶對(duì)象返回。如果在此過(guò)程中發(fā)生錯(cuò)誤,它會(huì)記錄錯(cuò)誤并返回一個(gè)空對(duì)象。
saveUsers:此函數(shù)通過(guò)使用 fs 模塊的 writeFileSync 方法寫(xiě)入用戶對(duì)象的 JSON 字符串表示形式,將用戶對(duì)象保存到“users.json”文件中。如果在此過(guò)程中發(fā)生錯(cuò)誤,則會(huì)記錄該錯(cuò)誤。
findAll:此函數(shù)返回一個(gè)解析為 UnitUser 對(duì)象數(shù)組的承諾。它使用 Object.values(users) 從用戶對(duì)象中提取值(用戶)。
findOne:此函數(shù)采用 id 參數(shù)并返回一個(gè) Promise,該 Promise 解析為與 users 對(duì)象中的該 id 對(duì)應(yīng)的 UnitUser 對(duì)象。
create:此函數(shù)將 userData 對(duì)象作為輸入,并返回一個(gè)解析為新創(chuàng)建的 UnitUser 對(duì)象的 Promise。它使用 uuid 包生成一個(gè)隨機(jī) ID,并檢查具有該 ID 的用戶是否已存在。如果具有該 id 的用戶存在,它將生成一個(gè)新的 id,直到找到唯一的 id。然后,它使用 bcrypt 對(duì) userData 對(duì)象的密碼進(jìn)行哈希處理,并將哈希后的密碼保存在 UnitUser 對(duì)象中。UnitUser 對(duì)象被添加到 users 對(duì)象中,使用 saveUsers 保存并返回。
findByEmail:此函數(shù)采用 user_email 參數(shù),并返回一個(gè)承諾,如果具有指定電子郵件的用戶存在,則該承諾解析為 UnitUser 對(duì)象,否則返回 null。它使用 findAll 檢索所有用戶,并使用 find 方法查找具有匹配電子郵件地址的用戶。
ComparePassword:此函數(shù)采用電子郵件和提供的密碼作為參數(shù),如果提供的密碼與用戶存儲(chǔ)的密碼匹配,則返回一個(gè)解析為 UnitUser 對(duì)象的承諾,否則返回 null。它調(diào)用 findByEmail 通過(guò)電子郵件檢索用戶,然后使用 bcrypt.compare 將散列存儲(chǔ)的密碼與提供的密碼進(jìn)行比較。
update:此函數(shù)采用 id 和 updateValues 作為參數(shù),并返回一個(gè)承諾,如果具有指定 id 的用戶存在,則該承諾解析為更新的 UnitUser 對(duì)象。它使用 findOne 檢查用戶是否存在,如果 updateValues 包含新密碼,則更新用戶的密碼。用戶的屬性使用 updateValues 中的值進(jìn)行更新,并使用 saveUsers 保存用戶對(duì)象。
remove:此函數(shù)采用 id 參數(shù)并返回一個(gè)承諾,如果具有指定 id 的用戶不存在,則該承諾解析為 null,否則返回 void。它使用 findOne 檢查用戶是否存在,并使用 delete 關(guān)鍵字從 users 對(duì)象中刪除該用戶。然后使用 saveUsers 保存更新的用戶對(duì)象。
這些函數(shù)充當(dāng)我們的 API 可用來(lái)處理和檢索數(shù)據(jù)庫(kù)信息的方法。
接下來(lái),讓 all 將所有必需的函數(shù)和模塊導(dǎo)入到路由文件 ./src/users.routes.ts 中并填充如下:
?import express, {Request, Response} from "express"
?import { UnitUser, User } from "./user.interface"
?import {StatusCodes} from "http-status-codes"
?import * as database from "./user.database"
?
?export const userRouter = express.Router()
?
?userRouter.get("/users", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const allUsers : UnitUser[] = await database.findAll()
?
? ? ? ? ?if (!allUsers) {
? ? ? ? ? ? ?return res.status(StatusCodes.NOT_FOUND).json({msg : `No users at this time..`})
? ? ? ? ?}
?
? ? ? ? ?return res.status(StatusCodes.OK).json({total_user : allUsers.length, allUsers})
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
?
?userRouter.get("/user/:id", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const user : UnitUser = await database.findOne(req.params.id)
?
? ? ? ? ?if (!user) {
? ? ? ? ? ? ?return res.status(StatusCodes.NOT_FOUND).json({error : `User not found!`})
? ? ? ? ?}
?
? ? ? ? ?return res.status(StatusCodes.OK).json({user})
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
?
?userRouter.post("/register", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const { username, email, password } = req.body
?
? ? ? ? ?if (!username || !email || !password) {
? ? ? ? ? ? ?return res.status(StatusCodes.BAD_REQUEST).json({error : `Please provide all the required parameters..`})
? ? ? ? ?}
?
? ? ? ? ?const user = await database.findByEmail(email)
?
? ? ? ? ?if (user) {
? ? ? ? ? ? ?return res.status(StatusCodes.BAD_REQUEST).json({error : `This email has already been registered..`})
? ? ? ? ?}
?
? ? ? ? ?const newUser = await database.create(req.body)
?
? ? ? ? ?return res.status(StatusCodes.CREATED).json({newUser})
?
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
?
?userRouter.post("/login", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const {email, password} = req.body
?
? ? ? ? ?if (!email || !password) {
? ? ? ? ? ? ?return res.status(StatusCodes.BAD_REQUEST).json({error : "Please provide all the required parameters.."})
? ? ? ? ?}
?
? ? ? ? ?const user = await database.findByEmail(email)
?
? ? ? ? ?if (!user) {
? ? ? ? ? ? ?return res.status(StatusCodes.NOT_FOUND).json({error : "No user exists with the email provided.."})
? ? ? ? ?}
?
? ? ? ? ?const comparePassword = await database.comparePassword(email, password)
?
? ? ? ? ?if (!comparePassword) {
? ? ? ? ? ? ?return res.status(StatusCodes.BAD_REQUEST).json({error : `Incorrect Password!`})
? ? ? ? ?}
?
? ? ? ? ?return res.status(StatusCodes.OK).json({user})
?
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
?
?
?userRouter.put('/user/:id', async (req : Request, res : Response) => {
?
? ? ?try {
?
? ? ? ? ?const {username, email, password} = req.body
?
? ? ? ? ?const getUser = await database.findOne(req.params.id)
?
? ? ? ? ?if (!username || !email || !password) {
? ? ? ? ? ? ?return res.status(401).json({error : `Please provide all the required parameters..`})
? ? ? ? ?}
?
? ? ? ? ?if (!getUser) {
? ? ? ? ? ? ?return res.status(404).json({error : `No user with id ${req.params.id}`})
? ? ? ? ?}
?
? ? ? ? ?const updateUser = await database.update((req.params.id), req.body)
?
? ? ? ? ?return res.status(201).json({updateUser})
? ? ?} catch (error) {
? ? ? ? ?console.log(error)
? ? ? ? ?return res.status(500).json({error})
? ? ?}
?})
?
?userRouter.delete("/user/:id", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const id = (req.params.id)
?
? ? ? ? ?const user = await database.findOne(id)
?
? ? ? ? ?if (!user) {
? ? ? ? ? ? ?return res.status(StatusCodes.NOT_FOUND).json({error : `User does not exist`})
? ? ? ? ?}
?
? ? ? ? ?await database.remove(id)
?
? ? ? ? ?return res.status(StatusCodes.OK).json({msg : "User deleted"})
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
這是每個(gè)函數(shù)的作用:
userRouter.get("/users"):此函數(shù)處理對(duì)“/users”的 GET 請(qǐng)求。它從數(shù)據(jù)庫(kù)模塊調(diào)用 findAll 函數(shù)來(lái)檢索所有用戶。如果未找到用戶,它將返回 404 狀態(tài)代碼和一條消息。如果找到用戶,則返回 200 狀態(tài)碼以及用戶總數(shù)和所有用戶的數(shù)組。
userRouter.get("/user/:id"):此函數(shù)處理對(duì)“/user/:id”的 GET 請(qǐng)求,其中 :id 代表特定用戶的 ID。它從數(shù)據(jù)庫(kù)模塊調(diào)用 findOne 函數(shù)來(lái)檢索具有指定 ID 的用戶。如果未找到用戶,則會(huì)返回 404 狀態(tài)代碼和錯(cuò)誤消息。如果找到用戶,它將返回 200 狀態(tài)代碼以及用戶對(duì)象。
userRouter.post("/register"):此函數(shù)處理對(duì)“/register”的 POST 請(qǐng)求以進(jìn)行用戶注冊(cè)。它從請(qǐng)求正文中提取用戶名、電子郵件和密碼。如果缺少任何這些字段,它將返回 400 狀態(tài)代碼和錯(cuò)誤消息。它從數(shù)據(jù)庫(kù)模塊調(diào)用 findByEmail 函數(shù)來(lái)檢查電子郵件是否已注冊(cè)。如果找到電子郵件,它會(huì)返回 400 狀態(tài)代碼和錯(cuò)誤消息。如果未找到電子郵件,它將調(diào)用數(shù)據(jù)庫(kù)模塊中的創(chuàng)建函數(shù)來(lái)創(chuàng)建新用戶,并返回 201 狀態(tài)代碼和新創(chuàng)建的用戶對(duì)象。
userRouter.post("/login"):此函數(shù)處理對(duì)“/login”的 POST 請(qǐng)求以進(jìn)行用戶登錄。它從請(qǐng)求正文中提取電子郵件和密碼。如果缺少任何這些字段,它將返回 400 狀態(tài)代碼和錯(cuò)誤消息。它從數(shù)據(jù)庫(kù)模塊調(diào)用 findByEmail 函數(shù)來(lái)檢查電子郵件是否存在。如果未找到電子郵件,則會(huì)返回 404 狀態(tài)代碼和錯(cuò)誤消息。如果找到電子郵件,它將調(diào)用數(shù)據(jù)庫(kù)模塊中的comparePassword 函數(shù)來(lái)檢查提供的密碼是否與存儲(chǔ)的密碼匹配。如果密碼不匹配,它將返回 400 狀態(tài)代碼和錯(cuò)誤消息。如果密碼匹配,它將返回 200 狀態(tài)代碼以及用戶對(duì)象。
userRouter.put('/user/:id'):此函數(shù)處理對(duì)“/user/:id”的 PUT 請(qǐng)求,其中 :id 代表特定用戶的 ID。它從請(qǐng)求正文中提取用戶名、電子郵件和密碼。如果缺少任何這些字段,它將返回 401 狀態(tài)代碼和錯(cuò)誤消息。它從數(shù)據(jù)庫(kù)模塊調(diào)用findOne函數(shù)來(lái)檢查具有指定ID的用戶是否存在。如果未找到用戶,則會(huì)返回 404 狀態(tài)代碼和錯(cuò)誤消息。如果找到用戶,它會(huì)從數(shù)據(jù)庫(kù)模塊調(diào)用更新函數(shù)來(lái)更新用戶的詳細(xì)信息,并返回 201 狀態(tài)代碼和更新后的用戶對(duì)象。
userRouter.delete("/user/:id"):此函數(shù)處理對(duì)“/user/:id”的 DELETE 請(qǐng)求,其中 :id 代表特定用戶的 ID。它從請(qǐng)求參數(shù)中提取 id。它從數(shù)據(jù)庫(kù)模塊調(diào)用findOne函數(shù)來(lái)檢查具有指定ID的用戶是否存在。如果未找到用戶,則會(huì)返回 404 狀態(tài)代碼和錯(cuò)誤消息。如果找到用戶,它會(huì)調(diào)用數(shù)據(jù)庫(kù)模塊中的刪除函數(shù)來(lái)刪除該用戶,并返回 200 狀態(tài)代碼和成功消息。
所有這些函數(shù)定義了與用戶相關(guān)的操作的路由和相應(yīng)的邏輯,例如檢索所有用戶、檢索特定用戶、注冊(cè)新用戶、登錄用戶、更新用戶詳細(xì)信息和刪除用戶。
最后,為了對(duì)這些路由進(jìn)行 API 調(diào)用,我們需要將它們導(dǎo)入到我們的 app.ts 文件中并更新我們的代碼,如下所示:
?import express from "express"
?import * as dotevnv from "dotenv"
?import cors from "cors"
?import helmet from "helmet"
?import { userRouter } from "./users/users.routes"
?
?dotevnv.config()
?
?if (!process.env.PORT) {
? ? ?console.log(`No port value specified...`)
?}
?
?const PORT = parseInt(process.env.PORT as string, 10)
?
?const app = express()
?
?app.use(express.json())
?app.use(express.urlencoded({extended : true}))
?app.use(cors())
?app.use(helmet())
?
?app.use('/', userRouter)
?
?app.listen(PORT, () => {
? ? ?console.log(`Server is listening on port ${PORT}`)
?})
偉大的!現(xiàn)在讓我們啟動(dòng)服務(wù)器并使用 Postman 測(cè)試我們的 API。
npm run dev在你的終端中運(yùn)行
你的終端應(yīng)該與此類似
?[INFO] 20:55:40 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.1, typescript ver. 5.1.3)Server is listening on port 7000
偉大的!讓我們調(diào)用我們的端點(diǎn)。
注冊(cè)用戶
登錄用戶
獲取所有用戶
獲取單個(gè)用戶
更新用戶
刪除用戶:
注意:如果您添加了用戶,您的 users.json 文件應(yīng)不斷追加新用戶,并且應(yīng)如下所示。
用戶數(shù)據(jù)存儲(chǔ)文件:
最后,讓我們?yōu)槲覀兊漠a(chǎn)品創(chuàng)建登錄和路由。 因此,讓我們將用戶界面的內(nèi)容復(fù)制到文件中并進(jìn)行少量更改./src/product.interface.ts
?export interface Product {
? ? ?name : string,
? ? ?price : number;
? ? ?quantity : number;
? ? ?image : string;
?}
?
?export interface UnitProduct extends Product {
? ? ?id : string
?}
?
?export interface Products {
? ? ?[key : string] : UnitProduct
?}
您可以參考有關(guān)用戶界面的部分,了解有關(guān)這些界面的用途的詳細(xì)信息。
接下來(lái),就像在文件中一樣,讓我們用類似的邏輯./src/users.database.ts填充 。./src/products.database.ts
?import { Product, Products, UnitProduct } from "./product.interface";
?import { v4 as random } from "uuid";
?import fs from "fs";
?
?let products: Products = loadProducts();
?
?function loadProducts(): Products {
? ?try {
? ? ?const data = fs.readFileSync("./products.json", "utf-8");
? ? ?return JSON.parse(data);
? ?} catch (error) {
? ? ?console.log(`Error ${error}`);
? ? ?return {};
? ?}
?}
?
?function saveProducts() {
? ? ?try {
? ? ? ? ?fs.writeFileSync("./products.json", JSON.stringify(products), "utf-8");
? ? ? ? ?console.log("Products saved successfully!")
? ? ?} catch (error) {
? ? ? ? ?console.log("Error", error)
? ? ?}
?}
?
?
?export const findAll = async () : Promise<UnitProduct[]> => Object.values(products)
?
?export const findOne = async (id : string) : Promise<UnitProduct> => products[id]
?
?export const create = async (productInfo : Product) : Promise<null | UnitProduct> => {
?
? ? ?let id = random()
?
? ? ?let product = await findOne(id)
?
? ? ?while (product) {
? ? ? ? ?id = random ()
? ? ? ? ?await findOne(id)
? ? ?}
?
? ? ?products[id] = {
? ? ? ? ?id : id,
? ? ? ? ?...productInfo
? ? ?}
?
? ? ?saveProducts()
?
? ? ?return products[id]
?}
?
?export const update = async (id : string, updateValues : Product) : Promise<UnitProduct | null> => {
?
? ? ?const product = await findOne(id)
?
? ? ?if (!product) {
? ? ? ? ?return null
? ? ?}
?
? ? ?products[id] = {
? ? ? ? ?id,
? ? ? ? ?...updateValues
? ? ?}
?
? ? ?saveProducts()
?
? ? ?return products[id]
?}
?
?export const remove = async (id : string) : Promise<null | void> => {
?
? ? ?const product = await findOne(id)
?
? ? ?if (!product) {
? ? ? ? ?return null
? ? ?}
?
? ? ?delete products[id]
?
? ? ?saveProducts()
?
?}
同樣,您可以參考用戶部分,了解有關(guān)這些函數(shù)為我們的 API 提供的功能的更多詳細(xì)信息。
一旦我們的邏輯檢查通過(guò),就可以為我們的產(chǎn)品實(shí)施路線了。
./src/products.routes.ts使用以下代碼填充該文件:
?import express, {Request, Response} from "express"
?import { Product, UnitProduct } from "./product.interface"
?import * as database from "./product.database"
?import {StatusCodes} from "http-status-codes"
?
?export const productRouter = express.Router()
?
?productRouter.get('/products', async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? const allProducts = await database.findAll()
?
? ? ? ? if (!allProducts) {
? ? ? ? ?return res.status(StatusCodes.NOT_FOUND).json({error : `No products found!`})
? ? ? ? }
?
? ? ? ? return res.status(StatusCodes.OK).json({total : allProducts.length, allProducts})
? ? ?} catch (error) {
? ? ? ? return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
?
?productRouter.get("/product/:id", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const product = await database.findOne(req.params.id)
?
? ? ? ? ?if (!product) {
? ? ? ? ? ? ?return res.status(StatusCodes.NOT_FOUND).json({error : "Product does not exist"})
? ? ? ? ?}
?
? ? ? ? ?return res.status(StatusCodes.OK).json({product})
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
?
?
?productRouter.post("/product", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const {name, price, quantity, image} = req.body
?
? ? ? ? ?if (!name || !price || !quantity || !image) {
? ? ? ? ? ? ?return res.status(StatusCodes.BAD_REQUEST).json({error : `Please provide all the required parameters..`})
? ? ? ? ?}
? ? ? ? ?const newProduct = await database.create({...req.body})
? ? ? ? ?return res.status(StatusCodes.CREATED).json({newProduct})
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
?
?productRouter.put("/product/:id", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const id = req.params.id
?
? ? ? ? ?const newProduct = req.body
?
? ? ? ? ?const findProduct = await database.findOne(id)
?
? ? ? ? ?if (!findProduct) {
? ? ? ? ? ? ?return res.status(StatusCodes.NOT_FOUND).json({error : `Product does not exist..`})
? ? ? ? ?}
?
? ? ? ? ?const updateProduct = await database.update(id, newProduct)
?
? ? ? ? ?return res.status(StatusCodes.OK).json({updateProduct})
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
?
?
?productRouter.delete("/product/:id", async (req : Request, res : Response) => {
? ? ?try {
? ? ? ? ?const getProduct = await database.findOne(req.params.id)
?
? ? ? ? ?if (!getProduct) {
? ? ? ? ? ? ?return res.status(StatusCodes.NOT_FOUND).json({error : `No product with ID ${req.params.id}`})
? ? ? ? ?}
?
? ? ? ? ?await database.remove(req.params.id)
?
? ? ? ? ?return res.status(StatusCodes.OK).json({msg : `Product deleted..`})
?
? ? ?} catch (error) {
? ? ? ? ?return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
? ? ?}
?})
不要忘記在我們的 app.ts 文件中導(dǎo)入并調(diào)用產(chǎn)品的路由,該文件現(xiàn)在應(yīng)該如下所示:
?import express from "express"
?import * as dotevnv from "dotenv"
?import cors from "cors"
?import helmet from "helmet"
?import { userRouter } from "./users/users.routes"
?import { productRouter } from "./products/product.routes"
?
?dotevnv.config()
?
?if (!process.env.PORT) {
? ? ?console.log(`No port value specified...`)
?}
?
?const PORT = parseInt(process.env.PORT as string, 10)
?
?const app = express()
?
?app.use(express.json())
?app.use(express.urlencoded({extended : true}))
?app.use(cors())
?app.use(helmet())
?
?app.use('/', userRouter)
?app.use('/', productRouter)
?
?app.listen(PORT, () => {
? ? ?console.log(`Server is listening on port ${PORT}`)
?})
完美的。我們現(xiàn)在擁有一個(gè)使用 Typescript 和 Nodejs 構(gòu)建的成熟 API。歡呼!!
讓我們測(cè)試一下我們的端點(diǎn)。
創(chuàng)建產(chǎn)品
所有產(chǎn)品
單品
更新產(chǎn)品
刪除產(chǎn)品
如果您添加新產(chǎn)品,它們將被附加到products.json文件中,如下所示:
我們就這樣完成了。如果您已經(jīng)走到這一步,恭喜您并謝謝您!
歡迎提出意見(jiàn)和建議。
你可以在 github 上找到完整的代碼 -> GITHUB:/REALSTEVEIG/REST-API-WITH-TYPESCRIPT-NODEJS-AND-A-FILE-BASED-STORAGE-SYSTEM