Knowing how to connect frontend to backend is the moment a project stops being two separate toys and becomes an actual app. The frontend runs in a browser or on a phone; the backend runs on a server you control. Connecting them means agreeing on a contract — a set of URLs, request shapes, and responses — then handling the boring-but-critical parts: authentication, CORS, and errors. The 2026 tooling is friendlier than ever, but the failure modes are exactly the same as a decade ago.
What changed in 2026
- Type-safe clients went mainstream. tRPC, generated OpenAPI clients, and GraphQL codegen give you end-to-end types, so a backend field rename becomes a red squiggle in the editor instead of a 2 a.m. runtime crash.
- Edge functions blurred the line. Vercel, Cloudflare Workers, and Netlify let a "backend" run a few milliseconds from the user, so the frontend/backend split is now about responsibility, not physical distance.
- Auth moved to short-lived tokens plus httpOnly cookies. Storing raw JWTs in
localStorage is now widely treated as a mistake; the safer default is an httpOnly cookie or a token refreshed silently in the background.
- AI scaffolds the glue code. Tools built on Claude- and GPT-class models write your fetch layer in seconds — helpful, but they cheerfully invent endpoints, so verify every route against the real backend.
Pick your contract first
Before writing a single request, decide how the two sides will talk. It is the choice you will most regret rushing — it shapes your caching, your types, and your debugging for the whole project.
| Approach |
Best for |
Type safety |
Watch out for |
| REST + JSON |
Public APIs, simple CRUD |
Add-on via OpenAPI |
Over/under-fetching, versioning |
| GraphQL |
Many clients, nested data |
Strong with codegen |
Caching and N+1 queries |
| tRPC |
TypeScript full-stack |
End-to-end, built in |
Locks you into TypeScript |
| WebSockets / SSE |
Live chat, dashboards |
Mostly manual |
Reconnection, scaling |
If you are shipping a first app with a handful of endpoints, REST is almost always the right answer. Reach for GraphQL or tRPC when the pain they solve actually shows up.
Making the actual request
Once the contract exists, the frontend calls the backend over HTTP. The modern default is the built-in fetch — you rarely need Axios in 2026 unless you want interceptors or automatic retries.
// Frontend: call the backend and send the auth cookie
async function getProfile() {
const base = import.meta.env.VITE_API_URL; // never hardcode this
const res = await fetch(`${base}/me`, {
method: "GET",
credentials: "include", // send httpOnly cookie
headers: { "Content-Type": "application/json" },
});
if (!res.ok) throw new Error(`Backend returned ${res.status}`);
return res.json();
}
Three rules that save hours: check res.ok before parsing, handle the network-error catch, and never assume the backend returns the shape you expect.
CORS, the error you will hit on day one
CORS is the browser refusing a response because the backend did not explicitly allow your frontend's origin. It is not a bug in your code — it is a security feature doing its job. The fix lives entirely on the backend.
// Backend (Express): allow only your real frontend origin
app.use(cors({
origin: "https://app.example.com",
credentials: true,
}));
Never ship origin: "*" together with credentials — browsers block that combination, and it is a genuine security hole. If CORS keeps fighting you, the cleaner fix is putting frontend and backend behind the same domain with a reverse proxy, which sidesteps cross-origin rules entirely.
Where each side lives
The frontend is usually static files served from a CDN host — Vercel, Netlify, Cloudflare Pages, or S3 behind CloudFront. The backend is a running process: a container, a VM, or serverless functions. They connect only through the API base URL, which is why that URL belongs in an environment variable.
A same-origin setup (frontend and API under one domain, split by path) avoids CORS and simplifies cookies. Split-origin works too, but budget an afternoon for CORS and cookie SameSite settings the first time.
What to skip
- Skip localStorage for tokens. A single XSS bug exposes everything in it; prefer httpOnly cookies the JavaScript cannot read.
- Skip hardcoding the API URL. Use env vars so dev and production point at different servers without code edits.
- Skip rolling your own auth for a first app. A provider or battle-tested library is safer than a login you will get subtly wrong.
- Skip GraphQL for a three-endpoint CRUD app. The setup cost outweighs the benefit until you have real client-shaped complexity.
FAQ
Do I even need a separate backend?
Not always. If you only read public data or lean on a hosted service for auth and storage, serverless functions may be enough. You need a real backend once you hold secrets or enforce rules users must not bypass.
Why does it work in Postman but not the browser?
Almost always CORS. Postman is not a browser and ignores cross-origin rules, so a request that passes there can still be blocked in the frontend until the backend allows your origin.
Should I use fetch or Axios in 2026?
Start with fetch — it is built in and enough for most apps. Add Axios or a wrapper only when you want interceptors, automatic retries, or upload progress out of the box.
How do I keep API keys secret?
Never put a secret key in the frontend bundle; anyone can read it. Keep secrets on the backend, and have the frontend call your backend instead of the third-party service directly.
Where to go next
Once the wire is connected, the interesting problems move to the backend. Read ACID transactions explained in 2026 to keep your data correct under concurrency, AWS vs GCP in 2026 to decide where the backend actually runs, and Docker vs Kubernetes in 2026 to figure out how to package and scale it once real traffic shows up.