Basic Usage

Applications developed with Modern.js can define API functions in the api/ directory, which can be called by the front-end to send requests without writing front and back-end glue layer code, At the same time, it ensures the type safety of the front and back end

Enable BFF

  1. Execute pnpm new and select "Enable BFF"
  2. Add the following code to modern.config.[tj]s according to the chosen runtime framework:
Express.js
Koa.js
modern.config.ts
import { expressPlugin } from '@modern-js/plugin-express';
import { bffPlugin } from '@modern-js/plugin-bff';

export default defineConfig({
  plugins: [expressPlugin(), bffPlugin()],
});

BFF Function

The functions that are allowed to be called through integration are called BFF functions. Here is the simplest BFF function to write, creating an api/hello.ts file:

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

Then directly import the function in src/App.tsx and call:

src/App.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>;
};
INFO

Modern.js generator has already configured the @api alias in tsconfig.json, so you can import functions directly by aliases.

The functions import in src/App.tsx will be automatically converted into interface calls, so there is no need to call the interface through fetch.

Execute pnpm run dev, then open http://localhost:8080/ to see that the page has displayed the content returned by the BFF function. In Network, you can see that the page sent a request to http://localhost:8080/api/hello.

Network

API Routes

In Modern.js, the BFF function routing system is implemented based on the file system, and it is also a conventional routing system.

In Function Mode, All files under api/ will map to an interface. In Framework Mode, All files under api/lambda will map to an interface

NOTE

Function Mode & Framework Mode will introduce soon.

All routes generated by BFF functions have a prefix, and the default value is /api. The prefix can be set through bff.prefix.

Several routing conventions are described as follow.

Default Route

Files named index.[jt]s are mapped to the previous directory.

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

Multi-layer Routing

Supports parsing nested files, if you create a nested folder structure, the files will still automatically parse routes in the same way.

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

Dynamic Routing

Create folders or files named with [xxx] to support dynamic named routing parameters.

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

Allow List

By default, all files in the'api/'directory will be parsed as BFF function files, but the following files will not be parsed:

  • file name start with _, for example _utils.ts.
  • files in directory which name start with _, for example _utils/index.ts_utils/cp.ts.
  • test files, for example foo.test.ts.
  • type files, for example hello.d.ts.
  • files in node_module.

RESTful API

Modern.js BFF functions need to be defined according to the RESTful API standard, follow the HTTP Method specification, and do not allow free parameter definition.

INFO

Assuming that the function allows free definition of parameters, the resulting route must be called by the private protocol (the reason is that the request parameters cannot be distinguished from the request body), and cannot implement any RESTful API.

If the service is only used for the application itself, there is no problem. but its non-standard interface definition cannot be integrated into the larger system. In the case of multiple systems working together (such as BFF low-code construction), other systems also need to follow the private protocol.

Function Named Export

Modern.js the export name of the BFF function determines the Method of the corresponding interface of the function, such as get, post and so on.

For example, following the example, a GET interface can be exported.

export const get = async () => {
  return {
    name: 'Modern.js',
    desc: 'Web engineering system',
  };
};

Following the example below, a POST interface can be exported.

export const post = async () => {
  return {
    name: 'Modern.js',
    desc: 'Web engineering system',
  };
};
  • Modern.js supports 9 definitions for HTTP Method: GETPOSTPUTDELETECONNECTTRACEPATCHOPTIONSHEAD, can be exported using these methods as functions.

  • The name is size insensitive, if GET, can write getGetGEtGET, can be accurately identified. But default export as export default xxx will be map to Get.

  • Multiple functions of different Methods can be defined in one file, but if multiple functions of the same Method are defined, only the first will take effect.

INFO

It should be noted that the defined functions should all be asynchronous, which is related to the type when the function is called, which will be mentioned later.

Function Parameter Rule

As mentioned above, in order to meet the design criteria of RESTful APIs, the BFF function in Modern.js needs to follow certain imported parameter rules.

The function parameters are divided into two parts, the dynamic part in the request path and the request option RequestOption.

Dynamic Path

Dynamic routing will be used as imported parameters in the first part of the function, and each imported parameter corresponds to a dynamic route. For example, in the following example, uid will be passed into the function as the first two parameters:

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

Pass dynamic parameters directly when calling:

App.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

The parameter after Dynamic Path is the object RequestOption containing querystring and request body, which is used to define the types of data and query.

In normal functions without dynamic routing, the incoming data and query can be obtained from the first imported parameter, for example:

api/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
}

When a function file uses dynamic routing rules, dynamic routing before the RequestOption parameter.

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

Also pass in the parameters according to the function definition:

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

As mentioned earlier, the defined functions should be asynchronous because they are automatically converted to HTTP interface calls when called by the front end.

so in order to keep the type definition consistent with the actual calling, it is necessary to set the BFF function to asynchronous when defining it.