Building a Composable GraphQL App with Bit

How to build a maintainable and scalable full-stack React with GraphQL app using independent Bit components

Nitsan Cohen
Bits and Pieces

--

Composable software is transforming how we develop applications. This approach enhances flexibility, scalability, and maintainability by focusing on building applications as collections of independent, reusable components.

It addresses key challenges in conventional development, particularly in managing and reutilizing components across various projects.

In this post, we dive into the practical application of these concepts by building a composable GraphQL application using Bit. We’ll demonstrate how Bit facilitates the development of modular, maintainable, and scalable applications.

Our demo app will show a gallery of Pokemon characters fetched from a GraphQL API (implemented in this tutorial).

The app we are going to build. Gotta catch ’em all!

Building the GraphQL API Component

Setting Up the Workspace

To begin building our composable GraphQL application, the first step is to establish a Bit Worspace. Run the following in your terminal:

bit new react my-graphql-project --env bitdev.react/react-env --empty

A Bit Workspace is not just a folder structure; it’s a dynamic environment where components are developed, tested, and managed. It allows for seamless component isolation, dependency management, and integration, making it an ideal choice for building scalable and maintainable applications.

Creating a shared type and schema validator

See the component on bit.cloud

Our first task is to create the entity component. This component is crucial for defining the data models and types shared across the front and back end (or possibly between different services). It also includes a few utility functions for handling that data.

Run the following command to create the entity component using the entity component template:

bit create entity entities/character-entity

The character-entity component we've created includes essential data structures for our application. Here's a glimpse of what it looks like:

import 'reflect-metadata';

export enum Gender {
Male = 'Male',
Female = 'Female',
Unknown = 'Unknown',
}

export interface CharacterType {
name: string;
gender: Gender;
profileImage: string;
}

export class Character implements CharacterType {
constructor(
readonly name: string = '',
readonly gender: Gender = Gender.Unknown,
readonly profileImage: string = ''
) {}

static fromJson(jsonStr: string): Character {
const object = JSON.parse(jsonStr);
return new Character(
object.name,
object.gender as Gender,
object.profileImage
);
}

static fromObject(plainCharacter: Character): Character {
return new Character(
plainCharacter.name,
plainCharacter.gender,
plainCharacter.profileImage
);
}
}

With the entity component established, we have set a strong foundation for our application’s data architecture. We are now ready to move forward with building the GraphQL server component, which will utilize these data structures for effective data management and operations.

To learn more about sharing types and mock data, see this blog post:

Developing the GraphQL Server Component

The GraphQL API (app component) hosted in a scope on bit.cloud

Now, let’s turn to develop the GraphQL server component. This component is integral to our application’s backend, handling GraphQL queries and mutations based on the data models we defined earlier.

Again, we start by creating a new api/character-server component in our Bit workspace:

bit create graphql-server api/character-server

In this component, we’ll implement our GraphQL server logic. Here’s an example of how we integrate the Character entity from our previously created entity component:

// graphql-server.ts

import { Character, Gender } from '@nitsan770/graphql.entities.character';

export class GraphqlServer {
private characters: Character[] = [
new Character('Ash Ketchum', Gender.Male),
new Character('Misty', Gender.Female),
new Character('Brock', Gender.Male),
];
async getCharacters() {
return this.characters;
}
static from() {
return new GraphqlServer();
}
}

This code sets up a basic GraphQL server with a getCharacters query. It utilizes the Character class from our entity component, ensuring consistency in data structures. We will then use it characters-server.graphql.ts file.

With these implementations, our GraphQL server component is ready to handle API requests, using the shared data models established in the entity component. This integration exemplifies how Bit enables a cohesive and efficient development workflow for a GraphQL application.

Utilizing the Entity in Frontend Components

You can clearly see how both the API and frontend use the entity.

In our GraphQL application, the frontend components effectively utilize the entity component, demonstrating seamless integration and consistency across the entire application.

Using the shared mock data for testing

Let’s create the provider component that will handle state management related to GraphQL data:

bit create react providers/characters-provider --env nitsan770.graphql/envs/graphql-providers

The characters-provider component we created uses mock data defined in the entity component. This mock data serves as a placeholder, allowing the frontend to function and be tested even without the backend fully in place.

// character.mock.ts

import { gql } from '@apollo/client';
import { MockedResponse } from '@apollo/client/testing';
import { mockCharacterList as rawMockCharacterList } from '@nitsan770/graphql.entities.character';

const GET_CHARACTERS = gql`
query {
characters(filter: { gender: "Genderless" }) {
results {
name
gender
}
}
}
`;
export const mockCharacterList: MockedResponse[] = [
{
request: {
query: GET_CHARACTERS,
},
result: {
data: {
getCharacters: rawMockCharacterList,
},
},
},
];

In the mock file, we import the mockCharacterList from our entity component, demonstrating how the entity's data structure is directly used in the frontend provider component. This ensures that the data used in the frontend aligns with the data structures defined in our entity component.

Next, we create a custom hook for fetching character data:

bit create react hooks/use-character --env nitsan770.graphql/envs/graphql

Using the shared type

The custom hook, use-character, leverages the Character class from our entity component as a type, ensuring that the data it handles conforms to the predefined structure.

// use-character.ts

import { gql, useQuery } from '@apollo/client';
import { Character } from '@nitsan770/graphql.entities.character';

export const GET_CHARACTERS = gql`
query {
characters(filter: { gender: "Male" }) {
results {
name
gender
}
}
}
`;
export function useCharacters(): Character[] | undefined {
const { data } = useQuery(GET_CHARACTERS, {});
const characters = data?.characters.results.map((plainCharacter: Character) =>
Character.fromObject(plainCharacter)
);
return characters;
}

Here, the useCharacters hook fetches character data and maps each item to a Character instance. This use of the Character class ensures that the frontend component not only retrieves the data but also processes it in a manner consistent with the defined data model.

Implementing the client-side app

Having created our components, we now integrate them into the front-end application, demonstrating the practical use of Bit in a real-world scenario.

We’ve developed a simple yet functional React application named character-app. Here's the core code for the app:

import { Routes, Route } from 'react-router-dom';
import { GraphQlProvider } from '@nitsan770/graphql.context.apollo-provider';
import { CharactersList } from '@nitsan770/graphql.ui.characters-list';

export function CharacterApp() {
return (
<GraphQlProvider>
<div style={{ textAlign: 'center' }}>
<h1 style={{ fontSize: '2em', margin: '20px 0' }}>
🌟 Pokemon Character Gallery 🌟
</h1>
<div style={{ display: 'flex', justifyContent: 'center' }}>
<Routes>
<Route path="/" element={<CharactersList />} />
</Routes>
</div>
</div>
</GraphQlProvider>
);
}

This application utilizes the provider (GraphQlProvider) and UI component (CharactersList) We've created and showcased a Pokémon character gallery.

Releasing our components’ first version

Initially, all components are new and require versioning. We use Bit to tag and export these components:

bit tag -m "first release"

And export (push) them to a remote scope on bit.cloud:

bit export

These commands version all the new components and prepare them for export. Bit’s efficient versioning system ensures that only changed components and their dependents are versioned in subsequent updates. This selective versioning enhances the development workflow, making it more efficient.

Building and Deploying the Application:

  • Upon exporting, our components are built on Bit.cloud using Ripple CI. Ripple CI optimizes the build process by focusing only on the components that have been modified.
You don't start from scratch if something goes wrong in the build. Ripple resumes from the failed component in the graph.
  • The frontend application (component) includes a deployment task utilizing the Netlify deployer component. This composable approach means you can easily swap in different deployers as needed.

Backend Deployment Consideration:

  • We haven’t set up a deploy task for the backend GraphQL server, but it’s possible to do so. Deploying the backend would complete the CI/CD pipeline for the entire application.

With our frontend application implemented, versioned, and deployed, we showcase a complete workflow using Bit, from development to deployment, emphasizing efficiency and modularity.

Conclusion

In this practical guide, we’ve navigated the steps of creating a composable GraphQL application using Bit, from initiating the essential entity component to the seamless integration and deployment of frontend components. Our journey highlights Bit’s capacity to enhance and streamline the development process.

The precise versioning and focused building of components facilitated by Bit save time and ensure our application’s consistency and reliability. Our project, a Pokémon character gallery, stands as a testament to the effectiveness of modular development, demonstrating how Bit adeptly manages, versions, and deploys each component of a complex application.

Notably, it’s not just us who recognize the power of Bit in managing sophisticated projects. The teams responsible for developing applications for the Pokémon brand also utilize Bit to build and maintain their apps.

As we wrap up, we hope this guide has effectively illustrated the practical benefits of using Bit in real-world development scenarios, showcasing its indispensability for contemporary developers.

--

--

Making 𝘾𝙤𝙢𝙥𝙤𝙣𝙚𝙣𝙩-𝘿𝙧𝙞𝙫𝙚𝙣 Software a 𝐑𝐞𝐚𝐥𝐢𝐭𝐲 @ 𝐛𝐢𝐭.𝐝𝐞𝐯