logo
  • Guide
  • Config
  • Plugin
  • API
  • Examples
  • Community
  • Modern.js 2.x Docs
  • English
    • 简体中文
    • English
    • Start
      Introduction
      Quick Start
      Upgrading
      Glossary
      Tech Stack
      Core Concept
      Page Entry
      Build Engine
      Web Server
      Basic Features
      Routes
      Routing
      Config Routes
      Data Solution
      Data Fetching
      Data Writing
      Data Caching
      Rendering
      Server-Side Rendering
      Streaming SSR
      Rendering Cache
      Static Site Generation
      Render Preprocessing
      Styling
      Styling
      Use CSS Modules
      Using CSS-in-JS
      Using Tailwind CSS
      HTML Template
      Import Static Assets
      Import JSON Files
      Import SVG Assets
      Import Wasm Assets
      Debug
      Data Mocking
      Network Proxy
      Using Rsdoctor
      Using Storybook
      Testing
      Playwright
      Vitest
      Jest
      Cypress
      Path Alias
      Environment Variables
      Output Files
      Deploy Application
      Advanced Features
      Using Rspack
      Using BFF
      Basic Usage
      Runtime Framework
      Extend BFF Server
      Extend Request SDK
      File Upload
      Cross-Project Invocation
      Optimize Page Performance
      Code Splitting
      Inline Static Assets
      Bundle Size Optimization
      React Compiler
      Improve Build Performance
      Browser Compatibility
      Low-Level Tools
      Source Code Build Mode
      Server Monitor
      Monitors
      Logs Events
      Metrics Events
      Internationalization
      Basic Concepts
      Quick Start
      Configuration
      Locale Detection
      Resource Loading
      Routing Integration
      API Reference
      Advanced Usage
      Best Practices
      Custom Web Server
      Topic Detail
      Module Federation
      Introduction
      Getting Started
      Application-Level Modules
      Server-Side Rendering
      Deployment
      Integrating Internationalization
      FAQ
      Dependencies FAQ
      CLI FAQ
      Build FAQ
      HMR FAQ
      Deprecated
      📝 Edit this page
      Previous pageUsing BFFNext pageRuntime Framework

      #Basic Usage

      In a Modern.js application, developers can define API files under the api/lambda directory and export API functions from these files. In the frontend code, these API functions can be directly invoked by importing the file, which initiates the API requests.

      This invocation method is called unified invocation, where developers do not need to write glue code for the frontend and backend separately, thereby ensuring type safety across both.

      #Enable BFF

      1. Execute the new command:
      npm
      yarn
      pnpm
      bun
      deno
      npm run new
      yarn run new
      pnpm run new
      bun run new
      deno run npm:new
      1. Follow the prompts to enable BFF functionality:
      ? Please select the operation you want to perform Enable optional features
      ? Please select the feature to enable Enable "BFF"
      1. Depending on the chosen runtime framework, add the following code to modern.config.[tj]s:
      modern.config.ts
      import { bffPlugin } from '@modern-js/plugin-bff';
      
      export default defineConfig({
        plugins: [bffPlugin()],
      });

      #BFF Functions

      Functions that allow unified invocation are called BFF Functions. Here is an example of the simplest BFF function. First, create the api/lambda/hello.ts file:

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

      Then, import and invoke the function directly in src/routes/page.tsx:

      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

      After running the new command, the Modern.js generator will automatically configure the @api alias in tsconfig.json, allowing you to import functions directly using the alias.

      The function imported in src/routes/page.tsx will be automatically converted into an API call, eliminating the need to use an SDK or Web Fetch to call the API.

      After running pnpm run dev, open http://localhost:8080/ and you can see that the page displays the content returned by the BFF function. In the Network tab, you can see a request was made to http://localhost:8080/api/hello.

      Network

      #Function Routes

      In Modern.js, the routing system for BFF functions is implemented based on the file system, which is another form of conventional routing. Each BFF function in the api/lambda directory is mapped to an API route. Here are some routing conventions.

      Info

      All routes generated by BFF functions have a common prefix, which defaults to /api. This can be configured using bff.prefix.

      #Default Routes

      Files named index.[jt]s will be mapped to the parent directory.

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

      #Nested Routes

      Nested directories are supported, and files will be automatically parsed into routes in the same way.

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

      #Dynamic Routes

      Similarly, creating a directory or file with [xxx] in the name supports dynamic route parameters. The rules for dynamic route function parameters can be found in dynamic-path.

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

      #Whitelist

      By default, all files under the api/lambda/ directory are parsed as BFF function files, but the following files are ignored:

      • Files starting with an underscore _. For example: _utils.ts.
      • Files under directories starting with an underscore _. For example: _utils/index.ts, _utils/cp.ts.
      • Test files. For example: foo.test.ts.
      • TypeScript type files. For example: hello.d.ts.
      • Files under node_modules.

      #RESTful API

      Modern.js BFF functions need to follow RESTful API standards for definition. Developers must define BFF functions according to a set of rules.

      Design Principles

      BFF functions should not only be invoked within the project but also be accessible to other projects via an SDK or Web fetch. Therefore, Modern.js does not define a private protocol for unified invocation but uses standard HTTP methods along with common HTTP request parameters like params, query, and body to define functions.

      #Function Export Rules

      #HTTP Method Named Functions

      Modern.js BFF functions' export names determine the HTTP method for the corresponding API, such as get, post, etc. For example, to export a GET API:

      export const get = async () => {
        return {
          name: 'Modern.js',
          desc: 'A modern web engineering solution',
        };
      };

      The following example exports a POST API:

      export const post = async () => {
        return {
          name: 'Modern.js',
          desc: 'A modern web engineering solution',
        };
      };
      • Modern.js supports 9 HTTP methods: GET, POST, PUT, DELETE, CONNECT, TRACE, PATCH, OPTIONS, and HEAD, which can be used as function export names.

      • Names are case-insensitive. If the method is GET, it can be written as get, Get, GEt, or GET, and the default export, i.e., export default xxx, will be mapped to Get.

      #Using Async Functions

      Modern.js recommends defining BFF functions as async functions, even if there is no asynchronous process in the function, for example:

      export const get = async () => {
        return {
          name: 'Modern.js',
          desc: 'A modern web engineering solution',
        };
      };

      This is because, during frontend invocation, the BFF function will be automatically converted into an HTTP API call, and HTTP API calls are asynchronous. On the frontend, it is typically used like this:

      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>;
      };

      Therefore, to keep the type definitions consistent with the actual invocation experience, we recommend defining BFF functions as async functions.

      #Function Parameter Rules

      Function parameter rules are divided into two parts: dynamic routes in the request path (Dynamic Path) and request options (RequestOption).

      #Dynamic Path

      Dynamic routes will be the first part of the BFF function parameters, with each parameter corresponding to a segment of the dynamic route. For example, the level and id parameters will be passed to the function in the following example:

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

      Invoke the function by directly passing in the dynamic parameters:

      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

      Parameters following the dynamic path are an object called RequestOption, which includes the query string and request body. This field is used to define the types for data and query.

      In a standard function without dynamic routes, RequestOption can be obtained from the first parameter, for example:

      api/lambda/hello.ts
      import type { RequestOption } from '@modern-js/plugin-bff/server';
      
      export async function post({
        query,
        data,
      }: RequestOption<Record<string, string>, Record<string, string>>) {
        // do something
      }

      Custom types can also be used here:

      api/lambda/hello.ts
      import type { RequestOption } from '@modern-js/plugin-bff/server';
      
      type IQuery = {
        // some types
      };
      type IData = {
        // some types
      };
      
      export async function post({ query, data }: { query: IQuery; data: IData }) {
        // do something
      }

      When the function file uses dynamic routing, dynamic routes will precede the RequestOption object parameter.

      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
      }

      Pass the corresponding parameters when invoking the function according to its definition:

      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}>Add SKU</div>;
      };

      #Extend BFF Function

      The standard BFF function writing method may not always meet your needs. We are designing a more powerful BFF function writing method to allow developers to extend BFF functions more conveniently.

      Note

      Coming soon

      #Code Sharing

      Besides the BFF functions in the api/ directory, which can be referenced in the src/ directory through an integrated calling method, the src/ and api/ directories cannot directly reference each other's code by default. To achieve code sharing, a shared directory can be created at the root of the project for both src/ and api/ to use commonly.