Made with Hope

Hope AI Coding Patterns

Hope AI consistently uses tried and true coding patterns to provide with an effective and efficient custom platforms. This document aims to walk you through them, so you can better understand how to scale its work, debug and expand.

All platforms generated by Hope AI uses Bit Dev and Harmony.

Overview

Hope AI generates codebases that look like this, where each domain owns its fullstack functionality:

my-platform/
├── design/             # Design System with shared UI components & theme
├── products/           # Products Domain
│   ├── ui/             # React components
│   ├── hooks/          # Data fetching and other hooks  
│   ├── entities/       # Domain models
├── shopping-cart/      # Shopping cart Domain
└── platform/           # Application shell, routing, auth, layout and other platform engineering
CopiedCopy

Each directory represents a business domain, containing everything needed for that business capability.

The complete product is a composition of all features and the platform to stitch them together using the Application Manifest:

// store-platform/store.ts
export const StorePlatform = HarmonyPlatform.from({
  name: 'store-platform',
  platform: [SymphonyPlatformAspect], // Core platform aspect
  aspects: [
    ProductsAspect,        // Product catalog
    ShoppingCartAspect,    // Shopping cart
  ]
});
CopiedCopy

Within each domain you find several components and an Aspect that connects both Frontend and Backend code to a single feature module. An Aspect defines frontend functionality in the *.browser-runtime.ts file and backend in *.node-runtime.ts communicating using an API Gateway.

Overview of an architecture by Hope AI

This approach allows Hope AI to generate a fully functional platform where all business features work together seamlessly while remaining independently developed.

Learn More:

Design System & Theming

The application uses a centralized theme system:

// design/theme/theme-provider.tsx
export function ThemeProvider({ children }) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light');
  
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      <div className={styles[theme]}>{children}</div>
    </ThemeContext.Provider>
  );
}
CopiedCopy

By default components access theme through CSS modules:

// component.module.scss
@import '@design/theme/variables';

.button {
  background: var(--primary-color);
  color: var(--text-on-primary);
}
CopiedCopy

Using React Router under the hook you get a unified routing system where each aspect can register its own routes and components.

// products/products.browser.runtime.ts
export class ProductsBrowser {
  static dependencies = [SymphonyPlatformAspect];
  
  static async provider([symphonyPlatform]: [SymphonyPlatformBrowser]) {
    // Register routes for this aspect
    symphonyPlatform.registerRoute([
      {
        path: '/products',
        element: () => <ProductList />
      },
      {
        path: '/products/:id',
        element: ({ params }) => <ProductDetail productId={params.id} />
      },
    ]);
    
    return new ProductsBrowser();
  }
}
CopiedCopy

The platform loads all aspects, each aspect's browser runtime registers its routes, and they are merged into the React Router configuration.

Composition of routes with React RouterI

State Management

Hook-Based Architecture

State is managed through custom React hooks that encapsulate data fetching and state logic:

// products/hooks/use-products.ts
export function useProducts() {
  const [products, setProducts] = useState<Product[]>([]);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    fetchProducts().then(data => {
      setProducts(data);
      setLoading(false);
    });
  }, []);
  
  return { products, loading };
}
CopiedCopy

Context for Global State

When global state is needed, use React Context wrapped in custom providers:

// shopping-cart/context/cart-context.tsx
export const CartContext = createContext<CartContextType>(...);

export function CartProvider({ children }) {
  // Cart state management
  return <CartContext.Provider value={...}>{children}</CartContext.Provider>;
}
CopiedCopy

Authentication & Authorization

Hope AI generates authentication as part of the Platform Aspect using a session-based flow:

Login Page → Auth Hook → GraphQL Resolver → Auth Service → MongoDB → Session Store → Protected Routes

// store-platform/hooks/use-auth/use-auth.tsx - Session management
export function useAuth() {
  const [user, setUser] = useState(null);
  const login = async (credentials) => {
    const response = await fetch('/api/login', { 
      method: 'POST', 
      body: JSON.stringify(credentials) 
    });
    setUser(await response.json());
  };
  return { user, login, logout };
}

// store-platform/ui/protected-route.tsx - Route protection
export function ProtectedRoute({ children }) {
  const { user } = useAuth();
  return user ? children : <Navigate to="/login" />;
}

// store-platform/store-platform/store-platform.node.runtime.ts - Backend authorization
 static async provider(
    [symphonyPlatform]: [SymphonyPlatformNode],
    config: ToyStorePlatformConfig,
    [onSignupHookSlot]: [OnSignupHookSlot]
  ) {
    ...
    symphonyPlatform.registerMiddlewares([
      // implement a middleware to authenticate users and attach the user to the request.
    ]);
    ...
  }
CopiedCopy

Data Fetching

Each aspect owns the complete data fetching flow from frontend to database. When you query for products, the Products Aspect handles everything:

Data fetching in an aspect

This single aspect ownership means the Products Aspect provides the React UI components, GraphQL schema, resolvers, repository, and database entities - ensuring full-stack cohesion and independent development.

Repository Pattern

Each aspect implements its own repository for data access using a consistent CRUD pattern:

// products/products/product-repository.ts
export class ProductsRepository {
  constructor(private db: Db) {
    this.collection = db.collection('products');
  }

  async getProducts(): Promise<Product[]> {
    return this.collection.find({}).toArray();
  }

  async getProductById(id: string): Promise<Product | null> {
    return this.collection.findOne({ _id: new ObjectId(id) });
  }

  async createProduct(product: Omit<Product, '_id'>): Promise<Product> {
    const result = await this.collection.insertOne(product);
    return { ...product, _id: result.insertedId };
  }
}
CopiedCopy
// products/products.graphql.ts - Schema definition
export const productSchema = gql`
  type Product { id: ID!, name: String!, price: Float! }
  type Query { products: [Product!]! }
`;

// products/ui/product-list.tsx - Client-side data fetching
export function ProductList() {
  const { data, loading, error } = useQuery(GET_PRODUCTS);
  if (loading) return <LoadingSpinner />;
  if (error) return <ErrorMessage error={error} />;
  return (
    <div>{data.products.map(product => 
      <ProductCard key={product.id} product={product} />
    )}</div>
  );
}
CopiedCopy

Database & Data Management

Hope AI projects use MongoDB as the default database, with each aspect managing its own collections through repository patterns.

Overview of an architecture by Hope AI

Each aspect owns its collections and provides data models and repositories for CRUD operations.

// products/entities/product.entity.ts - Type definitions
export interface Product {
  _id?: ObjectId;
  name: string;
  price: number;
  category: string;
  inStock: boolean;
  createdAt: Date;
}

// products/products/product.model.ts
/**
 * Represents a specific variant of a product.
 */
export class ProductVariantModel {
  @prop({ required: true, type: String })
  public name!: string;
  ...
}

/**
 * Represents a product in the store.
 */
@modelOptions({ schemaOptions: { timestamps: true } })
@index({ id: 1 }, { unique: true })
@index({ name: 'text', description: 'text' }) // For text search
export class ProductModel {
  @prop({ unique: true, required: true, type: String, default: () => uuidv4() })
  public id!: string;
  ...
}

/**
 * Mock data for products.
 */
export const mockProductsData: ProductModel[] = [
  {
    id: uuidv4(),
    name: 'Chihuahua Plush Toy',
    ...
  },
];
CopiedCopy

Data is seeded to collections during platform initialization in the registerOnStart platform API:

// products/products/products.node.runtime.ts
static async provider(
    [symphonyPlatform, storePlatform, shoppingCart]: [SymphonyPlatformNode, storePlatformNode, ShoppingCartNode],
    config: ProductsConfig,
  ) {
    ...
    symphonyPlatform.registerOnStart(async () => {
      ...
    });
  }
CopiedCopy


🦶 Next steps