The Client-Server Mental Model
You’ve built the backend. Now you’ll build the frontend that talks to it. Before writing any React code, you need a clear mental model of how a client-side app communicates with a server.
What changes when you have a backend
Section titled “What changes when you have a backend”In your previous React apps (ZeroBudget, FamilyTree), data lived in localStorage or component state. It was always available, always synchronous, and never left the browser.
With Bulletin, data lives on a server in a database. Accessing it requires:
- Sending an HTTP request over the network to your Railway API
- Waiting for the server to process and respond
- Handling three possible outcomes: loading, success, or error
This is fundamentally different from reading localStorage. The network is slow, unreliable, and asynchronous.
The request-response cycle
Section titled “The request-response cycle”React component ↓ axios.get('https://api.railway.app/posts') ↓ Network request (10ms–2000ms+)Railway API ↓ Express route handler ↓ SQLite query ↑ JSON response { posts: [...] }React component ↑ Update state with received data ↑ Re-render with posts displayedEvery data fetch is an async operation with this cycle. Your UI must account for all three states: loading (waiting), success (data arrived), and error (something went wrong).
JSON as the data format
Section titled “JSON as the data format”Your API returns JSON. React receives it as a JavaScript object:
const response = await axios.get('/posts')// response.data → { posts: [...], total: 42, page: 1 }TypeScript interfaces describe the shape of this data:
interface Post { id: number title: string body: string user_id: number upvotes: number created_at: string author_username: string}
interface PostsResponse { posts: Post[] total: number page: number limit: number}Define these interfaces in a src/types/api.ts file and use them throughout the frontend.
The base URL
Section titled “The base URL”Your React app needs to know where the API is. In development, it’s http://localhost:3000. In production, it’s your Railway URL.
Use Vite environment variables:
export const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3000'In .env.local (for local development):
VITE_API_URL=http://localhost:3000In GitHub Actions (for production):
VITE_API_URL=https://your-app.up.railway.appVITE_ prefix is required for Vite to expose environment variables to the browser.
Setting up the Bulletin frontend
Section titled “Setting up the Bulletin frontend”npm create vue@latest bulletin # This is a React course — use:npm create vite@latest bulletin -- --template react-tscd bulletinnpm installnpm run devThe frontend is a standard Vite React + TypeScript project — the same scaffold you’ve used in React Foundations.
Exercise
Section titled “Exercise”Think through the Bulletin frontend architecture:
- What pages (routes) does Bulletin need? Write them out.
- What data does each page need to fetch?
- What actions (mutations) does each page need to perform?
- Which pages are accessible to anonymous users? Which require authentication?
- Data lives on the server — every access requires an HTTP request.
- Every request has three states: loading, success, error — your UI must handle all three.
- JSON is the data format — define TypeScript interfaces for the API response shapes.
VITE_API_URLlets you switch between local and production API endpoints without code changes.