Skip to content

Standardize frontend API calls with centralized axios client and backend path alignment#56

Merged
csecrestjr merged 1 commit into
mainfrom
copilot/fix-6eb026a4-c469-41a2-a4a2-f048ea4b9f1d
Aug 31, 2025
Merged

Standardize frontend API calls with centralized axios client and backend path alignment#56
csecrestjr merged 1 commit into
mainfrom
copilot/fix-6eb026a4-c469-41a2-a4a2-f048ea4b9f1d

Conversation

Copilot AI commented Aug 31, 2025

Copy link
Copy Markdown

This PR adapts the frontend to use existing backend API paths and introduces a centralized axios client for consistent authentication header management across all requests.

Key Changes

Centralized API Client

Created /frontend/chat-ui/src/api.js with:

  • Automatic Authorization: Bearer ${token} header injection from localStorage
  • Fallback x-api-key header support via VITE_API_KEY environment variable
  • Configurable base URL via VITE_API_BASE_URL for different deployment environments
  • 30-second timeout and JSON content-type defaults

Frontend Path Standardization

Updated frontend components to use backend's existing API structure:

  • Authentication: user_login.js now calls /api/auth/login instead of hardcoded http://localhost:5000/auth/login
  • Admin routes: Updated to use /api/admin/users and /api/admin/keys
  • Key validation: Changed to /api/keys/validate-key
  • Chat UI: TopBar.jsx now uses centralized API client for conversation deletion via api.delete(/conversations/${id})

Development Experience Improvements

  • Vite proxy configuration: Added proxying for /api/*, /conversations/*, and /messages/* routes to http://localhost:5000
  • Port standardization: Changed dev server from port 3000 → 5173
  • Environment template: Added .env.example with documented configuration options
  • Dependency management: Proper .gitignore to exclude build artifacts

Authentication Flow Enhancement

The centralized approach ensures:

  • Token-based authentication is consistently applied across all API calls
  • Manual header management is eliminated (no more scattered x-api-key usage)
  • Environment-specific API configuration is centralized

Backend Compatibility

This PR does not modify any backend code. It adapts the frontend to work with the existing backend routes that are already mounted at:

  • /api/auth/* for authentication
  • /api/admin/* for admin operations
  • /api/keys/* for key management
  • /conversations/* for chat conversations
  • /messages/* for chat messages

Testing

  • ✅ Frontend builds successfully (npm run build)
  • ✅ Backend tests continue to pass
  • ✅ Dev server runs on port 5173 with proxy configuration
  • ✅ All syntax and import validation passes

The changes maintain backward compatibility while providing a more maintainable and consistent API interaction pattern for future development.

This pull request was created as a result of the following prompt from Copilot chat.

Goal: adapt the frontend to use the existing backend paths (do NOT change backend). Add a centralized axios API client, update the legacy frontend to call /api/* for auth/admin/keys, provide a Vite dev proxy and .env.example, and update the chat-ui TopBar to use the shared API client for delete calls. This PR standardizes auth header usage (Authorization: Bearer when available) and makes local dev easier (proxy to http://localhost:5000).

Changes to add/update (file contents are provided below):

  1. Add frontend/chat-ui/src/api.js — centralized axios client that:

    • uses VITE_API_BASE_URL or relative paths
    • attaches Authorization: Bearer from localStorage when present
    • attaches x-api-key from VITE_API_KEY as fallback
  2. Add frontend/chat-ui/vite.config.js — Vite config with dev proxy mapping /api, /conversations, and /messages to http://localhost:5000 to avoid CORS in development.

  3. Add frontend/chat-ui/.env.example — documents VITE_API_BASE_URL and VITE_API_KEY usage.

  4. Update frontend/user_login.js — replace hard-coded http://localhost:5000 calls with relative /api/* paths so frontend matches backend mounts. Uses axios (relative) so proxy can forward requests in dev.

  5. Update frontend/chat-ui/src/components/TopBar.jsx — replace the existing delete/remove call to use the new api client (api.delete(/conversations/${id})) and remove manual x-api-key header usage. Also add import for api.

Files to add/update (contents):

import axios from 'axios';

const baseURL = import.meta.env.VITE_API_BASE_URL || '';

const api = axios.create({
  baseURL,
  timeout: 30000,
  headers: {
    'Content-Type': 'application/json',
  },
});

api.interceptors.request.use((config) => {
  const token = localStorage.getItem('token');
  if (token) {
    config.headers = config.headers || {};
    config.headers.Authorization = `Bearer ${token}`;
  }

  if (import.meta.env.VITE_API_KEY) {
    config.headers = config.headers || {};
    if (!config.headers['x-api-key']) {
      config.headers['x-api-key'] = import.meta.env.VITE_API_KEY;
    }
  }

  return config;
}, (error) => Promise.reject(error));

export default api;
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        secure: false,
      },
      '/conversations': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        secure: false,
      },
      '/messages': {
        target: 'http://localhost:5000',
        changeOrigin: true,
        secure: false,
      },
    },
  },
});
# Base URL for the API backend. Use empty string for same-origin (recommended when using Vite proxy)
VITE_API_BASE_URL=

# Optional API key used by some frontend fetches. If you use tokens (recommended) this can remain empty.
VITE_API_KEY=
import React, { useState, useEffect } from 'react';
import axios from 'axios';

export default function UserLogin() {
  const [form, setForm] = useState({ email: "", password: "" });
  const [message, setMessage] = useState("");
  const [key, setKey] = useState("");
  const [keyMessage, setKeyMessage] = useState("");
  const [users, setUsers] = useState([]);
  const [keys, setKeys] = useState([]);
  const [adminView, setAdminView] = useState(false);

  useEffect(() => {
    const token = localStorage.getItem("token");
    if (token) {
      setAdminView(true);
      fetchUsers();
      fetchKeys();
    }
  }, []);

  const handleChange = (e) => {
    setForm({ ...form, [e.target.name]: e.target.value });
  };

  const handleLogin = async () => {
    try {
      // backend mounts auth under /api/auth
      const res = await axios.post('/api/auth/login', form);
      localStorage.setItem("token", res.data.token);
      setMessage("Login successful!");
      setAdminView(true);
      fetchUsers();
      fetchKeys();
    } catch (error) {
      console.error(error);
      setMessage("Login failed. Check credentials.");
    }
  };

  const handleKeyValidation = async () => {
    try {
      // backend expects /api/keys/validate-key
      const res = await axios.post('/api/keys/validate-key', { key });
      setKeyMessage("Key validated successfully!");
    } catch (error) {
      setKeyMessage("Invalid or expired key.");
    }
  };

  const fetchUsers = async () => {
    try {
      // admin routes are mounted on /api/admin
      const res = await axios.get('/api/admin/users');
      setUsers(res.data);
    } catch (error) {
      console.error("Failed to fetch users", error);
    }
  };

  const fetchKeys = async () => {
    try {
      const res = await axios.get('/api/admin/keys');
      setKeys(res.data);
    } catch (error) {
      console.error("Failed to fetch keys", error);
    }
  };

  return (
    <div className="flex flex-col items-center justify-center min-h-screen">
      {!adminView ? (
        <>
          <h1 className="text-2xl mb-4">Login</h1>
          <input
            type="email"
            name="email"
            placeholder="Email"
            className="border p-2 mb-2"
            onChange={handleChange}
          />
          <input
            type="password"
            name="password"
            placeholder="Password"
            className="border p-2 mb-2"
            onChange={handleChange}
          />
          <button className="bg-blue-500 text-white p-2 rounded" onClick={handleLogin}>
            Login
          </button>
          {message && <p className="mt-2">{message}</p>}
        </>
      ) : (
        <div className="w-3/4">
          <h1 className="text-2xl mb-4">Admin Dashboard</h1>
          <h2 className="text-xl mb-2">User List</h2>
          <ul className="border p-4">
            {users.map((user) => (
              <li key={user._id}>{user.email} - {user.role}</li>
            ))}
          </ul>
        </div>
      )}
    </div>
  );
}
// NOTE: This patch adds an import for the shared API client and replaces the delete/remove call
// to use api.delete(`/conversations/${id}`) instead of fetch with x-api-key.

import React from 'react';
import api from '../api'; // new centralized axios client

// ... other imports and component code above (kept unchanged)

const remove = async () => {
  if (!state.currentConversation) return;
  try {
    await api.delete(`/conversations/${state.currentConversation.id}`);
    dispatch({
      type: 'SET_CONVERSATIONS',
      conversations: state.conversations.filter((c) => c.id !== state.currentConversation.id),
    });
    dispatch({ type: 'SET_CURRENT_CONVERSATION', conversation: null });
    dispatch({ type: 'SET_MESSAGES', messages: [] });
  } catch (e) {
    console.error(e);
  }
};

// ... rest of component unchanged
export default TopBar;

Notes and rationale:

  • This PR intentionally keeps backend routes as currently mounted. It adapts frontend calls to match backend mounts under /api/* for auth/admin/keys and uses relative root paths for conversation endpoints.
  • Vite proxy is added to ease local development and avoid editing backend CORS for this PR.
  • The centralized axios client ensures Authorization header is attached across requests and provides a single place to change API base URL for different environments.

Testing instructions (what to test after this PR is merged):

  1. In backend: run server (PORT=5000). Verify curl http://localhost:5000/health returns OK.
  2. In frontend/chat-ui: copy .env.example to .env and (optionally) set VITE_API_BASE_URL. Run npm install and npm run dev. Ensure login requests go to /api/auth/login (proxied) and conversations requests go to /conversations (proxied).
  3. Confirm token stored in localStorage and Authorization header is attached to subsequent requests.

Please create a PR with these changes on the repository. Do not change backend code or mounts.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@csecrestjr
csecrestjr marked this pull request as ready for review August 31, 2025 17:06
@csecrestjr
csecrestjr merged commit 24487f6 into main Aug 31, 2025
1 check passed
Copilot AI changed the title [WIP] Frontend: standardize API client, use relative /api paths, add dev proxy (keep backend paths) Standardize frontend API calls with centralized axios client and backend path alignment Aug 31, 2025
Copilot AI requested a review from csecrestjr August 31, 2025 17:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants