# 基础用法

在 Modern.js 应用中，开发者可以在 `api/lambda` 目录下定义接口文件，并导出接口函数。在前端代码中，可以用文件引用的方式，直接调用这些接口函数，发起接口请求。

这种调用方式我们称为**一体化调用**，开发者无需编写前后端胶水层代码，并天然地保证前后端类型安全。

## 启用 BFF

import EnableBFF from "@site-docs/components/enable-bff"

<EnableBFF/>

## BFF 函数

允许使用一体化调用的函数，我们称为 **BFF 函数**。这里写一个最简单的 BFF 函数，首先创建 `api/lambda/hello.ts` 文件：

```ts title="api/lambda/hello.ts"
export const get = async () => 'Hello Modern.js';
```

接着在 `src/routes/page.tsx` 中直接引入函数并调用：

```tsx title="src/routes/page.tsx"
import { useState, useEffect } from 'react';
import { get as hello } from '@api/hello';

export default () => {
  const [text, setText] = useState('');

  useEffect(() => {
    hello().then(setText);
  }, []);
  return <div>{text}</div>;
};
```

:::tip
在调用 `new` 命令后，Modern.js 生成器会自动在 `tsconfig.json` 中配置 `@api` 别名，因此可以直接通过别名的方式引入函数。

:::

在 `src/routes/page.tsx` 中引入的函数，会自动转换成接口调用，不需要再通过请求 SDK 或 Web Fetch 调用接口。

执行 `pnpm run dev` 后，打开 `http://localhost:8080/` 可以看到页面已经展示了 BFF 函数返回的内容，在 Network 中可以看到页面向 `http://localhost:8080/api/hello` 发送了请求：

![Network](https://p6-piu.byteimg.com/tos-cn-i-8jisjyls3a/fd41750f8d414179a9b4ecb519919b36~tplv-8jisjyls3a-3:0:0:q75.png)

## 函数路由

Modern.js 中，BFF 函数对应的路由系统是基于文件系统实现的，也是一种**约定式路由**。`api/lambda` 下的所有文件中的每个 BFF 函数都会映射为一个接口，下面介绍几种路由的约定。

:::info
所有 BFF 函数生成的路由都带有统一的前缀，默认值为 `/api`。可以通过 [bff.prefix](/configure/app/bff/prefix) 设置公共路由的前缀。
:::

### 默认路由

以 `index.[jt]s` 命名的文件会被映射到上一层目录。

- `api/lambda/index.ts` -> `{prefix}/`
- `api/lambda/user/index.ts` -> `{prefix}/user`

### 多层路由

支持解析嵌套的文件，如果创建嵌套文件夹结构，文件仍会以相同方式自动解析路由。

- `api/lambda/hello.ts` -> `{prefix}/hello`
- `api/lambda/user/list.ts` -> `{prefix}/user/list`

### 动态路由

同样的，创建命名带有 `[xxx]` 的文件夹或者文件，支持动态的命名路由参数。动态路由的函数参数规则可以看 [dynamac-path](/guides/advanced-features/bff/function#dynamic-path)。

- `api/lambda/user/[username]/info.ts` -> `{prefix}/user/:username/info`
- `api/lambda/user/username/[action].ts` -> `{prefix}/user/username/:action`

### 白名单

默认 `api/lambda/` 目录下所有文件都会当作 BFF 函数文件去解析，但以下文件不会被解析：

- 命名以 `_` 开头的文件。例如：`_utils.ts`。
- 命名以 `_` 开头的文件夹下所有文件。例如：`_utils/index.ts`、`_utils/cp.ts`。
- 测试文件。例如：`foo.test.ts`。
- TypeScript 类型文件。例如：`hello.d.ts`。
- `node_module` 下的文件。

## RESTful API

Modern.js 的 BFF 函数需要遵循 RESTful API 标准来定义，开发者需要按照一系列规则来定义 BFF 函数。

:::tip 设计原则
BFF 函数不仅会在项目中被调用，也应该允许其他项目通过请求 SDK 或 Web fetch 调用。因此 Modern.js 没有在一体化调用时定义**私有协议**，而是通过标准的 HTTP Method，以及 `params`、`query`、`body` 等通用的 HTTP 请求参数来定义函数。

:::

### 函数导出规则

#### HTTP Method 具名函数

Modern.js BFF 函数的导出名决定了函数对应接口的 HTTP Method，如 `get`，`post` 等。例如导出一个 GET 接口：

```ts
export const get = async () => {
  return {
    name: 'Modern.js',
    desc: '现代 web 工程方案',
  };
};
```

按照以下例子，则可导出一个 `POST` 接口：

```ts
export const post = async () => {
  return {
    name: 'Modern.js',
    desc: '现代 web 工程方案',
  };
};
```

- 对应 HTTP Method，Modern.js 也支持了 9 种定义，即：`GET`、`POST`、`PUT`、`DELETE`、`CONNECT`、`TRACE`、`PATCH`、`OPTIONS`、`HEAD`，即可以用这些 Method 作为函数导出的名字。

- 名字是大小不敏感的，如果是 `GET`，写成 `get`、`Get`、`GEt`、`GET`，都可以准确识别。而默认导出，即 `export default xxx` 则会被映射为 `Get`。

#### 使用 Async 函数

Modern.js 推荐将 BFF 函数定义为 Async 异步函数，即使函数中不存在异步流程，例如：

```ts
export const get = async () => {
  return {
    name: 'Modern.js',
    desc: '现代 web 工程方案',
  };
};
```

这是因为在前端调用时，BFF 函数会自动转换成 HTTP 接口调用，而 HTTP 接口调用时异步的，在前端通常会这样使用：

```tsx title="src/routes/page.tsx"
import { useState, useEffect } from 'react';
import { get as hello } from '@api/hello';

export default () => {
  const [text, setText] = useState('');

  useEffect(() => {
    hello().then(setText);
  }, []);
  return <div>{text}</div>;
};
```

因此，为了保持类型定义与实际调用体验统一，我们推荐在定义 BFF 函数时将它设置为异步函数。

### 函数参数规则

函数参数规则分为两块，分别是请求路径中的动态路由（Dynamic Path）和请求选项（RequestOption）。

#### Dynamic Path

动态路由会作为 BFF 函数第一部分的入参，每个入参对应一段动态路由。例如以下示例，`level` 和 `id` 会作为前两个参数传递到函数中：

```ts title="api/lambda/[level]/[id].ts"
export default async (level: number, id: number) => {
  const userData = await queryUser(level, uid);
  return userData;
};
```

在调用时直接传入动态参数：

```tsx title="src/routes/page.tsx"
import { useState, useEffect } from 'react';
import { get as getUser } from '@api/[level]/[id]';

export default () => {
  const [name, setName] = useState('');

  useEffect(() => {
    getUser(6, 001).then(userData => setName(userData.name));
  }, []);

  return <div>{name}</div>;
};
```

#### RequestOption

Dynamic Path 之后的参数是包含 querystring、request body 的对象 `RequestOption`，这个字段用来定义 `data` 和 `query` 的类型。

在不存在动态路由的普通函数中，可以从第一个入参中获取传入的 `data` 和 `query`，例如：

```ts title="api/lambda/hello.ts"
import type { RequestOption } from '@modern-js/runtime/server';

export async function post({
  query,
  data,
}: RequestOption<Record<string, string>, Record<string, string>>) {
  // do somethings
}
```

这里你也可以使用自定义类型：

```ts title="api/lambda/hello.ts"
import type { RequestOption } from '@modern-js/runtime/server';

type IQuery = {
  // some types
};
type IData = {
  // some types
};

export async function post({ query, data }: { query: IQuery; data: IData }) {
  // do somethings
}
```

当函数文件使用动态路由规则时，动态路由会在 `RequestOption` 对象参数前。

```ts title="api/lambda/[sku]/[id]/item.ts"
export async function post(
  sku: string,
  id: string,
  {
    data,
    query,
  }: RequestOption<Record<string, string>, Record<string, string>>,
) {
  // do somethings
}
```

调用时也按照函数定义，传入对应的参数即可：

```ts title="src/routes/page.tsx"
import { post } from '@api/[sku]/[id]/item';

export default () => {
  const addSku = () => {
    post('0001' /* sku */, '1234' /* id */, {
      query: {
        /* ... */
      },
      data: {
        /* ... */
      },
    });
  };

  return <div onClick={addSku}>添加 SKU</div>;
};
```

## 扩展 BFF 函数

import ExtendBFF from "@site-docs/components/extend-bff-function"

<ExtendBFF/>

## 代码共享

除 `api/` 目录下的 BFF 函数可在 `src/` 中通过一体化调用方式引用，项目中 `src/` 和 `api/` 目录默认不能直接引用对方代码。为实现代码共享，可在项目根目录创建 [`shared`](/apis/app/hooks/shared.html) 目录，供 `src/` 和 `api/` 共同引用。
