Node.js 和 TypeScript 教程:使用 Typescript、NodeJS 和基于文件的存储系统构建 rest API。

怼烎@ 2024-03-16 20:59 20阅读 0赞

欢迎到我的博客!在本教程中,我将指导您完成使用 Node.js、Express 和 TypeScript 构建强大的微型电子商务 API 的过程。我们将一起探讨各种功能和技术,这些功能和技术将使您能够为电子商务应用程序创建强大的 API。

我们在这个项目中的一个关键决定是实现一个基于文件的存储系统,而不是依赖像 MongoDB 这样的传统数据库。这种方法简单易行,非常适合规模较小的应用程序或可能不需要成熟的数据库管理系统的场景。

本教程将涵盖基本主题,例如用户管理、产品处理和身份验证。

您将获得使用跨越用户和产品数据的功能的实践经验,展示这些实体如何在电子商务 API 中交互。在本教程结束时,您将全面了解如何构建强大的 API,从而实现与用户和产品资源的无缝交互。

因此,加入我这一激动人心的旅程,我们将深入研究使用 Node.js、Express 和 TypeScript 创建微型电子商务 API。

在 Node.js 中开始使用 TypeScript
首先创建一个如下所示的项目目录。

efe34e70b96d3414d73a68c684b81ed3.png

接下来,通过使用以下命令创建具有默认设置的 package.json 文件,在项目目录中初始化 Node.js 项目:

npm init -y

安装项目依赖

您的 Node.js 项目需要一些依赖项才能使用 TypeScript 创建安全的 Express 服务器。像这样安装它们:

npm i express dotenv helmet cors http-status-codes uuid bcryptjs
要使用 TypeScript,您还需要安装稳定版本的 TypeScript 作为开发者依赖项:

npm i -D typescript

要有效地使用 TypeScript,您需要为之前安装的包安装类型定义:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>npm i -D @types/express @types/dotenv @types/helmet @types/cors @types/http-status-codes @types/uuid @types/bcryptjs
  2. </code></span></span>

使用以下定义服务器可用于侦听请求的端口的变量填充 .env 隐藏文件:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>PORT=7000
  2. </code></span></span>

接下来,在 src 文件夹的根目录中找到 app.js 文件并导入您之前安装的项目依赖项,并使用 dotenv.config() 方法从本地 .env 文件加载任何环境变量:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>import express from "express"
  2. import * as dotevnv from "dotenv"
  3. import cors from "cors"
  4. import helmet from "helmet"
  5. dotevnv.config()
  6. if (!process.env.PORT) {
  7. console.log(`No port value specified...`)
  8. }
  9. const PORT = parseInt(process.env.PORT as string, 10)
  10. const app = express()
  11. app.use(express.json())
  12. app.use(express.urlencoded({extended : true}))
  13. app.use(cors())
  14. app.use(helmet())
  15. app.listen(PORT, () => {
  16. console.log(`Server is listening on port ${PORT}`)
  17. })
  18. </code></span></span>

在此代码片段中,使用 Express 框架设置了一个 Node.js 应用程序。这是正在发生的事情的细分:

导入所需的模块:

express被导入作为构建 Web 应用程序的主要框架。

导入dotenv以处理环境变量。

导入cors以启用跨源资源共享。

导入helmet以将安全标头添加到 HTTP 响应。

该代码检查是否定义了 PORT 环境变量。如果没有,一条消息将记录到控制台。

使用 parseInt() 将 PORT 变量从字符串解析为整数。

Express 应用程序的实例是使用 express() 创建的,并分配给 app 变量。

中间件功能添加到 Express 应用程序:

express.json()用于解析传入请求的 JSON 主体。

express.urlencoded({extended : true})用于解析传入请求的 URL 编码主体。

cors()用于启用跨源资源共享。

helmet()用于通过设置各种 HTTP 标头来增强应用程序的安全性。

Express 应用程序通过调用 app.listen() 开始监听指定的端口。服务器运行后,一条指示端口号的消息将记录到控制台。

改进 TypeScript 开发工作流程

TypeScript 编译过程会增加应用程序的引导时间。但是,只要源代码发生变化,您就不需要重新编译整个项目。您可以设置 ts-node-dev 以显着减少进行更改时重新启动应用程序所需的时间。

首先安装此软件包以启动您的开发工作流程:

npm i -D ts-node-dev

ts-node-dev 在任何所需文件更改时重新启动目标 Node.js 进程。但是,它在重启之间共享 Typescript 编译过程,这可以显着提高重启速度。

您可以在 package.json 中创建一个开发 npm 脚本来运行您的服务器。像这样更新你的 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" } }

让我们简要分解一下 ts-node-dev 的选项:

—respawn:在脚本退出后继续观察变化。

—pretty:使用漂亮的诊断格式化程序(TS_NODE_PRETTY)。

./src/app.ts:这是应用程序的入口文件。

现在,只需运行开发脚本即可启动您的项目:

npm run dev

如果一切正常,您会看到一条消息,表明服务器正在侦听端口 7000 上的请求。

使用 TypeScript 接口建模数据

在创建任何路由之前,定义您要管理的数据的结构。我们的用户数据库将具有以下属性:

id:(字符串)项目记录的唯一标识符。
用户名:(字符串)项目的名称。
email : (number) 以美分为单位的商品价格。
密码:(字符串)项目的描述。

使用以下定义填充 src/users/user.interface.ts:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>export interface User {
  2. username : string,
  3. email : string,
  4. password : string
  5. }
  6. export interface UnitUser extends User {
  7. id : string
  8. }
  9. export interface Users {
  10. [key : string] : UnitUser
  11. }
  12. </code></span></span>

这段代码定义了三个 TypeScript 接口:

  • 用户界面表示具有三个属性的基本用户对象:

username,这是一个表示用户用户名的字符串。
email,这是一个表示用户电子邮件地址的字符串。
password,这是一个表示用户密码的字符串。

  • UnitUser 接口扩展了 User 接口并添加了一个 id 属性:

id,是一个字符串,表示用户的唯一标识符。

  • Users 接口表示具有动态键的用户对象的集合:

[key: string]表示Users对象的key可以是任意字符串。
Users 对象的值属于 UnitUser 类型,这意味着集合中的每个用户对象都应符合 UnitUser 接口。
简单来说,这些接口定义了用户对象的结构和类型。User 接口定义了用户的基本属性,而 UnitUser 接口添加了一个 id 属性来表示具有唯一标识符的用户。Users 接口表示用户对象的集合,其中键是字符串,值是 UnitUser 对象。

接下来,我们将为数据存储创建逻辑。如果你愿意,你可以称它为数据库。
使用以下代码填充 src/users/user.database.ts:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>import { User, UnitUser, Users } from "./user.interface";
  2. import bcrypt from "bcryptjs"
  3. import {v4 as random} from "uuid"
  4. import fs from "fs"
  5. let users: Users = loadUsers()
  6. function loadUsers () : Users {
  7. try {
  8. const data = fs.readFileSync("./users.json", "utf-8")
  9. return JSON.parse(data)
  10. } catch (error) {
  11. console.log(`Error ${error}`)
  12. return {}
  13. }
  14. }
  15. function saveUsers () {
  16. try {
  17. fs.writeFileSync("./users.json", JSON.stringify(users), "utf-8")
  18. console.log(`User saved successfully!`)
  19. } catch (error) {
  20. console.log(`Error : ${error}`)
  21. }
  22. }
  23. export const findAll = async (): Promise<UnitUser[]> => Object.values(users);
  24. export const findOne = async (id: string): Promise<UnitUser> => users[id];
  25. export const create = async (userData: UnitUser): Promise<UnitUser | null> => {
  26. let id = random()
  27. let check_user = await findOne(id);
  28. while (check_user) {
  29. id = random()
  30. check_user = await findOne(id)
  31. }
  32. const salt = await bcrypt.genSalt(10);
  33. const hashedPassword = await bcrypt.hash(userData.password, salt);
  34. const user : UnitUser = {
  35. id : id,
  36. username : userData.username,
  37. email : userData.email,
  38. password: hashedPassword
  39. };
  40. users[id] = user;
  41. saveUsers()
  42. return user;
  43. };
  44. export const findByEmail = async (user_email: string): Promise<null | UnitUser> => {
  45. const allUsers = await findAll();
  46. const getUser = allUsers.find(result => user_email === result.email);
  47. if (!getUser) {
  48. return null;
  49. }
  50. return getUser;
  51. };
  52. export const comparePassword = async (email : string, supplied_password : string) : Promise<null | UnitUser> => {
  53. const user = await findByEmail(email)
  54. const decryptPassword = await bcrypt.compare(supplied_password, user!.password)
  55. if (!decryptPassword) {
  56. return null
  57. }
  58. return user
  59. }
  60. export const update = async (id : string, updateValues : User) : Promise<UnitUser | null> => {
  61. const userExists = await findOne(id)
  62. if (!userExists) {
  63. return null
  64. }
  65. if(updateValues.password) {
  66. const salt = await bcrypt.genSalt(10)
  67. const newPass = await bcrypt.hash(updateValues.password, salt)
  68. updateValues.password = newPass
  69. }
  70. users[id] = {
  71. ...userExists,
  72. ...updateValues
  73. }
  74. saveUsers()
  75. return users[id]
  76. }
  77. export const remove = async (id : string) : Promise<null | void> => {
  78. const user = await findOne(id)
  79. if (!user) {
  80. return null
  81. }
  82. delete users[id]
  83. saveUsers()
  84. }
  85. </code></span></span>

让我解释一下上面代码中的每个函数:

loadUsers:此函数使用 fs 模块从名为“users.json”的文件中读取数据。它尝试将数据解析为 JSON 并将其作为用户对象返回。如果在此过程中发生错误,它会记录错误并返回一个空对象。

saveUsers:此函数通过使用 fs 模块的 writeFileSync 方法写入用户对象的 JSON 字符串表示形式,将用户对象保存到“users.json”文件。如果在此过程中发生错误,它会记录错误。

findAll:此函数返回解析为 UnitUser 对象数组的承诺。它使用 Object.values(users) 从用户对象中提取值(用户)。

findOne:此函数采用一个 id 参数并返回一个承诺,该承诺解析为与用户对象中的该 id 对应的 UnitUser 对象。

create:此函数将 userData 对象作为输入并返回解析为新创建的 UnitUser 对象的承诺。它使用 uuid 包生成一个随机 ID,并检查具有该 ID 的用户是否已经存在。如果存在具有该 ID 的用户,它会生成一个新 ID,直到找到一个唯一的 ID。然后它使用 bcrypt 对 userData 对象的密码进行哈希处理,并将哈希后的密码保存在 UnitUser 对象中。UnitUser 对象被添加到 users 对象,使用 saveUsers 保存,并返回。

findByEmail:此函数采用 user_email 参数并返回一个承诺,如果具有指定电子邮件的用户存在,则该承诺解析为 UnitUser 对象,否则返回 null。它使用 findAll 检索所有用户,并使用 find 方法找到具有匹配电子邮件的用户。

comparePassword:此函数将电子邮件和 supplied_password 作为参数,如果提供的密码与用户存储的密码匹配,则返回解析为 UnitUser 对象的承诺,否则返回 null。它调用 findByEmail 通过电子邮件检索用户,然后使用 bcrypt.compare 将哈希存储的密码与提供的密码进行比较。

update:此函数将 id 和 updateValues 作为参数,如果具有指定 id 的用户存在,则返回解析为更新后的 UnitUser 对象的承诺。它使用 findOne 检查用户是否存在,如果 updateValues 包含新密码,则更新用户密码。使用 updateValues 中的值更新用户的属性,并使用 saveUsers 保存用户对象。

remove:此函数采用一个 id 参数并返回一个承诺,如果具有指定 id 的用户不存在,则该承诺将解析为 null ,否则返回 void 。它使用 findOne 检查用户是否存在,并使用 delete 关键字从用户对象中删除该用户。然后使用 saveUsers 保存更新的用户对象。

这些函数作为我们的 API 可以用来处理和检索数据库信息的方法。

接下来,让 all 将所有需要的功能和模块导入到路由文件 ./src/users.routes.ts 中,并填充如下:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>import express, {Request, Response} from "express"
  2. import { UnitUser, User } from "./user.interface"
  3. import {StatusCodes} from "http-status-codes"
  4. import * as database from "./user.database"
  5. export const userRouter = express.Router()
  6. userRouter.get("/users", async (req : Request, res : Response) => {
  7. try {
  8. const allUsers : UnitUser[] = await database.findAll()
  9. if (!allUsers) {
  10. return res.status(StatusCodes.NOT_FOUND).json({msg : `No users at this time..`})
  11. }
  12. return res.status(StatusCodes.OK).json({total_user : allUsers.length, allUsers})
  13. } catch (error) {
  14. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  15. }
  16. })
  17. userRouter.get("/user/:id", async (req : Request, res : Response) => {
  18. try {
  19. const user : UnitUser = await database.findOne(req.params.id)
  20. if (!user) {
  21. return res.status(StatusCodes.NOT_FOUND).json({error : `User not found!`})
  22. }
  23. return res.status(StatusCodes.OK).json({user})
  24. } catch (error) {
  25. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  26. }
  27. })
  28. userRouter.post("/register", async (req : Request, res : Response) => {
  29. try {
  30. const { username, email, password } = req.body
  31. if (!username || !email || !password) {
  32. return res.status(StatusCodes.BAD_REQUEST).json({error : `Please provide all the required parameters..`})
  33. }
  34. const user = await database.findByEmail(email)
  35. if (user) {
  36. return res.status(StatusCodes.BAD_REQUEST).json({error : `This email has already been registered..`})
  37. }
  38. const newUser = await database.create(req.body)
  39. return res.status(StatusCodes.CREATED).json({newUser})
  40. } catch (error) {
  41. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  42. }
  43. })
  44. userRouter.post("/login", async (req : Request, res : Response) => {
  45. try {
  46. const {email, password} = req.body
  47. if (!email || !password) {
  48. return res.status(StatusCodes.BAD_REQUEST).json({error : "Please provide all the required parameters.."})
  49. }
  50. const user = await database.findByEmail(email)
  51. if (!user) {
  52. return res.status(StatusCodes.NOT_FOUND).json({error : "No user exists with the email provided.."})
  53. }
  54. const comparePassword = await database.comparePassword(email, password)
  55. if (!comparePassword) {
  56. return res.status(StatusCodes.BAD_REQUEST).json({error : `Incorrect Password!`})
  57. }
  58. return res.status(StatusCodes.OK).json({user})
  59. } catch (error) {
  60. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  61. }
  62. })
  63. userRouter.put('/user/:id', async (req : Request, res : Response) => {
  64. try {
  65. const {username, email, password} = req.body
  66. const getUser = await database.findOne(req.params.id)
  67. if (!username || !email || !password) {
  68. return res.status(401).json({error : `Please provide all the required parameters..`})
  69. }
  70. if (!getUser) {
  71. return res.status(404).json({error : `No user with id ${req.params.id}`})
  72. }
  73. const updateUser = await database.update((req.params.id), req.body)
  74. return res.status(201).json({updateUser})
  75. } catch (error) {
  76. console.log(error)
  77. return res.status(500).json({error})
  78. }
  79. })
  80. userRouter.delete("/user/:id", async (req : Request, res : Response) => {
  81. try {
  82. const id = (req.params.id)
  83. const user = await database.findOne(id)
  84. if (!user) {
  85. return res.status(StatusCodes.NOT_FOUND).json({error : `User does not exist`})
  86. }
  87. await database.remove(id)
  88. return res.status(StatusCodes.OK).json({msg : "User deleted"})
  89. } catch (error) {
  90. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  91. }
  92. })
  93. </code></span></span>

这是每个函数的作用:

userRouter.get(“/users”):此函数处理对“/users”的 GET 请求。它从数据库模块调用 findAll 函数来检索所有用户。如果未找到用户,它会返回 404 状态代码和一条消息。如果找到用户,它会返回 200 状态代码以及用户总数和所有用户的数组。

userRouter.get(“/user/:id”):此函数处理对“/user/:id”的 GET 请求,其中:id 表示特定用户的 ID。它从数据库模块调用 findOne 函数来检索具有指定 ID 的用户。如果找不到用户,它会返回 404 状态代码和一条错误消息。如果找到用户,它会返回带有用户对象的 200 状态代码。

userRouter.post(“/register”):此函数处理对“/register”的用户注册的 POST 请求。它从请求正文中提取用户名、电子邮件和密码。如果缺少任何这些字段,它会返回 400 状态代码和一条错误消息。它从数据库模块调用 findByEmail 函数来检查电子邮件是否已经注册。如果找到电子邮件,它会返回 400 状态代码和一条错误消息。如果未找到电子邮件,它会调用数据库模块中的创建函数来创建新用户,并使用新创建的用户对象返回 201 状态代码。

userRouter.post(“/login”):此函数处理对“/login”的用户登录的 POST 请求。它从请求正文中提取电子邮件和密码。如果缺少任何这些字段,它会返回 400 状态代码和一条错误消息。它从数据库模块调用 findByEmail 函数来检查电子邮件是否存在。如果找不到电子邮件,它会返回 404 状态代码和一条错误消息。如果找到电子邮件,它会调用数据库模块中的 comparePassword 函数来检查提供的密码是否与存储的密码匹配。如果密码不匹配,它会返回 400 状态代码和一条错误消息。如果密码匹配,它会返回带有用户对象的 200 状态代码。

userRouter.put(‘/user/:id’):此函数处理对“/user/:id”的 PUT 请求,其中:id 表示特定用户的 ID。它从请求正文中提取用户名、电子邮件和密码。如果缺少任何这些字段,它会返回 401 状态代码和一条错误消息。它从数据库模块调用 findOne 函数来检查具有指定 ID 的用户是否存在。如果找不到用户,它会返回 404 状态代码和一条错误消息。如果找到用户,它会调用数据库模块的更新函数来更新用户的详细信息,并返回带有更新后的用户对象的 201 状态代码。

userRouter.delete(“/user/:id”):此函数处理对“/user/:id”的删除请求,其中:id 表示特定用户的 ID。它从请求参数中提取 id。它从数据库模块调用 findOne 函数来检查具有指定 ID 的用户是否存在。如果找不到用户,它会返回 404 状态代码和一条错误消息。如果找到用户,它会调用数据库模块中的删除函数来删除用户,并返回 200 状态码和成功消息。

所有这些函数定义了用户相关操作的路由和相应的逻辑,例如检索所有用户、检索特定用户、注册新用户、登录用户、更新用户详细信息和删除用户。

最后,为了对这些路由进行API 调用,我们需要将它们导入到我们的 app.ts 文件中并像这样更新我们的代码:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>import express from "express"
  2. import * as dotevnv from "dotenv"
  3. import cors from "cors"
  4. import helmet from "helmet"
  5. import { userRouter } from "./users/users.routes"
  6. dotevnv.config()
  7. if (!process.env.PORT) {
  8. console.log(`No port value specified...`)
  9. }
  10. const PORT = parseInt(process.env.PORT as string, 10)
  11. const app = express()
  12. app.use(express.json())
  13. app.use(express.urlencoded({extended : true}))
  14. app.use(cors())
  15. app.use(helmet())
  16. app.use('/', userRouter)
  17. app.listen(PORT, () => {
  18. console.log(`Server is listening on port ${PORT}`)
  19. })
  20. </code></span></span>

伟大的!现在让我们启动我们的服务器并使用 Postman 测试我们的 API。

npm run dev在你的终端运行

您的终端应该与此类似

[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

伟大的!让我们调用我们的端点。

注册用户

057b822c847800b342f6d8679fba6254.png

登录用户

378ebb12fbd98cdfa43a81c2cbde4218.png

获取所有用户

313158089091f2ddd9a21b317b18ca93.png

获取单个用户

82e3ab0d63ac2f62bd7c2328f2698cc8.png

更新用户

4ee196b152f178c15f9311e9d129a06a.png

删除用户:

1d257710e2144fe002f35ef6284de3f7.png

注意:如果您添加了用户,您的 users.json 文件应该不断添加新用户并且应该如下所示。

用户数据存储文件

c7912d610e34ec8f9f9888431a70bffa.png

最后,让我们为我们的产品创建登录名和路由。
因此,让我们复制我们的用户界面的内容,对文件进行微小的更改./src/product.interface.ts

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>export interface Product {
  2. name : string,
  3. price : number;
  4. quantity : number;
  5. image : string;
  6. }
  7. export interface UnitProduct extends Product {
  8. id : string
  9. }
  10. export interface Products {
  11. [key : string] : UnitProduct
  12. }
  13. </code></span></span>

您可以参考用户界面部分,了解有关这些界面功能的详细信息。

接下来,就像在文件中一样,让我们用类似的逻辑./src/users.database.ts填充 。./src/products.database.ts

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>import { Product, Products, UnitProduct } from "./product.interface";
  2. import { v4 as random } from "uuid";
  3. import fs from "fs";
  4. let products: Products = loadProducts();
  5. function loadProducts(): Products {
  6. try {
  7. const data = fs.readFileSync("./products.json", "utf-8");
  8. return JSON.parse(data);
  9. } catch (error) {
  10. console.log(`Error ${error}`);
  11. return {};
  12. }
  13. }
  14. function saveProducts() {
  15. try {
  16. fs.writeFileSync("./products.json", JSON.stringify(products), "utf-8");
  17. console.log("Products saved successfully!")
  18. } catch (error) {
  19. console.log("Error", error)
  20. }
  21. }
  22. export const findAll = async () : Promise<UnitProduct[]> => Object.values(products)
  23. export const findOne = async (id : string) : Promise<UnitProduct> => products[id]
  24. export const create = async (productInfo : Product) : Promise<null | UnitProduct> => {
  25. let id = random()
  26. let product = await findOne(id)
  27. while (product) {
  28. id = random ()
  29. await findOne(id)
  30. }
  31. products[id] = {
  32. id : id,
  33. ...productInfo
  34. }
  35. saveProducts()
  36. return products[id]
  37. }
  38. export const update = async (id : string, updateValues : Product) : Promise<UnitProduct | null> => {
  39. const product = await findOne(id)
  40. if (!product) {
  41. return null
  42. }
  43. products[id] = {
  44. id,
  45. ...updateValues
  46. }
  47. saveProducts()
  48. return products[id]
  49. }
  50. export const remove = async (id : string) : Promise<null | void> => {
  51. const product = await findOne(id)
  52. if (!product) {
  53. return null
  54. }
  55. delete products[id]
  56. saveProducts()
  57. }
  58. </code></span></span>

同样,您可以参考用户部分,了解有关这些功能为我们的 API 提供的内容的更多详细信息。

一旦我们的逻辑检查通过,就该为我们的产品实施路线了。

./src/products.routes.ts使用以下代码填充文件:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>import express, {Request, Response} from "express"
  2. import { Product, UnitProduct } from "./product.interface"
  3. import * as database from "./product.database"
  4. import {StatusCodes} from "http-status-codes"
  5. export const productRouter = express.Router()
  6. productRouter.get('/products', async (req : Request, res : Response) => {
  7. try {
  8. const allProducts = await database.findAll()
  9. if (!allProducts) {
  10. return res.status(StatusCodes.NOT_FOUND).json({error : `No products found!`})
  11. }
  12. return res.status(StatusCodes.OK).json({total : allProducts.length, allProducts})
  13. } catch (error) {
  14. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  15. }
  16. })
  17. productRouter.get("/product/:id", async (req : Request, res : Response) => {
  18. try {
  19. const product = await database.findOne(req.params.id)
  20. if (!product) {
  21. return res.status(StatusCodes.NOT_FOUND).json({error : "Product does not exist"})
  22. }
  23. return res.status(StatusCodes.OK).json({product})
  24. } catch (error) {
  25. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  26. }
  27. })
  28. productRouter.post("/product", async (req : Request, res : Response) => {
  29. try {
  30. const {name, price, quantity, image} = req.body
  31. if (!name || !price || !quantity || !image) {
  32. return res.status(StatusCodes.BAD_REQUEST).json({error : `Please provide all the required parameters..`})
  33. }
  34. const newProduct = await database.create({...req.body})
  35. return res.status(StatusCodes.CREATED).json({newProduct})
  36. } catch (error) {
  37. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  38. }
  39. })
  40. productRouter.put("/product/:id", async (req : Request, res : Response) => {
  41. try {
  42. const id = req.params.id
  43. const newProduct = req.body
  44. const findProduct = await database.findOne(id)
  45. if (!findProduct) {
  46. return res.status(StatusCodes.NOT_FOUND).json({error : `Product does not exist..`})
  47. }
  48. const updateProduct = await database.update(id, newProduct)
  49. return res.status(StatusCodes.OK).json({updateProduct})
  50. } catch (error) {
  51. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  52. }
  53. })
  54. productRouter.delete("/product/:id", async (req : Request, res : Response) => {
  55. try {
  56. const getProduct = await database.findOne(req.params.id)
  57. if (!getProduct) {
  58. return res.status(StatusCodes.NOT_FOUND).json({error : `No product with ID ${req.params.id}`})
  59. }
  60. await database.remove(req.params.id)
  61. return res.status(StatusCodes.OK).json({msg : `Product deleted..`})
  62. } catch (error) {
  63. return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({error})
  64. }
  65. })
  66. </code></span></span>

不要忘记在我们的 app.ts 文件中导入和调用产品的路由,它现在应该如下所示:

  1. <span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>import express from "express"
  2. import * as dotevnv from "dotenv"
  3. import cors from "cors"
  4. import helmet from "helmet"
  5. import { userRouter } from "./users/users.routes"
  6. import { productRouter } from "./products/product.routes"
  7. dotevnv.config()
  8. if (!process.env.PORT) {
  9. console.log(`No port value specified...`)
  10. }
  11. const PORT = parseInt(process.env.PORT as string, 10)
  12. const app = express()
  13. app.use(express.json())
  14. app.use(express.urlencoded({extended : true}))
  15. app.use(cors())
  16. app.use(helmet())
  17. app.use('/', userRouter)
  18. app.use('/', productRouter)
  19. app.listen(PORT, () => {
  20. console.log(`Server is listening on port ${PORT}`)
  21. })
  22. </code></span></span>

完美的。我们现在拥有一个使用 Typescript 和 Nodejs 构建的成熟 API。欢呼!!

让我们测试我们的端点。

创建产品

589237bbb8d9ff56403de36bfb22ceba.png

所有产品

40d1b0688389c18997a935631f570e26.png

单品

4bc2d5f20391f4c158915ff7e93b9825.png

更新产品

42bfd194f3aa58458e20850c9dedda69.png

删除产品

8ed4c42cf58a2c2b83d4769760721637.png

如果您添加新产品,它们将被附加到products.json文件中,它看起来像这样:

97b9f5cd7a48cefb8f309770f2d73592.png

这就是我们完成的。如果你走到这一步,恭喜并谢谢你!

欢迎提出意见和建议。

发表评论

表情:
评论列表 (有 0 条评论,20人围观)

还没有评论,来说两句吧...

相关阅读