Skip to main content
The frontend is a React 19 application built with TypeScript and Vite. It uses connect-query (connect-rpc + TanStack Query) for type-safe API communication with the gRPC backend.

Technology Stack

CategoryTechnology
FrameworkReact 19
LanguageTypeScript
Build ToolVite
API Clientconnect-query (connect-rpc + TanStack Query)
HTTP Client@connectrpc/connect-web
MockingMSW (Mock Service Worker)
Code QualityBiome (lint + format)

Prerequisites

Quick Start

1

Navigate to frontend directory

cd frontend
2

Install dependencies

npm install
3

Copy environment file

cp .env.example .env
4

Start development server

npm run dev
The dev server starts at:
http://localhost:5173
The development server includes hot module replacement (HMR) for instant updates when you edit code.

Directory Structure

frontend/src/
├── app/
│   ├── App.tsx              # Root component
│   ├── main.tsx             # Entry point, MSW bootstrap
│   └── providers/
│       └── app-provider.tsx # QueryClient and transport setup
├── features/
│   ├── auth/                # Authentication UI (login/signup)
│   ├── greeter/             # Greeter service demo
│   └── gateway/             # Gateway service demo
├── gen/                     # Auto-generated from Protocol Buffers
│   ├── greeter/v1/          # Greeter TypeScript types
│   ├── gateway/v1/          # Gateway TypeScript types
│   └── ...                  # Other service types
├── interceptors/
│   └── auth-interceptor.ts  # JWT token injection
├── lib/
│   ├── transport.ts         # connect-web transport config
│   └── query-client.ts      # TanStack Query client config
├── testing/
│   ├── browser.ts           # MSW worker setup
│   ├── handlers.ts          # MSW request handlers
│   └── test-utils.tsx       # Testing utilities
└── types/
    └── env.d.ts             # TypeScript environment types

Key Directories

app/

Application root, providers, and entry point.
  • main.tsx: Entry point that conditionally starts MSW based on VITE_USE_MOCK
  • App.tsx: Main component that renders feature demos
  • providers/: Context providers for React Query and connect-rpc transport

features/

Feature-based modules following the Bulletproof React pattern. Each feature contains:
  • components/ — React components
  • hooks/ — Custom React hooks (e.g., useGreet for RPC calls)
  • types/ — TypeScript types

gen/

DO NOT EDIT FILES IN THIS DIRECTORY.This directory is auto-generated by buf generate from Protocol Buffer definitions. All changes will be overwritten.
Generated files include:
  • TypeScript types for protobuf messages
  • connect-query hooks for RPC methods
  • Service definitions

interceptors/

connect-rpc interceptors for cross-cutting concerns.
  • auth-interceptor.ts: Adds JWT token from localStorage to Authorization header

lib/

Shared utilities and configuration.
  • transport.ts: Configures connect-web transport with base URL and interceptors
  • query-client.ts: TanStack Query client with retry and cache settings

testing/

Mock Service Worker (MSW) setup for development without a backend.
  • browser.ts: MSW worker instance
  • handlers.ts: Mock request handlers for greeter and gateway APIs
  • test-utils.tsx: Testing utilities (for future unit tests)

Environment Configuration

Frontend configuration is managed through .env files and Vite’s environment variables.

frontend/.env

# API Gateway URL (Traefik)
VITE_API_BASE_URL=http://localhost:30081

# Enable MSW mock mode
VITE_USE_MOCK=false

Environment Variables

VariableDefaultDescription
VITE_API_BASE_URLhttp://localhost:30081Backend API base URL
VITE_USE_MOCKfalseEnable MSW for mock API responses
All environment variables prefixed with VITE_ are exposed to the browser at build time. Never include secrets in these variables.

Development Modes

Connect to Real Services

Use this mode when developing against actual backend services.
1

Start backend services

docker compose up
2

Configure environment

Ensure frontend/.env points to Traefik:
VITE_API_BASE_URL=http://localhost:30081
VITE_USE_MOCK=false
3

Start dev server

cd frontend
npm run dev
4

Access application

Open http://localhost:5173The frontend will make real API calls to:
  • http://localhost:30081/greeter.v1.GreeterService/Greet
  • http://localhost:30081/gateway.v1.GatewayService/InvokeCustom
  • http://localhost:30081/auth/login
  • etc.
This is the recommended mode for most development since it tests real integrations.

API Communication

connect-query Integration

The frontend uses connect-query to call backend gRPC services over HTTP.

Transport Setup

Transport is configured in src/lib/transport.ts:
import { createConnectTransport } from '@connectrpc/connect-web';
import { authInterceptor } from '../interceptors/auth-interceptor';

export const transport = createConnectTransport({
  baseUrl: import.meta.env.VITE_API_BASE_URL || 'http://localhost:30081',
  interceptors: [authInterceptor],
});

Query Client Setup

TanStack Query client is configured in src/lib/query-client.ts:
import { QueryClient } from '@tanstack/react-query';

export const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 1,
      refetchOnWindowFocus: false,
    },
  },
});

Making RPC Calls

Generated hooks make RPC calls simple:
import { useQuery } from '@connectrpc/connect-query';
import { greet } from '../gen/greeter/v1/greeter-GreeterService_connectquery';

function GreeterDemo() {
  const { data, isLoading } = useQuery(greet, { name: 'World' });

  if (isLoading) return <p>Loading...</p>;
  return <p>{data?.message}</p>;
}

Auth Interceptor

The auth interceptor (src/interceptors/auth-interceptor.ts) automatically adds JWT tokens to requests:
import type { Interceptor } from '@connectrpc/connect';

export const authInterceptor: Interceptor = (next) => async (req) => {
  const token = localStorage.getItem('token');
  if (token) {
    req.header.set('Authorization', `Bearer ${token}`);
  }
  return next(req);
};
This ensures all RPC calls include the user’s authentication token if available.

Available Scripts

Development

npm run dev
Starts Vite dev server at http://localhost:5173 with:
  • Hot module replacement (HMR)
  • Fast refresh for React components
  • Source maps for debugging

Build

npm run build
Runs TypeScript type checking (tsc -b) followed by production build (vite build). Output is written to dist/:
frontend/dist/
├── index.html
├── assets/
│   ├── index-[hash].js
│   └── index-[hash].css
└── ...

Preview

npm run preview
Serves the production build locally for testing:
http://localhost:4173

Code Quality

# Check for lint errors
npm run lint

# Auto-fix lint errors
npm run lint:fix
Uses Biome for fast linting.
The gen/ directory is excluded from Biome checks since it’s auto-generated.

Code Generation

When backend proto files change, regenerate TypeScript types:
1

Update proto definitions

Edit files in proto/ (in the project root).
2

Run buf generate

From project root:
buf generate
This updates:
  • frontend/src/gen/ — TypeScript types and connect-query hooks
  • services/gen/go/ — Go protobuf code
3

Restart dev server

Vite should auto-reload, but you may need to restart:
# Stop dev server (Ctrl+C), then:
npm run dev
Never manually edit files in src/gen/. All changes will be overwritten by buf generate.

Project Configuration

Vite Config

vite.config.ts:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    host: '0.0.0.0',  // Allow external connections
    port: 5173
  }
});

TypeScript Config

tsconfig.json and tsconfig.app.json configure TypeScript for React 19 and strict mode. Key settings:
  • strict: true — Strict type checking
  • jsx: "react-jsx" — React 19 JSX transform
  • moduleResolution: "bundler" — Modern module resolution

Biome Config

biome.json configures linting and formatting:
{
  "files": {
    "ignore": ["src/gen/**"]  # Exclude generated code
  },
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}

Common Tasks

Add a new feature

1

Create feature directory

mkdir -p src/features/my-feature/components
mkdir -p src/features/my-feature/hooks
2

Add components

Create React components in components/:
// src/features/my-feature/components/MyComponent.tsx
export function MyComponent() {
  return <div>My Feature</div>;
}
3

Add hooks for API calls

// src/features/my-feature/hooks/useMyFeature.ts
import { useQuery } from '@connectrpc/connect-query';
import { myMethod } from '../../gen/myservice/v1/myservice-MyService_connectquery';

export function useMyFeature(input: { name: string }) {
  return useQuery(myMethod, input);
}
4

Import in App.tsx

import { MyComponent } from '../features/my-feature/components/MyComponent';

export function App() {
  return (
    <main>
      <MyComponent />
    </main>
  );
}

Debug API calls

  1. Open Chrome/Firefox DevTools
  2. Go to Network tab
  3. Filter by “Fetch/XHR”
  4. Look for requests to /greeter.v1.GreeterService/...
  5. Inspect request/response payloads

Troubleshooting

Dev server won’t start

Check port availability:
lsof -i :5173
Kill the process using the port or change the port in vite.config.ts.

API calls fail

Check backend is running:
curl http://localhost:30081/healthz
Check VITE_API_BASE_URL:
cat frontend/.env
Ensure it matches your backend setup (default: http://localhost:30081).

Type errors after proto changes

Regenerate code:
buf generate
Restart TypeScript server (in VS Code: Cmd+Shift+P → “Restart TS Server”).

MSW not working

Verify environment variable:
cat frontend/.env | grep VITE_USE_MOCK
Should be VITE_USE_MOCK=true. Check browser console for MSW startup message:
[MSW] Mocking enabled.
If missing, restart dev server.

Build fails

Run type checking:
npm run build
Fix any TypeScript errors reported by tsc.

Next Steps

Docker Compose

Run backend services locally

Kubernetes + Tilt

Advanced local development

Build docs developers (and LLMs) love