Siksha Sarovar

Siksha Sarovar (sikshasarovar.com) is a free educational web application that helps students in India learn programming and prepare for academic and competitive exams. The platform offers structured coding courses (C, C++, Python, Java, HTML, CSS, PHP, Power BI, AI, Machine Learning, Data Science), complete university curriculum notes for BCA/MCA students with previous year question papers, Class 10 and Class 12 CBSE/HBSE school notes, and dedicated preparation material for SSC, UPSC, Banking, Railway and other government exams. Browsing the site is completely free and requires no account. Users may optionally sign in with Google solely to save their learning progress, quiz scores and personal preferences across devices.

Privacy Policy | Terms of Service | Contact Siksha Sarovar | About Siksha Sarovar

v4.0.9 · PWA
Siksha Sarovar logo
Siksha Sarovar
Your Learning Universe

Siksha Sarovar is a free e-learning platform for coding courses, BCA university notes and competitive exam preparation. Optional Google sign-in saves your learning progress across devices.

Initializing knowledge base…
Compiling modules 0%

3. How to Connect Frontend and Backend

Lesson 3 of 23 in the free Backend Development notes on Siksha Sarovar, written by Rohit Jangra.

The CORS Problem

When your React app runs on http://localhost:5173 and your Express backend runs on http://localhost:5000, the browser blocks requests between them. This is the Same-Origin Policy — a browser security mechanism that prevents malicious websites from making unauthorised requests to other origins.

An origin is the combination of protocol + domain + port:

  • http://localhost:5173 — one origin
  • http://localhost:5000 — different origin (different port)
  • https://api.myapp.com vs https://myapp.com — different origins (different subdomain)

CORS (Cross-Origin Resource Sharing) is a mechanism that uses HTTP headers to tell the browser which cross-origin requests are permitted.

---

Fixing CORS in Express

Install the cors npm package:

npm install cors

Allow all origins (development only — never in production):

import cors from 'cors';
app.use(cors());

Allow specific origin (production setup):

app.use(cors({
  origin: process.env.CORS_ORIGIN,   // e.g., 'https://myapp.com'
  credentials: true,                  // Allow cookies/auth headers
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
}));
Setting origin: '*' (all origins) combined with credentials: true is invalid and will cause a browser error. You must specify the exact origin when sending credentials.

---

Axios vs Fetch — Comparison

FeatureAxiosFetch
Installationnpm install axiosBuilt into browser, no install
Request syntaxaxios.get(url, { params })fetch(url)
Response dataresponse.data (auto-parsed JSON)response.json() (manual parse)
Error handlingThrows on 4xx/5xx status codesOnly throws on network errors
InterceptorsBuilt-in (auth headers, error handling)Not supported natively
Request cancellationCancelToken / AbortControllerAbortController
Timeout supporttimeout: 5000 optionManual with AbortController
Base URLaxios.create({ baseURL })Manual string concatenation
Upload progressSupportedSupported
In a professional MERN project, Axios is preferred because of interceptors, automatic JSON parsing, and cleaner error handling. Fetch is fine for simple one-off requests.

---

API Base URL with Environment Variables

Hardcoding the API URL is bad practice because it differs between environments:

// src/config/api.ts
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000';

export const axiosInstance = axios.create({
  baseURL: API_BASE_URL,
  withCredentials: true,   // Send cookies with requests
  timeout: 10000,
});

// .env.development
// VITE_API_BASE_URL=http://localhost:5000

// .env.production
// VITE_API_BASE_URL=https://api.myapp.com

---

Proxy Setup in Vite (Avoid CORS in Development)

Instead of enabling CORS, you can configure Vite to proxy API requests during development — the browser thinks all requests go to localhost:5173:

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

export default defineConfig({
  plugins: [react()],
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^/api/, ''),
      },
    },
  },
});

With this setup, axios.get('/api/users') in React is proxied to http://localhost:5000/users — no CORS issue.

---

Cookies vs LocalStorage for Auth Tokens

AspecthttpOnly CookielocalStorage
XSS protection✅ JS cannot read httpOnly cookies❌ Any JS can access it
CSRF protectionNeeds SameSite + CSRF token✅ Not sent automatically
Auto-sentYes (with same origin)No (must attach manually)
ExpirySet by server (maxAge/expires)Manual, or until cleared
Server controlServer can revoke / set secureClient-side only
Recommendation✅ Preferred for auth tokens❌ Avoid for sensitive tokens
Store the access token in an httpOnly cookie (so JavaScript cannot steal it via XSS). The refresh token should also be in an httpOnly cookie for the same reason.

---

Common CORS Errors and Solutions

ErrorCauseSolution
No 'Access-Control-Allow-Origin' headerCORS not configuredAdd cors() middleware before routes
Credentials flag is true, but wildcard originorigin: '*' with credentials: trueSet exact origin string
Preflight OPTIONS request failingMissing OPTIONS handlingcors() handles this automatically
Cookie not being sentwithCredentials not setAdd withCredentials: true in Axios
Cookie not being receivedMissing credentials: true in CORS configAdd credentials: true to cors options

---

Example: React Frontend Calling Express Backend

// React component calling the backend
import axios from 'axios';
import { useState } from 'react';

const axiosInstance = axios.create({
  baseURL: import.meta.env.VITE_API_BASE_URL,
  withCredentials: true,
});

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault();
    try {
      const res = await axiosInstance.post('/api/auth/login', { email, password });
      console.log('Logged in:', res.data.user);
    } catch (err: any) {
      console.error('Login failed:', err.response?.data?.message);
    }
  };

  return (
    <form onSubmit={handleLogin}>
      <input value={email} onChange={e => setEmail(e.target.value)} />
      <input type="password" value={password} onChange={e => setPassword(e.target.value)} />
      <button type="submit">Login</button>
    </form>
  );
}