โ† All guides

Mock a REST API for your React app in 60 seconds

The backend isn't ready. It never is. You could hard-code an array of {name: "Test User 1"} objects โ€” or you could build against a real HTTP API with realistic data, working pagination and honest loading states, so that when the actual backend lands, the swap is a one-line env change.

1. Get an API (10 seconds)

Click "try it" on the Mockbird landing page, or from a terminal:

curl -X POST https://HOST/api/projects \
  -H 'content-type: application/json' -d '{"name":"react-demo"}'
# โ†’ {"id":"abc123","adminKey":"...","baseUrl":"https://HOST/m/abc123"}

curl -X POST https://HOST/api/projects/abc123/resources \
  -H 'content-type: application/json' -H 'x-admin-key: YOUR_KEY' \
  -d '{"name":"users","template":"users","seed":50}'

You now have 50 realistic users โ€” names, emails, avatars, cities โ€” at https://HOST/m/abc123/users. CORS is on by default, so the browser can call it from any origin, including localhost.

2. Point React at it

// .env.development
VITE_API_URL=https://HOST/m/abc123

// .env.production  (later, when the real backend exists)
VITE_API_URL=https://api.yourapp.com
const API = import.meta.env.VITE_API_URL;

function useUsers(page, search) {
  const [state, setState] = useState({ users: [], total: 0, loading: true, error: null });

  useEffect(() => {
    const ctrl = new AbortController();
    setState(s => ({ ...s, loading: true }));
    const q = new URLSearchParams({ page, limit: 10, ...(search && { search }) });
    fetch(`${API}/users?${q}`, { signal: ctrl.signal })
      .then(r => {
        if (!r.ok) throw new Error(`HTTP ${r.status}`);
        return r.json().then(users => ({ users, total: +r.headers.get("X-Total-Count") }));
      })
      .then(({ users, total }) => setState({ users, total, loading: false, error: null }))
      .catch(e => e.name !== "AbortError" &&
        setState(s => ({ ...s, loading: false, error: e.message })));
    return () => ctrl.abort();
  }, [page, search]);

  return state;
}

Pagination is real (page/limit, total count in the X-Total-Count header), search is real (?search=ana matches across fields), and sorting works too (?sortBy=lastName&order=asc). Your UI logic is exercised for real, not against a stub that always succeeds instantly.

3. Test the states you always forget

This is the part hard-coded arrays can never give you. Append a flag to any request:

fetch(`${API}/users?mock_delay=2000`)   // does your skeleton loader actually show?
fetch(`${API}/users?mock_status=500`)   // does your error boundary catch it?
fetch(`${API}/users?mock_status=401`)   // does your app redirect to login?

No code changes, no browser dev-tools throttling, no conditional mocks โ€” just a query param you can also hand to QA.

4. Writes work too

Full CRUD is live: POST a new user and it gets the next id; PATCH merges fields; DELETE removes the row. The data persists, so your optimistic-update logic gets tested against a server that actually changes state between renders. Broke the data while experimenting? Re-seed from the dashboard in one click.

5. Ship day: swap the URL

Because Mockbird follows plain REST conventions, moving to the real backend is changing VITE_API_URL. If your backend team wants a contract to build against, hand them your mock's live OpenAPI spec: https://HOST/m/abc123/openapi.json โ€” importable into Swagger UI, Postman, or a code generator.

The same flow works for Vue, Svelte, Angular, or plain fetch โ€” nothing here is React-specific except the hook. Using MSW for unit tests? Keep it โ€” here's how a hosted mock and MSW compose (and when you need a real URL instead). Free tier: 20 projects, 10k requests/project/day. Docs โ†’