使用Bun创建应用程序API

使用Bun创建应用程序API

Bun 是与 Node 和 Deno 竞争的新 JavaScript Runtime,具有更快的速度和其他一些特性。本文将介绍如何使用 Bun 创建完整的 API。

Bun.js 是一种新的 JavaScript 运行环境,与 Node.jsDeno 相似,但速度更快、更独立。它采用快速的底层语言 Zig 编写,并利用了为 Safari 等 Webkit 浏览器提供支持的 JavaScriptCore Engine。Zig 与 JavaScriptCore 引擎的结合使 Bun 成为速度最快的 JavaScript 运行环境时之一。

此外,Bun 不仅仅是一个运行环境。它还是软件包管理器、测试运行程序和捆绑程序。在本教程中,您将学习如何使用 ElysiaPostgreSQLPrisma 通过 Bun 创建一个简单的配方共享 API。

设置开发环境

要使用 Bun,首先要在系统中安装它。运行以下命令将 Bun 安装到 macOS、Linux 或 Windows Subsystem for Linux (WSL)。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
curl -fsSL https://bun.sh/install | bash
curl -fsSL https://bun.sh/install | bash
curl -fsSL https://bun.sh/install | bash

目前,Bun 仅有一个用于 Windows 的实验版本,仅支持运行环境。

安装 Bun 后,运行下面的命令创建并 cd 到项目目录:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mkdir recipe-sharing-api && cd recipe-sharing-api
mkdir recipe-sharing-api && cd recipe-sharing-api
mkdir recipe-sharing-api && cd recipe-sharing-api

接下来,运行下面的命令初始化一个新的 Bun 应用程序:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bun init
bun init
bun init

上述命令将提示您输入应用程序的软件包名称和入口点。您可以按 ENTER 键选择默认值,如下图所示。

应用程序的软件包名称和入口点

当前目录应该如下图所示。

API目录

接下来,运行下面的命令安装所需的依赖项:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bun add elysia @elysiajs/cookie prisma @prisma/client dotenv pg jsonwebtoken@8.5.1
bun add elysia @elysiajs/cookie prisma @prisma/client dotenv pg jsonwebtoken@8.5.1
bun add elysia @elysiajs/cookie prisma @prisma/client dotenv pg jsonwebtoken@8.5.1

运行安装相应类型:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bun add -d @types/jsonwebtoken
bun add -d @types/jsonwebtoken
bun add -d @types/jsonwebtoken

您安装的依赖项是:

  • Elysia:Elysia 是 Bun 的网络框架,可简化与 Bun 的协作,类似于 Express 对 Node.js 的作用。
  • Prisma:Prisma 是 JavaScript 和 TypeScript 的对象关系映射器(ORM)。
  • Dotenv:Dotenv 是一个 NPM 软件包,用于将 .env 文件中的环境变量加载到 process.env 中。
  • PG:PG 是 PostgreSQL 的本地驱动程序。
  • jsonwebtoken@8.5.1实现 JWT 标准(8.5.1 版)的软件包。

设置数据库

食谱共享 API 将涉及三个表:RecipesUsers, 和 Comments。用户可以创建和共享菜谱,查看他人的菜谱,并对菜谱发表评论。

运行以下命令在应用程序中使用 PostgreSQL 初始化 Prisma:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bunx prisma init --datasource-provider postgresql
bunx prisma init --datasource-provider postgresql
bunx prisma init --datasource-provider postgresql

上述命令会创建一个 .env 文件和一个 Prisma 文件夹。您将在 Prisma 文件夹中找到 schema.prisma 文件。该文件包含数据库连接的配置。

接下来,打开 .env 文件,将虚拟数据库连接 URI 替换为数据库的连接 URI。

创建 Prisma 模型

Prisma 模型代表数据库中的表。Prisma 模式中的每个模型都对应数据库中的一个表,定义其结构。

打开 schema.prisma 文件,添加以下代码块以创建 User 模型。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
recipies Recipe[]
comments Comment[]
}
model User { id Int @id @default(autoincrement()) email String @unique name String? password String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt recipies Recipe[] comments Comment[] }
model User {
id        Int      @id @default(autoincrement())
email     String   @unique
name      String?
password  String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
recipies  Recipe[]
comments  Comment[]
}

上面的代码块代表 User 模型。它包含应用程序所需的所有用户信息,如电子邮件、姓名、密码、食谱和评论。

当新用户注册时,您将创建一个新的 User 模型实例,当用户尝试登录时,您将获取该实例,并将存储的信息与登录请求中发送的信息进行比较。

接下来,将下面的代码块添加到 schema.prisma 文件中,以创建 Recipe 模型:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
model Recipe {
id Int @id @default(autoincrement())
title String
body String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId Int
comments Comment[]
}
model Recipe { id Int @id @default(autoincrement()) title String body String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) userId Int comments Comment[] }
model Recipe {
id        Int      @id @default(autoincrement())
title     String
body      String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user      User     @relation(fields: [userId], references: [id])
userId    Int
comments  Comment[]
}

上面的代码块表示 Recipe 模型。它包含应用程序所需的所有配方信息,如标题、正文和创建配方的用户信息。

当用户创建配方时,您将创建一个新的 Recipe 模型实例。

然后,将下面的代码块添加到 schema.prisma 文件中,以创建 Comment 模型:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
model Comment {
id Int @id @default(autoincrement())
body String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
userId Int
recipe Recipe @relation(fields: [recipeId], references: [id])
recipeId Int
}
model Comment { id Int @id @default(autoincrement()) body String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) userId Int recipe Recipe @relation(fields: [recipeId], references: [id]) recipeId Int }
model Comment {
id        Int      @id @default(autoincrement())
body      String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user      User     @relation(fields: [userId], references: [id])
userId    Int
recipe   Recipe  @relation(fields: [recipeId], references: [id])
recipeId Int
}

上面的代码块代表了您的 Comment 模型。它包含应用程序所需的所有注释信息,包括正文、日期、创建注释的用户以及注释的配方。

当用户对配方发表评论时,就会创建一个新的 Comment 模型实例。

最后,运行下面的命令生成并运行迁移

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bunx prisma migrate dev --name init
bunx prisma migrate dev --name init
bunx prisma migrate dev --name init

您可以将 init 替换为您选择的任何名称。

运行上述命令将确保 Prisma 模式与数据库模式同步。

创建服务和控制器

在项目中创建服务和控制器有助于组织代码,使其更易于维护。

运行以下命令,在项目根目录下创建 controllers 和 services 文件夹:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
mkdir controllers && mkdir services
mkdir controllers && mkdir services
mkdir controllers && mkdir services

接下来,在 services 文件夹中创建以下文件:

  • user.service.ts: 该文件包含与用户注册和登录相关的所有逻辑。
  • recipe.service.ts: 该文件包含创建和查看食谱的所有逻辑。
  • comment.service.ts: 该文件包含对菜谱进行评论的所有逻辑。
  • auth.service.ts: 该文件包含验证用户的逻辑。

然后,在 controllers 文件夹中创建以下文件:

  • comments.controller.ts: 该文件包含评论的所有控制器逻辑。
  • recipe.controller.ts: 该文件包含配方控制器的所有逻辑。
  • user.controller.ts: 该文件包含用户身份验证的所有控制器逻辑。

实现服务逻辑

服务是功能或逻辑的独特单元,旨在执行特定任务。

要实现这一点,请打开 auth.service.ts 文件并添加以下代码块。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//auth.service.ts
import jwt from "jsonwebtoken";
export const verifyToken = (token: string) => {
let payload: any;
//Verify the JWT token
jwt.verify(token, process.env.JWT_SECRET as string, (error, decoded) => {
if (error) {
throw new Error("Invalid token");
}
payload = decoded;
});
return payload;
};
export const signUserToken = (data: { id: number; email: string }) => {
//Sign the JWT token
const token = jwt.sign(
{
id: data.id,
email: data.email,
},
process.env.JWT_SECRET as string,
{ expiresIn: "1d" }
);
return token;
};
//auth.service.ts import jwt from "jsonwebtoken"; export const verifyToken = (token: string) => { let payload: any; //Verify the JWT token jwt.verify(token, process.env.JWT_SECRET as string, (error, decoded) => { if (error) { throw new Error("Invalid token"); } payload = decoded; }); return payload; }; export const signUserToken = (data: { id: number; email: string }) => { //Sign the JWT token const token = jwt.sign( { id: data.id, email: data.email, }, process.env.JWT_SECRET as string, { expiresIn: "1d" } ); return token; };
//auth.service.ts
import jwt from "jsonwebtoken";
export const verifyToken = (token: string) => {
let payload: any;
//Verify the JWT token
jwt.verify(token, process.env.JWT_SECRET as string, (error, decoded) => {
if (error) {
throw new Error("Invalid token");
}
payload = decoded;
});
return payload;
};
export const signUserToken = (data: { id: number; email: string }) => {
//Sign the JWT token
const token = jwt.sign(
{
id: data.id,
email: data.email,
},
process.env.JWT_SECRET as string,
{ expiresIn: "1d" }
);
return token;
};

上面的代码块导出了两个函数: verifyTokensignUserTokenverifyToken 函数接收用户的访问令牌并检查其有效性。如果有效,它就会解码令牌并返回令牌中包含的信息,否则就会出错。

signUserToken 函数将用户数据作为有效载荷,创建并返回有效期为一天的 JWT。

接下来,打开 user.service.ts 文件并添加下面的代码块:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//user.service.ts
import { prisma } from "../index";
import { signUserToken } from "./auth.service";
export const createNewUser = async (data: {
name: string;
email: string;
password: string;
}) => {
try {
const { name, email, password } = data;
//Hash the password using the Bun package and bcrypt algorithm
const hashedPassword = await Bun.password.hash(password, {
algorithm: "bcrypt",
});
//Create the user
const user = await prisma.user.create({
data: {
name,
email,
password: hashedPassword,
},
});
return user;
} catch (error) {
throw error;
}
};
export const login = async (data: { email: string; password: string }) => {
try {
const { email, password } = data;
//Find the user
const user = await prisma.user.findUnique({
where: {
email,
},
});
if (!user) {
throw new Error("User not found");
}
//Verify the password
const valid = await Bun.password.verify(password, user.password);
if (!valid) {
throw new Error("Invalid credentials");
}
//Sign the JWT token
const token = signUserToken({
id: user.id,
email: user.email,
});
return {
message: "User logged in successfully",
token,
};
} catch (error) {
throw error;
}
};
//user.service.ts import { prisma } from "../index"; import { signUserToken } from "./auth.service"; export const createNewUser = async (data: { name: string; email: string; password: string; }) => { try { const { name, email, password } = data; //Hash the password using the Bun package and bcrypt algorithm const hashedPassword = await Bun.password.hash(password, { algorithm: "bcrypt", }); //Create the user const user = await prisma.user.create({ data: { name, email, password: hashedPassword, }, }); return user; } catch (error) { throw error; } }; export const login = async (data: { email: string; password: string }) => { try { const { email, password } = data; //Find the user const user = await prisma.user.findUnique({ where: { email, }, }); if (!user) { throw new Error("User not found"); } //Verify the password const valid = await Bun.password.verify(password, user.password); if (!valid) { throw new Error("Invalid credentials"); } //Sign the JWT token const token = signUserToken({ id: user.id, email: user.email, }); return { message: "User logged in successfully", token, }; } catch (error) { throw error; } };
//user.service.ts
import { prisma } from "../index";
import { signUserToken } from "./auth.service";
export const createNewUser = async (data: {
name: string;
email: string;
password: string;
}) => {
try {
const { name, email, password } = data;
//Hash the password using the Bun package and bcrypt algorithm
const hashedPassword = await Bun.password.hash(password, {
algorithm: "bcrypt",
});
//Create the user
const user = await prisma.user.create({
data: {
name,
email,
password: hashedPassword,
},
});
return user;
} catch (error) {
throw error;
}
};
export const login = async (data: { email: string; password: string }) => {
try {
const { email, password } = data;
//Find the user
const user = await prisma.user.findUnique({
where: {
email,
},
});
if (!user) {
throw new Error("User not found");
}
//Verify the password
const valid = await Bun.password.verify(password, user.password);
if (!valid) {
throw new Error("Invalid credentials");
}
//Sign the JWT token
const token = signUserToken({
id: user.id,
email: user.email,
});
return {
message: "User logged in successfully",
token,
};
} catch (error) {
throw error;
}
};

上面的代码块导出了两个函数: createNewUserlogincreateNewUser 函数接受用户名、电子邮件和密码。它使用 Bun 内置的密码模块对密码进行散列,并使用所提供的信息创建一个新用户。

login 函数接收用户的凭据,并与数据库中存储的记录进行验证。如果匹配,就会为用户创建一个访问令牌;否则就会出错。

接下来,打开 recipe.service.ts 文件,添加下面的代码块。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//recipe.service.ts
import { prisma } from "../index";
export const createRecipe = async (data: {
title: string;
body: string;
userId: number;
}) => {
const { title, body, userId } = data;
//Create the recipe
const recipe = await prisma.recipe.create({
data: {
title,
body,
userId,
},
});
return recipe;
};
export const getAllRecipes = async () => {
//Get all recipes
const recipes = await prisma.recipe.findMany({
include: {
user: true,
comments: true,
},
});
return recipes;
};
export const getRecipeById = async (id: number) => {
//Get recipe by id and include the user
const recipe = await prisma.recipe.findUnique({
where: {
id,
},
include: {
user: true,
},
});
return recipe;
};
//recipe.service.ts import { prisma } from "../index"; export const createRecipe = async (data: { title: string; body: string; userId: number; }) => { const { title, body, userId } = data; //Create the recipe const recipe = await prisma.recipe.create({ data: { title, body, userId, }, }); return recipe; }; export const getAllRecipes = async () => { //Get all recipes const recipes = await prisma.recipe.findMany({ include: { user: true, comments: true, }, }); return recipes; }; export const getRecipeById = async (id: number) => { //Get recipe by id and include the user const recipe = await prisma.recipe.findUnique({ where: { id, }, include: { user: true, }, }); return recipe; };
//recipe.service.ts
import { prisma } from "../index";
export const createRecipe = async (data: {
title: string;
body: string;
userId: number;
}) => {
const { title, body, userId } = data;
//Create the recipe
const recipe = await prisma.recipe.create({
data: {
title,
body,
userId,
},
});
return recipe;
};
export const getAllRecipes = async () => {
//Get all recipes
const recipes = await prisma.recipe.findMany({
include: {
user: true,
comments: true,
},
});
return recipes;
};
export const getRecipeById = async (id: number) => {
//Get recipe by id and include the user
const recipe = await prisma.recipe.findUnique({
where: {
id,
},
include: {
user: true,
},
});
return recipe;
};

上面的代码块导出了三个函数: createRecipegetAllRecipiesgetRecipeById

createRecipe 函数根据作为参数传递的数据创建新配方并返回。 getAllRecipies 函数检索并返回数据库中的所有菜谱。getRecipeById 函数根据作为参数传递的 id 获取配方并返回。

接下来,打开你的 comments.service.ts 文件并添加下面的代码块。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//comments.service.ts
import { prisma } from "../index";
export const createComment = async (data: {
body: string;
recipeId: number;
userId: number;
}) => {
try {
const { body, recipeId, userId } = data;
//Create the comment for the recipe with the given id
const comment = await prisma.comment.create({
data: {
body,
userId,
recipeId: recipeId,
},
});
return comment;
} catch (error: any) {
throw error;
}
};
export const getAllCommentsForRecipe = async (recipeId: number) => {
//Get all comments for the recipe with the given id
const comments = await prisma.comment.findMany({
where: {
recipeId,
},
include: {
user: true,
},
});
return comments;
};
//comments.service.ts import { prisma } from "../index"; export const createComment = async (data: { body: string; recipeId: number; userId: number; }) => { try { const { body, recipeId, userId } = data; //Create the comment for the recipe with the given id const comment = await prisma.comment.create({ data: { body, userId, recipeId: recipeId, }, }); return comment; } catch (error: any) { throw error; } }; export const getAllCommentsForRecipe = async (recipeId: number) => { //Get all comments for the recipe with the given id const comments = await prisma.comment.findMany({ where: { recipeId, }, include: { user: true, }, }); return comments; };
//comments.service.ts
import { prisma } from "../index";
export const createComment = async (data: {
body: string;
recipeId: number;
userId: number;
}) => {
try {
const { body, recipeId, userId } = data;
//Create the comment for the recipe with the given id
const comment = await prisma.comment.create({
data: {
body,
userId,
recipeId: recipeId,
},
});
return comment;
} catch (error: any) {
throw error;
}
};
export const getAllCommentsForRecipe = async (recipeId: number) => {
//Get all comments for the recipe with the given id
const comments = await prisma.comment.findMany({
where: {
recipeId,
},
include: {
user: true,
},
});
return comments;
};

上面的代码块导出了两个函数: createCommentgetAllCommentsForRecipecreateComment 为特定配方创建新的注释,而 getAllCommentsForRecipe 则返回特定配方的所有注释。

实现控制器逻辑

与使用 request 和 response 对象处理请求的 Express.js 不同,Elysia 使用上下文对象

上下文对象提供的方法与 Express 的 request 和 response 对象类似。此外,Elysia 还会自动将控制器函数的返回值映射到响应中,并将其返回给客户端。

要实现控制器逻辑,请打开 user.controller.ts 文件并添加以下代码块。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//user.controller.ts
import Elysia from "elysia";
import { createNewUser, login } from "../services/user.service";
export const userController = (app: Elysia) => {
app.post("/signup", async (context) => {
try {
const userData: any = context.body;
const newUser = await createNewUser({
name: userData.name,
email: userData.email,
password: userData.password,
});
return {
user: newUser,
};
} catch (error: any) {
return {
error: error.message,
};
}
});
app.post("/login", async (context) => {
try {
const userData: any = context.body;
const loggedInUser = await login({
email: userData.email,
password: userData.password,
});
return loggedInUser;
} catch (error: any) {
console.log(error);
return {
error: error.message,
};
}
});
};
//user.controller.ts import Elysia from "elysia"; import { createNewUser, login } from "../services/user.service"; export const userController = (app: Elysia) => { app.post("/signup", async (context) => { try { const userData: any = context.body; const newUser = await createNewUser({ name: userData.name, email: userData.email, password: userData.password, }); return { user: newUser, }; } catch (error: any) { return { error: error.message, }; } }); app.post("/login", async (context) => { try { const userData: any = context.body; const loggedInUser = await login({ email: userData.email, password: userData.password, }); return loggedInUser; } catch (error: any) { console.log(error); return { error: error.message, }; } }); };
//user.controller.ts
import Elysia from "elysia";
import { createNewUser, login } from "../services/user.service";
export const userController = (app: Elysia) => {
app.post("/signup", async (context) => {
try {
const userData: any = context.body;
const newUser = await createNewUser({
name: userData.name,
email: userData.email,
password: userData.password,
});
return {
user: newUser,
};
} catch (error: any) {
return {
error: error.message,
};
}
});
app.post("/login", async (context) => {
try {
const userData: any = context.body;
const loggedInUser = await login({
email: userData.email,
password: userData.password,
});
return loggedInUser;
} catch (error: any) {
console.log(error);
return {
error: error.message,
};
}
});
};

上面的代码块实现了 /signup/login 的控制器逻辑。

当用户向 /signup 发送 POST 请求时,控制器将从上下文对象( context.body )中提取请求正文,并将其传递给在 users.service.ts 文件中创建的 createNewUser 函数。

当用户向 /login 发送 POST 请求时,控制器将从上下文对象中提取请求体,并将电子邮件和密码传递给登录函数。如果用户信息正确无误,控制器将返回一条成功消息和访问令牌。

接下来,打开 recipe.controller.ts 文件,添加下面的代码块。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//recipe.controller.ts
import Elysia from "elysia";
import { createRecipe, getAllRecipes } from "../services/recipe.service";
import { verifyToken } from "../services/auth.service";
export const recipeController = (app: Elysia) => {
app.post("/create-recipe", async (context) => {
try {
const authHeader = context.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (!token) {
throw new Error("Invalid token");
}
const verifiedToken = verifyToken(token as string);
const recipeData: any = context.body;
const newRecipe = await createRecipe({
title: recipeData.title,
body: recipeData.body,
userId: verifiedToken?.id,
});
return {
recipe: newRecipe,
};
} catch (error: any) {
return {
error: error.message,
};
}
});
app.get("/recipes", async () => {
try {
const recipes = await getAllRecipes();
return recipes;
} catch (error: any) {
return {
error: error.message,
};
}
});
};
//recipe.controller.ts import Elysia from "elysia"; import { createRecipe, getAllRecipes } from "../services/recipe.service"; import { verifyToken } from "../services/auth.service"; export const recipeController = (app: Elysia) => { app.post("/create-recipe", async (context) => { try { const authHeader = context.headers["authorization"]; const token = authHeader && authHeader.split(" ")[1]; if (!token) { throw new Error("Invalid token"); } const verifiedToken = verifyToken(token as string); const recipeData: any = context.body; const newRecipe = await createRecipe({ title: recipeData.title, body: recipeData.body, userId: verifiedToken?.id, }); return { recipe: newRecipe, }; } catch (error: any) { return { error: error.message, }; } }); app.get("/recipes", async () => { try { const recipes = await getAllRecipes(); return recipes; } catch (error: any) { return { error: error.message, }; } }); };
//recipe.controller.ts
import Elysia from "elysia";
import { createRecipe, getAllRecipes } from "../services/recipe.service";
import { verifyToken } from "../services/auth.service";
export const recipeController = (app: Elysia) => {
app.post("/create-recipe", async (context) => {
try {
const authHeader = context.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
if (!token) {
throw new Error("Invalid token");
}
const verifiedToken = verifyToken(token as string);
const recipeData: any = context.body;
const newRecipe = await createRecipe({
title: recipeData.title,
body: recipeData.body,
userId: verifiedToken?.id,
});
return {
recipe: newRecipe,
};
} catch (error: any) {
return {
error: error.message,
};
}
});
app.get("/recipes", async () => {
try {
const recipes = await getAllRecipes();
return recipes;
} catch (error: any) {
return {
error: error.message,
};
}
});
};

上面的代码块实现了 /create-recipe/recipes 的控制器逻辑。

当用户向 /create-recipe 发出 POST 请求时,控制器将检查用户是否拥有有效的访问令牌(检查用户是否已登录)。

如果用户没有访问令牌或令牌无效,控制器就会出错。如果令牌有效,控制器将从上下文对象中提取配方详细信息,并将其传递给 createRecipe 函数。

当用户向 /recipes 发送 GET 请求时,控制器将调用 getAllRecipes 函数并返回所有菜谱。

接下来,打开您的 comments.controller.ts,添加下面的代码块。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//comments.controller.ts
import Elysia from "elysia";
import {
createComment,
getAllCommentsForRecipe,
} from "../services/comments.service";
import { verifyToken } from "../services/auth.service";
export const commentController = (app: Elysia) => {
app.post("/:recipeId/create-comment", async (context) => {
try {
const authHeader = context.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
const recipeId = context.params.recipeId;
if (!token) {
throw new Error("Invalid token");
}
const verifiedToken = verifyToken(token as string);
const commentData: any = context.body;
const newComment = await createComment({
body: commentData.body,
recipeId: +recipeId,
userId: verifiedToken?.id,
});
return newComment;
} catch (error: any) {
return {
error: error.message,
};
}
});
app.get("/:recipeId/comments", async (context) => {
try {
const recipeId = context.params.recipeId;
const comments = await getAllCommentsForRecipe(+recipeId);
return {
comments,
};
} catch (error: any) {
return {
error: error.message,
};
}
});
};
//comments.controller.ts import Elysia from "elysia"; import { createComment, getAllCommentsForRecipe, } from "../services/comments.service"; import { verifyToken } from "../services/auth.service"; export const commentController = (app: Elysia) => { app.post("/:recipeId/create-comment", async (context) => { try { const authHeader = context.headers["authorization"]; const token = authHeader && authHeader.split(" ")[1]; const recipeId = context.params.recipeId; if (!token) { throw new Error("Invalid token"); } const verifiedToken = verifyToken(token as string); const commentData: any = context.body; const newComment = await createComment({ body: commentData.body, recipeId: +recipeId, userId: verifiedToken?.id, }); return newComment; } catch (error: any) { return { error: error.message, }; } }); app.get("/:recipeId/comments", async (context) => { try { const recipeId = context.params.recipeId; const comments = await getAllCommentsForRecipe(+recipeId); return { comments, }; } catch (error: any) { return { error: error.message, }; } }); };
//comments.controller.ts
import Elysia from "elysia";
import {
createComment,
getAllCommentsForRecipe,
} from "../services/comments.service";
import { verifyToken } from "../services/auth.service";
export const commentController = (app: Elysia) => {
app.post("/:recipeId/create-comment", async (context) => {
try {
const authHeader = context.headers["authorization"];
const token = authHeader && authHeader.split(" ")[1];
const recipeId = context.params.recipeId;
if (!token) {
throw new Error("Invalid token");
}
const verifiedToken = verifyToken(token as string);
const commentData: any = context.body;
const newComment = await createComment({
body: commentData.body,
recipeId: +recipeId,
userId: verifiedToken?.id,
});
return newComment;
} catch (error: any) {
return {
error: error.message,
};
}
});
app.get("/:recipeId/comments", async (context) => {
try {
const recipeId = context.params.recipeId;
const comments = await getAllCommentsForRecipe(+recipeId);
return {
comments,
};
} catch (error: any) {
return {
error: error.message,
};
}
});
};

上面的代码块实现了 /:recipeId/create-comment/:recipeId/comments 的控制器逻辑。

当用户向 /:recipeId/create-comment 发出POST请求时,控制器会检查用户是否已登录,如果已登录,就会从上下文对象中提取评论详情,并将其传递给 createComment 函数。

当用户向 /:recipeId/comments 发送 GET 请求时,控制器会从上下文对象( context.params.recipeId )中提取 recipeId,并将其作为参数传递给 getAllCommentsForRecipe,然后使用显式类型强制将其转换为数字。

设置 Bun-Elysia 服务器

创建服务和控制器后,您必须设置一个服务器来处理传入的请求。

要创建 Bun-Elysia 服务器,请打开 index.ts 文件并添加以下代码块。

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//index.ts
import Elysia from "elysia";
import { recipeController } from "./controllers/recipe.controller";
import { PrismaClient } from "@prisma/client";
import { userController } from "./controllers/user.controller";
import { commentController } from "./controllers/comments.controller";
//Create instances of prisma and Elysia
const prisma = new PrismaClient();
const app = new Elysia();
//Use controllers as middleware
app.use(userController as any);
app.use(recipeController as any);
app.use(commentController as any);
//Listen for traffic
app.listen(4040, () => {
console.log("Server is running on port 4040");
});
export { app, prisma };
//index.ts import Elysia from "elysia"; import { recipeController } from "./controllers/recipe.controller"; import { PrismaClient } from "@prisma/client"; import { userController } from "./controllers/user.controller"; import { commentController } from "./controllers/comments.controller"; //Create instances of prisma and Elysia const prisma = new PrismaClient(); const app = new Elysia(); //Use controllers as middleware app.use(userController as any); app.use(recipeController as any); app.use(commentController as any); //Listen for traffic app.listen(4040, () => { console.log("Server is running on port 4040"); }); export { app, prisma };
//index.ts
import Elysia from "elysia";
import { recipeController } from "./controllers/recipe.controller";
import { PrismaClient } from "@prisma/client";
import { userController } from "./controllers/user.controller";
import { commentController } from "./controllers/comments.controller";
//Create instances of prisma and Elysia
const prisma = new PrismaClient();
const app = new Elysia();
//Use controllers as middleware
app.use(userController as any);
app.use(recipeController as any);
app.use(commentController as any);
//Listen for traffic
app.listen(4040, () => {
console.log("Server is running on port 4040");
});
export { app, prisma };

上面的代码块导入了所有控制器、Elysia 框架和 PrismaClient。它还创建了 Prisma 和 Elysia 实例,并将控制器注册为中间件,以便将所有传入请求路由到正确的处理程序。

然后,它会监听 4040 端口的传入流量,并将 Elysia 和 Prisma 实例导出到应用程序的其他部分。

最后,运行下面的命令启动应用程序:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
bun --watch index.ts
bun --watch index.ts
bun --watch index.ts

上述命令以观察模式启动 Bun 应用程序。

小结

在本文中,你将学习如何使用 Bun、Elysia、Prisma 和 Postgres 构建一个简单的 API。您已经学会了安装和配置 Bun、构建数据库,以及实施模块化服务和控制器以实现高效的代码管理。您可以使用任何 API 测试工具(如 Postman 或 Insomnia)测试您构建的 API。

评论留言