Composable Backend-for-Frontend
Tags: node, webdev, typescript, react
Published: 2024-05-15 18:12:14


The Backend-for-Frontend (BFF) pattern is an architectural approach where a dedicated backend service is created for each frontend application or client type. 

Utilizing the BFF pattern allows developers to tailor the APIs and backend services to the specific requirements of each client. This can result in improved performance, better security controls, and a more cohesive user experience.

For the BFF architecture to be effective and sustainable, it has to be composable. BFFs should be assembled using loosely coupled, interoperable components that can be easily combined, reused, or replaced. 

---

A "BFF component" in a composable system is a standardized abstract layer for specific technical tasks or services built in-house or bought from different vendors. 

Components in this system should adhere to "rules of communication" agreed upon across teams to make them easily pluggable into the wider system architecture. 

A well-designed system of "BFF components" facilitates innovation and allows businesses to quickly respond to new opportunities or threats.

---

In this article, we’ll explore [Bit Harmony](https://bit.dev/docs/harmony-platform/harmony-platform), an innovative framework for the composition of full-stack components using smart dependency injection. The platform uses [Bit components](https://bit.dev) as its building blocks.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lmu9udv2oq9jxe9ahzf0.jpg)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wweekjusy3rr42zcfnlw.png)


Visit the ["LazyWorks Inc."](https://bit.cloud/lazyworks) Bit organization to see a demo of this approach.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ecdr4h0yhm0gufgr2wy.png)


## Aspects

An aspect is the building block of a Harmony solution. It is a full-stack Bit component that provides UI and backend “services” for other aspects.

An aspect represents a single feature that can be plugged into a larger system to form a full solution, a new application. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yc03tknj937sib2d2zr4.jpg)

Aspects extend other aspects by registering to their ‘slot’ API. This inversion of control allows teams to compose features with minimal overhead since the aspect is responsible for the integration, not the system that composes it.

For example, [the following Harmony app](https://bit.cloud/lazyworks/no-sweat-shop/no-sweat-shop) is an online shop for lazy living. The team responsible for that online store decided to add a blog to their site. After searching for a suitable aspect on the [Bit platform](https://bit.cloud/), they came across [this Contentful-based Blog aspect](https://bit.cloud/lazyworks/blog). Deciding they wanted to use it, they added it to their Hamrony application:

```ts
/**
 * @coponentId: lazyworks.no-sweat-shop/no-sweat-shop
 * @filename: no-sweat-shop.bit-app.ts
 */ 

// imports...

/**
 * The "LazyWorks Platform" defines how an 
 * aspect can integrate into the system 
 * (i,e, register new routes, 
 * extend the GraphQL schema, etc) 
 */
import { LazyworksPlatformAspect } from '@lazyworks/platform.lazyworks-platform';
import { NoSweatShopPlatformAspect } from '@lazyworks/no-sweat-shop.no-sweat-shop-platform';
import { BlogAspect } from '@lazyworks/blog.blog';

export const NoSweatShop = HarmonyPlatform.from({
  name: 'no-sweat-shop',
  platform: [
   /**
    * aspects register themselves to the 'platform' aspect which
    * is the entry point for this application
    */
    LazyworksPlatformAspect,
    {
      name: "No Sweat Shop",
      slogan: "Effortless Living, Delivered.",
      domain: "no-sweat-shop.com",
      logo: "https://static.bit.dev/extensions-icons/my-project.svg",
    },
  ],
  /**
    * aspects can run in multiple runtime environments. here, aspects 
    * provide functionalities to the NodeJS services and the web frontend apps
   */
  runtimes: [new BrowserRuntime(), new NodeJSRuntime()],

  aspects: [
    /* 'no sweat shop' aspect extends the system with its 
     * own unique functionalities. this aspect is maintained by 
     * a team that composes the aspects for their own solution.
     */
    NoSweatShopPlatformAspect,
    /**
     * the blog aspect extends the system with content 
     * management capabilities.
     */
    [
      BlogAspect,
      {
       /**
         * the blog aspect also provides a config API for this app to use
         * in this case, since the blog uses the Contentful platform,
         * the team needs to provide it with their own Contentful space ID
         */        
        spaceId: 'contentful-spaceId',
      },
    ],
  ],
});

export default NoSweatShop;
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jphdsq4843xopt2fwmwh.png)

The Blog aspect registers itself to the platform in several ways:

- It extends the system’s GraphQL schema with a node for content retrieval. This is done in the NodeJS Runtime.
- It extends the system’s routing with the /blog route. This is done in the Browser Runtime.
- It extends the header with an additional item a ‘Blog’ link to /blog . This is done in the Browser Runtime.

*Note that the UI provided by the aspects is optional. In some cases, depending on the type of application the BFF is serving, the browser-runtime features might not be used*

### NodeJS Runtime

```ts
/**
 * @coponentId: lazyworks.blog/blog
 * @filename: blog.node.runtime.ts
 */

export class BlogNode {
  constructor(private config: BlogConfig) {}

  async getBlogPosts() {
    const blogData = new BlogData(this.config);
    return blogData.getBlogPosts();
  }

  static dependencies = [LazyworksPlatformAspect];

  static async provider(
    [lazyworksPlatform]: [LazyworksPlatformAspect],
    config: BlogConfig
  ) {
    const blog = new BlogNode(config);
    const gqlSchema = blogGqlSchema(blog);

    lazyworksPlatform.registerBackendServer([
      {
        gql: gqlSchema,
      },
    ]);

    return blog;
  }
}

export default BlogNode
```

### Browser Runtime

```tsx
/**
 * @coponentId: lazyworks.blog/blog
 * @filename: blog.browser.runtime.ts
 */


export class BlogBrowser {
  constructor(private config: BlogConfig) {}

  static dependencies = [LazyworksPlatformAspect, HeaderAspect];

  static async provider(
    [lazyworksPlatform, header]: [LazyworksPlatformBrowser, HeaderBrowser],
    config: BlogConfig
  ) {
    const blog = new BlogBrowser(config);

    lazyworksPlatform.registerRoute([
      {
        path: '/blog',
        component: () => {
          return (
            <ApolloBlogProvider spaceId={config.spaceId}>
              <BlogLobby />
            </ApolloBlogProvider>
          );
        },
      },
    ]);

    header.registerLink([{ label: 'Blog', href: '/blog' }]);

    return blog;
  }
}

export default BlogBrowser;
```
---



#### To learn more, visit [Bit's documentation](https://bit.dev) site or the [Bit Platform](https://bit.cloud).