Full-Stack with React

Level: Intermediate · Time: ~30 minutes · Prerequisites: Your First API, basic React

StreetJS is backend-first, but the published @streetjs/* packages make a typed React frontend trivial. The client SDK is framework-agnostic and zero-dependency; the React package adds hooks on top. None of these add any frontend dependency to the backend.


1. Scaffold backend + frontend in one command

1
2
street create my-app --frontend react
cd my-app

This produces a StreetJS backend and a web/ Vite + React app already wired to the backend with a dev proxy. Run them in two terminals:

1
2
3
4
5
# Terminal 1 — backend
npm install && street dev          # http://localhost:3000

# Terminal 2 — frontend
cd web && npm install && npm run dev   # http://localhost:5173

Prefer Next.js? Use --frontend next for an App Router app backed by @streetjs/next.


2. The client + provider

The scaffold sets up the client and provider for you. Here is what it does:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// web/src/main.tsx
import { createStreetClient } from '@streetjs/client';
import { StreetProvider } from '@streetjs/react';

const client = createStreetClient({
  baseUrl: import.meta.env.VITE_API_URL ?? '',  // proxied to the backend in dev
  credentials: 'include',                        // send cookies for session auth
});

createRoot(document.getElementById('root')!).render(
  <StreetProvider client={client}>
    <App />
  </StreetProvider>,
);

createStreetClient gives you typed resources, auth, search, uploads, realtime, and AI streaming from one object — no framework assumptions.


3. Authentication

useAuth wraps login/register/logout and exposes the current session, refreshing it automatically on success:

1
2
3
4
5
6
7
8
9
10
11
12
import { useAuth } from '@streetjs/react';

function LoginPanel() {
  const { session, loading, login, logout } = useAuth();
  if (loading) return <p>Loading…</p>;
  if (session) return <button onClick={() => logout()}>Sign out</button>;
  return (
    <button onClick={() => login({ email: 'a@b.co', password: 'secret' })}>
      Sign in
    </button>
  );
}

Don’t want to build forms? Drop in the ready-made, accessible components from @streetjs/auth-ui:

1
2
3
import { LoginForm, RegisterForm, MFASetup } from '@streetjs/auth-ui';

<LoginForm theme="dark" onSuccess={() => location.assign('/')} />

4. Fetching data

useQuery runs an async fetcher and exposes { data, error, loading, refetch }. Use the typed REST resources off the client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { useQuery, useStreetClient } from '@streetjs/react';

interface Item { id: string; name: string; price: number; }

function Catalog() {
  const client = useStreetClient();
  const { data, loading, error, refetch } = useQuery<Item[]>(
    () => client.resource<Item>('items').list(),
  );

  if (loading) return <p>Loading…</p>;
  if (error) return <p>Failed to load.</p>;
  return (
    <ul>
      {data?.map((i) => <li key={i.id}>{i.name} — ${i.price}</li>)}
      <button onClick={refetch}>Refresh</button>
    </ul>
  );
}

Mutations use useMutation:

1
2
3
4
5
6
7
import { useMutation, useStreetClient } from '@streetjs/react';

const client = useStreetClient();
const create = useMutation((body: { name: string; price: number }) =>
  client.resource('items').create(body),
);
// create.mutate({ name: 'Widget', price: 9.99 });

5. Realtime, search, uploads, AI

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { useChannel, useSearch, useAIChat, useStreetClient } from '@streetjs/react';

// Realtime — subscribe for the lifetime of the component
useChannel<{ text: string }>('room:42', (m) => console.log(m.data.text));

// Search
const results = useSearch<Item[]>('wid');   // re-runs when the query changes

// File upload
const client = useStreetClient();
await client.uploadFile('/uploads', file, { folder: 'avatars' });

// AI streaming chat
const { messages, streaming, send } = useAIChat({ model: 'gpt-4o-mini' });
await send('Summarize the latest orders');

For polished AI and admin surfaces, use @streetjs/ai-ui (Chat, RAGSearch, ToolExecutionViewer) and @streetjs/admin-ui (UserManagement, RoleManager, AuditLogViewer, TenantSwitcher).


6. Deploy

The scaffold also writes .github/workflows/ci.yml that builds both the backend and the web/ app. For production, build the frontend (npm run build in web/) and serve it as static assets behind the same origin as the API (or a CDN with the API proxied), so cookies stay first-party. See Deployment.


Best practices

  • Keep credentials: 'include' and serve API + web same-origin (or proxy) so httpOnly session cookies work and you avoid storing tokens in JS.
  • Let hooks own request state; avoid duplicating loading/error flags by hand.
  • Reach for the UI kits before hand-rolling auth/AI/admin screens — they are accessible, themeable, and dark-mode-ready out of the box.

Troubleshooting

Symptom Cause / fix
401s in dev Dev proxy not configured, or credentials: 'include' missing — the scaffold sets both.
CORS errors Serve same-origin/proxied, or enable corsMiddleware with explicit origins on the backend.
useStreetClient must be used within a <StreetProvider> Wrap your tree in <StreetProvider client={...}>.