使用Prisma,节点JS和打字稿构建REST API。
#typescript #node #prisma

介绍

学习新技术的过程通常会感到乏味,尤其是在您尚未看到行动中的最初阶段。

本文将指导您完成集成这些惊人的技术的过程,并演示如何使用它们构建小型应用程序。

在本文过程中,我们将开发一个博客应用程序,以展示CRUD操作在实践中的工作方式,并具有基本功能,包括:

  • 创建新帖子
  • 检索所有帖子或特定帖子
  • 修改发布内容
  • 最终删除职位
  • 我们将实现的另一个功能是每个帖子都包含类似的计数。

crud表示创建,阅读,更新和删除

如果您有兴趣查看代码,请查看GitHub存储库here

先决条件

在进行继续之前,熟悉以下工具很重要:

  • node.js and express.js
  • 打字稿
  • Prisma

如果您需要简要概述Prisma,我的博客文章here可能是一个有用的资源。

让我们开始ð

打开终端,然后使用以下命令创建并导航到文件夹目录

mkdir prisma-typescript-blog && cd prisma-typescript-blog

接下来,使用纱线初始化项目

yarn init -y

然后,安装依赖项

yarn add -D @types/express @types/node prisma ts-node-dev typescript 
yarn add express @prisma/client

最后,用Prisma Cli的init命令设置Prisma:

npx prisma init --datasource-provider sqlite 

这将使用您的Prisma架构文件创建一个新的Prisma目录,并将SQLITE配置为数据库。

设置Prisma模式

prisma/prisma.schema

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

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

model Post {
  id         Int      @id @default(autoincrement())
  title      String
  content    String
  likesCount Int      @default(0)
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  // Relations
  comments Comment[]
}

model Comment {
  id        Int      @id @default(autoincrement())
  content   String
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  post   Post @relation(fields: [postId], references: [id])
  postId Int
}

在上面的模式中,我们定义了包括PostComment的博客模型3

post   Post @relation(fields: [postId], references: [id])

这种代码线意味着PostComment模型之间存在一对多关系。一个帖子可以发表很多评论,每个评论仅与一个帖子相关联。 Comment模型中的postId字段用于引用Post模型中的id字段。

完成此操作后,更新您的package.json以使其看起来像:

{
  "name": "prisma-typescript-blog",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "start": "ts-node-dev --respawn --transpile-only --exit-child src/server.ts",
    "db:migrate": "npx prisma migrate dev --name user-entity --create-only && npx prisma generate",
    "db:push": "npx prisma db push"
  },
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "lib": [
      "es6",
      "dom"
    ]
  },
  "devDependencies": {
    "@types/express": "^4.17.17",
    "prisma": "^4.11.0",
    "ts-node-dev": "^2.0.0",
    "typescript": "^4.9.5"
  },
  "dependencies": {
    "@prisma/client": "^4.11.0",
    "dotenv": "^16.0.3",
    "express": "^4.18.2"
  }
}

  • start启动节点JS服务器
  • db:migrate-生成迁移文件和Prisma客户端
  • db:push-将Prisma模式推向数据库

移民

在这一点上,您有一个Prisma模型,但还没有数据库。打开您的终端并运行以下命令以创建SQLITE数据库和以模型为代表的帖子表。

yarn db:migrate

运行命令后,Prisma CLI将分析您的模式并在prisma/migrations目录中生成迁移文件。

将Prisma模式推向数据库:

yarn db:push

将SQLite数据库与Prisma模式同步后,运行npx prisma studio以打开浏览器中的Prisma GUI工具。 Prisma Studio允许您查看和突变数据库中存储的数据。

现在,您的浏览器应自动打开一个新标签,您将看到类似的东西:

Prisma Studio Example

项目结构ð§

编辑您的项目结构,看起来像下面的

`prisma` -|
          `schema.prisma`
`src` -|
     `controllers`
          -| 
           `post.controller.ts`
     `routes`
          -|
           `post.route.ts`
     `server.ts`
.env
`tsconfig.json`

.env

DATABASE_URL="file:./dev.db"

用上面的摘要替换.env文件中的任何内容

让我们继续前进有趣的部分ð

server.ts

import express, { Request, Response } from "express";
import { PrismaClient } from "@prisma/client";
import PostRouter from "./routes/blog.route";

export const prisma = new PrismaClient();

const app = express();
const port = 8080;

async function main() {
  app.use(express.json());

  // Register API routes
  app.use("/api/v1/post", PostRouter);

  // Catch unregistered routes
  app.all("*", (req: Request, res: Response) => {
    res.status(404).json({ error: `Route ${req.originalUrl} not found` });
  });

  app.listen(port, () => {
    console.log(`Server is listening on port ${port}`);
  });
}

main()
  .then(async () => {
    await prisma.$connect();
  })
  .catch(async (e) => {
    console.error(e);
    await prisma.$disconnect();
    process.exit(1);
  });

这里发生了很多事情,所以让我解释

  • prisma = new PrismaClient()-创建Prisma客户端的新实例
  • express.json()登记中间件以解析传入的请求主体,为json。
  • app.use("/api/v1/post", PostRouter);使用PoststRouter模块登录API路由。
  • app.all("*",...定义了一条接收路线,以处理未注册的路线并返回404错误响应。

调用main()函数时,它启动Express Server并使用Prisma客户端连接到数据库。如果有任何错误,它将错误记录到控制台并与数据库断开连接。

该代码是使用node.js,express.js和prisma orm构建API服务器的起点。它演示了如何处理API路由,连接到数据库,然后启动用于来源请求的服务器。

路线

routes/post.route.ts

import express from "express";
import PostController from "../controllers/post.controller";

const router = express.Router();

router.post("/create", PostController.createBlogPost);
router.post("/createPostAndComments", PostController.createPostAndComments);
router.get("/getall", PostController.getBlogPosts);
router.get("/get/:id", PostController.getBlogPost);
router.put("/update/:id", PostController.updateBlogPost);
router.delete("/delete/:id", PostController.deleteBlogPost);
router.delete("/deleteall", PostController.deleteAllBlogPosts);
router.post("/like", PostController.likeBlogPost);

export default router;

除了/createPostAndComments路线外,这些路线非常简单。每个路由都处理特定的HTTP方法(发布,获取,放置或删除)。路由映射到PostController控制器中定义的方法,该方法处理每个路由的逻辑。

这些路由的/:id部分是一个代表可以在URL中传递的动态值的参数。 id参数是特定值的占位符,例如博客文章的唯一标识符。

createPostAndComments将帮助我们展示一对一的关系,因为它在架构中定义了。

Post Prisma模型中,PostComment之间存在一对多关系,其中一个Post可以具有许多Comments,但是每个Comment只能属于一个Post。这种关系是使用Post模型中的comments字段在Prisma模式中建模的:

model Post {
  id         Int      @id @default(autoincrement())
  title      String
  content    String
  likesCount Int      @default(0)
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt

  // Relations
  comments Comment[]
}

您可以看到,comments字段是Comment对象的数组,它代表一对一的关系。

控制器

controllers/post.controller.ts

import { Request, Response } from "express";
import { prisma } from "../server";

const createBlogPost = async (req: Request, res: Response) => {
  try {
    const { title, content } = req.body;
    const newBlogPost = await prisma.post.create({
      data: {
        title,
        content,
      },
    });
    res.status(200).json(newBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const createPostAndComments = async (req: Request, res: Response) => {
  try {
    const { title, content, comments } = req.body;
    const newBlogPost = await prisma.post.create({
      data: {
        title,
        content,
        comments: {
          create: comments,
        },
      },
      include: {
        comments: true, // Include the comments in the response
      }
    });
    res.status(200).json(newBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const getBlogPosts = async (req: Request, res: Response) => {
  try {
    const blogPosts = await prisma.post.findMany();
    res.status(200).json(blogPosts);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const getBlogPost = async (req: Request, res: Response) => {
  try {
    const { id } = req.params;
    const blogPost = await prisma.post.findUnique({
      where: {
        id: Number(id),
      },
    });
    res.status(200).json(blogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const updateBlogPost = async (req: Request, res: Response) => {
  try {
    const { id, title, content } = req.body;
    const updatedBlogPost = await prisma.post.update({
      where: {
        id: Number(id),
      },
      data: {
        title,
        content,
      },
    });
    res.status(200).json(updatedBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const deleteBlogPost = async (req: Request, res: Response) => {
  try {
    const { id } = req.body;
    const deletedBlogPost = await prisma.post.delete({
      where: {
        id: Number(id),
      },
    });
    res.status(200).json(deletedBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const deleteAllBlogPosts = async (req: Request, res: Response) => {
  try {
    const deletedBlogPosts = await prisma.post.deleteMany();
    res.status(200).json(deletedBlogPosts);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

const likeBlogPost = async (req: Request, res: Response) => {
  try {
    const { id } = req.body;
    const likedBlogPost = await prisma.post.update({
      where: {
        id: Number(id),
      },
      data: {
        likesCount: {
          increment: 1,
        },
      },
    });
    res.status(200).json(likedBlogPost);
  } catch (e) {
    res.status(500).json({ error: e });
  }
};

export default {
  createBlogPost,
  createPostAndComments,
  getBlogPosts,
  getBlogPost,
  updateBlogPost,
  deleteBlogPost,
  deleteAllBlogPosts,
  likeBlogPost,
};


这个控制器非常笨重,所以我将继续解释每个方法。

我们的方法列出如下

  • createBlogPost
  • createPostAndComments
  • getBlogPosts
  • getBlogPost
  • updateBlogPost
  • deleteBlogPost
  • deleteAllBlogPosts
  • likeBlogPost

createBlogPost
这是一种接受titlecontent的非常简单的方法。它首先会破坏req.body对象的标题和内容值,即请求有效载荷。

此外,它使用create()方法来创建数据库中的新博客文章。此方法返回承诺,这就是为什么正在等待关键字等待操作完成之前完成执行之前的原因。

当向http://localhost:8080/api/v1/post/create端点提出请求主体中包含的凭据时,createBlogPost处理程序将被唤起创建新的博客文章。

Blog post postman demo

Postman是我测试所有端点的首选工具,但请随时使用您喜欢的测试工具。

createPostAndComments

CreatePostandComments方法期望请求主体包括titlecontentcomments阵列。然后,该方法使用Prisma提供的create()方法创建新的帖子对象,并在数据对象中包含comments

Comment对象将包括content字段,因为当Comment与新的Post关联时,postId字段将由prisma自动设置。

最后,include参数用于在响应中包含新创建的Postcomments

这是有效载荷和响应看起来像

Example Body

Example Response

updateBlogPost
该方法首先使用对象破坏从请求主体中提取idtitlecontent属性。

然后,它使用Prisma使用指定的id更新博客文章。 update()方法在post模型上调用,传递了指定wheredata子句的对象。 where子句指定要更新的博客文章的唯一标识符,而data子句指定了titlecontent属性的新值。

likeBlogPost
该方法首先从请求主体中提取id属性。此id代表要喜欢的博客文章的唯一标识符。

然后,它使用Prisma prisma.post.update()来更新指定的博客文章。 update()方法在post模型上调用,传递了指定wheredata子句的对象。 where子句指定了要更新的博客文章的唯一标识符,而数据条款increments likesCount属性则使用增量关键字

这是行动

当向http://localhost:8080/api/v1/post/like端点提出请求时,将id作为主体传递时,它会调用createBlogPost处理程序,而likesCount将被递增

Like Image

所有其他方法都与涵盖的方法非常相似,因此我不会浏览它们

在所有这些步骤之后,请确保您的服务器仍在运行,如果不运行yarn start以使其启动并运行。

综上所述

本文通过使用节点,TypeScript和Prisma创建博客API来演示如何应用您对这些惊人工具的知识。此外,教程涵盖了将API与SQLite数据库连接的过程。

祝贺您到达本文的结尾!如果您发现它有帮助,请考虑对其表示赞许,并在下面留下您的评论。

在下一个ðð

中见