> For AI agents: the complete documentation index is available at /llms.txt, the full documentation bundle is available at /llms-full.txt.

# Runtime Framework

Modern.js and the UltraModern BFF extension provide two runtime frameworks:

- `hono` is the native plugin default and uses file-convention handlers from `api/lambda/**`.
- `effect` is the UltraModern extension default and uses [Effect HttpApi](https://effect.website/) from `api/index`.

`effect` and `hono` are strict runtime modes. There is no automatic fallback between them.

::::info
Generated UltraModern workspaces use strict Effect APIs: author HTTP APIs in
`shared/api.ts` and `api/index.ts`, not Hono/file-convention handlers.
::::

## Switch to Effect runtime

Use the fork build plugin below. It includes the native BFF plugin and registers
the Effect adapter. The application needs `@modern-js/bff-effect` and
`@modern-js/plugin-bff-extensions` as production dependencies from the same
framework cohort. Install the exact Effect peers described in
[`bff.effect`](/configure/app/bff/effect.md).

```ts title="modern.config.ts"
import { bffPlugin } from '@modern-js/plugin-bff-build-extensions';
import { appTools, defineConfig } from '@modern-js/app-tools';

export default defineConfig({
  plugins: [appTools(), bffPlugin()],
  bff: {
    runtimeFramework: 'effect',
    effect: {
      entry: './api/index',
      strictEffectApproach: true,
      openapi: {
        path: '/openapi.json',
      },
    },
  },
});
```

Define a shared Effect HttpApi contract (for type-safe client + server):

```ts title="shared/api.ts"
import {
  HttpApi,
  HttpApiEndpoint,
  HttpApiGroup,
  Schema,
} from '@modern-js/bff-effect/effect-client';

export const bffApi = HttpApi.make('MyApi').add(
  HttpApiGroup.make('hello').add(
    HttpApiEndpoint.get('ping', '/ping', {
      success: Schema.String,
    }),
  ),
);
```

Implement your Effect API entry at `api/index.ts`:

```ts title="api/index.ts"
import { defineEffectBff } from '@modern-js/bff-effect/effect';
import * as Context from 'effect/Context';
import * as Effect from 'effect/Effect';
import * as Layer from 'effect/Layer';
import * as Schema from 'effect/Schema';
import { HttpApiBuilder } from 'effect/unstable/httpapi';
import { bffApi } from '../shared/api';

class GreetingUnavailableError extends Schema.TaggedError<GreetingUnavailableError>()(
  'GreetingUnavailableError',
  {
    message: Schema.String,
  },
) {}

class GreetingService extends Context.Service<GreetingService>()('GreetingService', {
  make: Effect.succeed({
    hello: Effect.fn('GreetingService.hello')(function* () {
      if (Date.now() < 0) {
        return yield* Effect.fail(
          new GreetingUnavailableError({
            message: 'Greeting service is unavailable',
          }),
        );
      }
      return yield* Effect.succeed('pong');
    }),
  }),
}) {
  static readonly layer = Layer.effect(this, this.make);
}

const group = HttpApiBuilder.group(bffApi, 'hello', handlers =>
  handlers.handle('ping', () =>
    GreetingService.use(service => service.hello()).pipe(
      Effect.catchTag('GreetingUnavailableError', error =>
        Effect.succeed(error.message),
      ),
    ),
  ),
);

const layer = HttpApiBuilder.layer(bffApi).pipe(
  Layer.provide(group),
  Layer.provide(GreetingService.layer),
);

export default defineEffectBff({ api: bffApi, layer });
```

Create a native, fully inferred client from the shared contract:

```ts title="src/routes/page.tsx"
import { Effect, makeEffectHttpApiClient } from '@modern-js/bff-effect/effect-client';
import { bffApi } from '../../shared/api';

const response = await Effect.runPromise(
  makeEffectHttpApiClient(bffApi, { baseUrl: '/api' }).pipe(
    Effect.flatMap(client => client.hello.ping({})),
  ),
);
```

Requests, responses, and declared errors are inferred from `bffApi`. No client generation or server-entry import is needed.

For UltraModern, Effect `HttpApi` plus Effect BFF is the single blessed authored HTTP path. Use `HttpApi` endpoints with `query`, `params`, `payload`, `success`, and declared errors such as `HttpApiSchema.status(...)`; implement them with `HttpApiBuilder.group(...).handle(...)` and `HttpApiBuilder.layer(...).pipe(Layer.provide(...))`, then default-export the entry as `defineEffectBff({ api, layer })`. See `packages/server/bff-effect/tests/effect-edge-runtime.test.ts` for the live runtime shape.

Native Hono applications import operators from `@modern-js/plugin-bff/server`.
UltraModern-generated applications keep their strict Effect API model. Worker
handlers use `@modern-js/bff-effect/effect-edge`, including its worker request
context exports; Node handlers use the Effect entry shown above.

### Getting Request Context

Sometimes in BFF functions, it's necessary to obtain the request context to handle more logic. In such cases, you can use `useHonoContext` to get it:

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

export const get = async () => {
  const c = useHonoContext();
  console.info(`access url: ${c.req.url}`);
  return 'Hello Modern.js';
};
```

:::info
For more details, refer to [useHonoContext](/apis/app/runtime/bff/use-backend-context.md).
:::

### Getting Cookies

When getting cookies in BFF functions, you need to get the request context through `useHonoContext`, then use `c.req.header('cookie')` to get the Cookie string and parse it manually:

```ts title="api/lambda/cookies.ts"
import { Api, Get } from '@modern-js/plugin-bff/server';
import { useHonoContext } from '@modern-js/server-runtime';

// Helper function to parse Cookie string
function parseCookies(
  cookieHeader: string | undefined,
): Record<string, string> {
  const cookies: Record<string, string> = {};
  if (!cookieHeader) return cookies;

  cookieHeader.split(';').forEach(cookie => {
    const [name, ...rest] = cookie.trim().split('=');
    if (name) {
      cookies[name] = rest.join('=');
    }
  });

  return cookies;
}

export const getCookies = Api(Get('/cookies'), async () => {
  const c = useHonoContext();
  const cookieHeader = c.req.header('cookie');
  const cookies = parseCookies(cookieHeader);
  const token = cookies.token;
  const sessionId = cookies.sessionId;
  return {
    hasToken: !!token,
    token: token || null,
    sessionId: sessionId || null,
  };
});
```

:::caution Note
The `c.req.cookie()` method does not exist in the current version. You need to use `c.req.header('cookie')` to get the Cookie string and parse it manually.
:::

### Defining BFF Functions

When using Hono as the runtime framework, you can define interfaces through [Api functions](/guides/advanced-features/bff/operators.md):

```ts title="api/lambda/user.ts"
import { Api, Get, Query } from '@modern-js/plugin-bff/server';
import { z } from 'zod';

const QuerySchema = z.object({
  id: z.string(),
});

export const getUser = Api(
  Get('/user'),
  Query(QuerySchema),
  async ({ query }) => {
    return {
      id: query.id,
      name: 'Modern.js',
      email: 'modernjs@bytedance.com',
    };
  },
);
```

:::info
For more details about Api functions and operators, refer to [Creating Extensible BFF Functions](/guides/advanced-features/bff/operators.md).
:::

### Using Middleware

Hono supports a rich middleware ecosystem, and you can use middleware in BFF functions:

```ts title="api/lambda/user.ts"
import { Api, Get, Middleware } from '@modern-js/plugin-bff/server';

export const getUser = Api(
  Get('/user'),
  Middleware(async (c, next) => {
    // You can access Hono's Context in middleware
    c.res.headers.set('X-Powered-By', 'Modern.js');
    await next();
  }),
  async () => {
    return {
      name: 'Modern.js',
      email: 'modernjs@bytedance.com',
    };
  },
);
```

:::info
For more details about middleware, refer to [Creating Extensible BFF Functions](/guides/advanced-features/bff/operators.md#middleware).
:::

### More Hono Documentation

For more detailed information about Hono, please refer to the [Hono official documentation](https://hono.dev/).
