UNPKG

utquidem

Version:

The meta-framework suite designed from scratch for frontend-focused modern web development.

455 lines (344 loc) 13.1 kB
--- sidebar_position: 6 --- # 开发 API 服务 本章将介绍如何使用 Modern.js,进行 API 服务项目的开发。本章对应的代码仓库地址在[这里查看](https://github.com/modern-js-dev/modern-js-examples/tree/main/quick-start/api-service)。 通过本章你可以了解到: - 如何创建一个 API 服务项目。 - 如何在项目中创建和开发 API。 - BFF 函数写法和框架写法。 - 如何使用测试功能。 :::info 注 API 服务指提供 HTTP 接口的服务端项目。 ::: ## 环境准备 import EnvPrepare from '@site/docs/components/env-prepare.md'; <EnvPrepare /> ## 创建项目 使用 `@modern-js/create` 创建新项目,运行命令如下: ```bash npx @modern-js/create api-service-demo ``` :::info 注 api-service-demo 为创建的项目名。 ::: 按照如下选择,生成项目: ```bash ? 请选择你想创建的工程类型: 应用 ? 请选择开发语言: TS ? 请选择包管理工具: pnpm ? 是否需要支持以下类型应用: 不需要 ? 是否需要调整默认配置: 否 ``` 现在我们生成的是一个前端项目,我们给该项目添加 BFF 功能,在项目根目录下,执行 `pnpm run new`,进行如下选择: ```bash ? 请选择你想要的操作:启用可选功能 ? 启用可选功能: 启用「BFF」功能 ? 请选择 BFF 类型:函数写法 ? 请选择运行时框架:Egg ``` 删除 `src/` 目录,这样我们的项目就是一个纯粹的 API 服务项目了。 :::info 补充信息 Modern.js 中的 BFF 和 API 服务支持四种不同的运行时框架,详细介绍请参考【[运行时框架](/docs/guides/features/server-side/bff/frameworks)】。 ::: ## IDE 支持 Modern.js 对 VS Code 等主流 IDE 提供了开箱即用的支持,具备 Lint 问题自动检测、自动修复,代码提交前的准入检查等功能特性,可以让代码开发更加高效和智能。详细介绍请参考【[确认编程环境](/docs/guides/tutorials/c03-ide/3.1-setting-up)】。 ## 开发调试 在项目的根目录,执行 `pnpm run dev` 即可启动开发服务器: ```bash # 启动开发服务器 pnpm run dev ``` 浏览器访问 `http://localhost:8080/api`,可以看到返回信息为:`{ "message": "Hello Modernjs" }`。 ### 开发接口 现在让我们开发一个报表接口,报表接口返回所有的用户信息。创建 `api/users` 文件,编写以下代码(需要先安装 axios 依赖): ```ts title=api/users.ts import axios from 'axios'; export default async (): Promise< { key: string; name: string; age: number; country: string }[] > => { const res = await axios.get( 'https://lf3-static.bytednsdoc.com/obj/eden-cn/beeh7uvzhq/users.json', ); return res.data; }; ``` 访问 `http://localhost:8080/api/users`,会返回一个数组,数组中的每一项是一个用户信息,如下所示: ```json [ { "key": "1", "name": "John Brown", "age": 32, "country": "America" }, { "key": "2", "name": "Jim Green", "age": 42, "country": "England" }, { "key": "3", "name": "Ming Li", "age": 30, "country": "China" } ] ``` ### 调试接口 通过 VS Code 的 Debugger 模式,我们可以很容易地调试 API 服务。 在 VS Code 的 Terminal 面板,选择 Debugger Terminal,执行 `pnpm run dev` 命令,运行 API 服务。 ![alt terminal](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/start/service-project-terminal.png) 打上断点,重新访问 `http://localhost:8080/api/users`,代码逻辑会在对应的断点暂停。 ![alt debug](https://lf3-static.bytednsdoc.com/obj/eden-cn/aphqeh7uhohpquloj/modern-js/start/debug.png) ### 获取单个用户 开发报表时,一个常见场景是过滤数据。现在,我们开发一个新接口,用于根据用于 ID 获取单个用户的信息。 首先创建 `api/users/[id].ts` 文件: ```bash touch api/users/[id].ts ``` :::info 补充信息 在 `api/` 目录下当文件名以 `[xxx]` 命名时,该文件的 BFF 函数会注册为动态路由;更多信息,请参考【[动态路由](/docs/guides/features/server-side/bff/function#/动态路由)】。 ::: 在服务端的代码中,我们需要获取到动态路由实际传入的参数。Modern.js 中提供了 [useContext](/docs/guides/features/server-side/bff/frameworks) API,用于获取请求的上下文信息。 在 `api/users/[id].ts` 文件中写以下代码: ```ts title=api/users/[id].ts import axios from 'axios'; import { useContext } from '@modern-js/plugin-egg'; interface User { key:string; name: string; age: number; country: string; } export default async () => { const res = await axios<User|string>.get( 'https://lf3-static.bytednsdoc.com/obj/eden-cn/beeh7uvzhq/users.json', ); const ctx = useContext(); const users: User[] = res.data; const user = users.find(user => user.key === ctx.params.id) return user || 'not found' }; ``` 这样我们获取用户数据的接口就开发完成了,访问 `http://localhost:8080/api/users/1`,会返回以下数据: ```json { "key": "1", "name": "John Brown", "age": 32, "country": "America" } ``` :::info 注 Modern.js 服务端的代码同样是支持热更新功能的,修改代码后无需重启服务。 ::: ### 连接数据库 在真实的业务场景中,API 服务经常需要查询数据库返回相关数据,下面会介绍 API 服务项目如何连接数据库。 我们以 MySQL 数据库为例: :::info 注 如果没有安装 MySQL,需要先进行[下载安装](https://dev.mysql.com/downloads/mysql/)。 ::: - 创建数据库实例: ```sql CREATE DATABASE modernjs; ``` - 选择数据库: ```sql use modernjs; ``` - 创建表: ```sql CREATE TABLE IF NOT EXISTS `users`( `id` INT UNSIGNED AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL, `age` INT(4) NOT NULL, `country` VARCHAR(100), PRIMARY KEY ( `id` ) )ENGINE=InnoDB DEFAULT CHARSET=utf8; ``` - 插入数据: ```sql INSERT INTO users (name, age, country) VALUES ("John Brown", 32, "America"); ``` - 查询数据: ```sql select * from users; ``` - 在项目根目录下安装 `mysql2`: ```bash pnpm add mysql2 ``` - 编写连接数据库的代码: ```ts const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'password', database: 'modernjs', }); ``` - 创建一个 `query` 函数,支持通过 `sql` 语句查询数据库: ```ts const query = (sql: string) => new Promise((resolve, reject) => { connection.query(sql, (error, results, fields) => { if (error) { reject(error); } resolve(results); }); }); ``` - 修改之前的示例,通过查询数据库获取数据: ```ts export default async (): Promise<User | string> => { const users: User[] = await query(`select * from users`); const ctx = useContext(); const user = users.find(user => user.id == ctx.params.id); return user || 'not found'; }; ``` - 访问 `http://localhost:8080/api/users/1`,会得到下面的数据: ```json { "id": 1, "name": "John Brown", "age": 32, "country": "America" } ``` ### 添加数据 有了数据库之后,我们可以开发添加数据的接口。以上述例子为基础,在 `api/users.ts` 文件中创建 `addUser` 函数: ```ts title=api/users.ts const addUser = ({ name, age, country, }: Pick<User, 'name' | 'age' | 'country'>) => new Promise((resolve, reject) => { connection.execute( ` INSERT INTO users ( name, age, country) VALUES ( "${name}", "${age}", "${country}" ); `, (error: Error, results) => { if (error) { reject(error); } resolve(results.insertId); }, ); }); ``` 然后创建 `post` 函数,并将该 `post` 函数导出: ```ts title=api/users.ts export const post = async ({ data, }: { data: Pick<User, 'name' | 'age' | 'country'>; }) => { const id: number = await addUser(data); return { id, }; }; ``` :::info 补充信息 这里声明的 `post` 函数会注册一条 `api/post` 的路由,具体函数参数及用法可以参考【[BFF 介绍](/docs/guides/features/server-side/bff/function)】。 ::: 现在我们在终端通过 [curl](https://curl.se/) 发送 POST 请求到 `http://localhost:8080/api/users`,创建一个新用户: ```shell curl -H "Content-类型: application/json" -X POST -d '{"name":"Modernjs", "age": 0, "country": "China"}' http://localhost:8080/api/users ``` 再次访问 `http://localhost:8080/api/users`,可以看到新增了一个用户: ```json [ { "id": 1, "name": "John Brown", "age": 32, "country": "America" }, { "id": 2, "name": "Jim Green", "age": 42, "country": "England" }, { "id": 3, "name": "Ming Li", "age": 30, "country": "China" }, { "id": 4, "name": "Modernjs", "age": 0, "country": "China" } ] ``` ### 使用 Schema 校验请求参数 Modern.js 支持对接收到的请求做参数的类型校验。我们以 `post` 函数为例,对请求数据和响应数据做类型校验: ```ts title=api/users.ts import { match, String, Number } from '@modern-js/runtime/server'; export const post = match( { request: { data: { name: String, age: Number, country: String, }, }, response: { id: Number, }, }, async request => { const id: number = await addUser(request.data); return { id, }; }, ); ``` :::info 补充信息 更多用法,请参考【[携带 Schema 的 BFF 函数](/docs/guides/features/server-side/bff/schema)】。 ::: 当我们发送符合数据类型格式的数据时,会正常返回新增数据的 `id`。 ```shell curl -H "Content-类型: application/json" -X POST -d '{"name":"Modernjs", "age": 0, "country": "China"}' http://localhost:8080/api/users ``` 当我们发送的数据类型不符合数据格式要求,就会返回 400 的错误信息,例如: ```shell curl -H "Content-类型: application/json" -X POST -d '{"name":"Modernjs", "age": "age", "country": "China"}' http://localhost:8080/api/users ``` ```shell path: ["data","age"] age is not a number ``` ### 使用 BFF 代理 在一些场景下,我们只是做接口的转发;此时我们并不需要像前面所述,从零开发一个 API 服务的接口,而是可以直接使用 Modern.js 提供的 BFF 代理功能做请求转发。 例如,我们需要代理 [CNode](https://cnodejs.org/) 社区首页的接口,可以在项目的 `modern.config.js` 下,增加以下配置: ```js title="modern.config.js" export default defineConfig({ bff: { proxy: { '/api/v1/topics': 'https://cnodejs.org', }, }, }); ``` 现在访问 `http://localhost:8080/api/v1/topics`,就会得到 [CNode](https://cnodejs.org/) 首页的数据。 :::info 补充信息 更多用法,请参考【[BFF 代理](/docs/apis/config/bff/proxy)】。 更多 Modern.js 的代理方式请参考【[调试代理和 Mock](/docs/guides/usages/debug/proxy-and-mock)】。 ::: ## 框架写法 上述示例中,我们使用的是 Modern.js 中的 [BFF 函数写法](/docs/guides/features/server-side/bff/function)。 使用这种写法,我们只需要简单地定义一些服务端的处理函数,就能开发 API 服务接口。但当我们项目越来越复杂时,就无法避免地需要对项目进行分层,需要更多的框架元素。此时我们可以使用**框架写法**。 以 Egg 为例,当使用**框架写法**时,可以在 `api/lambda/` 下写 **BFF 函数**,其他 `api/` 目录下的文件约定与 Egg 保持一致。所以 Egg 原有提供的能力,如 service、插件、定时器等在**框架写法**下都是支持的。 ```bash title="egg 框架写法的目录结构" . ├── api/ | ├── app.ts (可选) | ├── agent.ts (可选) | ├── app (可选) | | ├── router.ts (可选) | │ ├── controller (可选) | │ | └── home.ts | │ ├── service (可选) | │ | └── user.ts | │ ├── middleware (可选) | │ | └── response_time.ts | │ ├── schedule (可选) | │ | └── my_task.ts | │ └── extend (可选) | │ ├── helper.ts (可选) | │ ├── request.ts (可选) | │ ├── response.ts (可选) | │ ├── context.ts (可选) | │ ├── application.ts (可选) | │ └── agent.ts (可选) │ ├── config/ │ │ └── config.default.ts │ └── lambda/ │ └── hello.ts ├── modern.config.js ├── package.json ├── pnpm-lock.yaml ├── tsconfig.json ``` :::info 补充信息 在 Egg 中 router.ts 和 controller 必须存在,而 Modern.js 可以通过函数编写路由,因此这两部分内容是可选的。 更多信息,请参考【[BFF 框架写法](/docs/guides/features/server-side/bff/frameworks#框架写法)】。 ::: ## 测试 :::tip 提示 近期上线,敬请期待。 ::: ## 部署 :::tip 提示 近期上线,敬请期待。 :::