Choorai

60분 완주 챌린지

Step 1/6

0%
Estimated time: 10 min

Frontend-Backend Connection

Call the backend API from the frontend to display real data. We'll use TanStack Query to easily manage API state.

TL;DR (Summary)

  • Install TanStack Query for API call management
  • Create API client + React hooks
  • Replace local state with API connection

Terms Before Step 4

  • CORS: Browser security rules for cross-origin requests.
  • Environment Variable: External config values (such as API URL).
  • Query: API call used to fetch data.
  • Mutation: API call used to create/update/delete data.

Before You Start

Both servers must be running:

  • Frontend: http://localhost:5173 (npm run dev)
  • Backend: http://localhost:8000 (uvicorn main:app --reload)

1Install TanStack Query

Run this in the frontend folder (my-admin):

Terminal
# Run in the my-admin folder
npm install @tanstack/react-query

Add the QueryClientProvider to src/main.tsx:

src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import App from './App'
import './index.css'

const queryClient = new QueryClient()

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <QueryClientProvider client={queryClient}>
      <App />
    </QueryClientProvider>
  </React.StrictMode>,
)

2Create API Client

Create the src/api/client.ts file:

src/api/client.ts
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000';

class ApiClient {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  async get<T>(path: string): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`);
    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`);
    }
    return response.json() as Promise<T>;
  }

  async post<T, D>(path: string, data: D): Promise<T> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`);
    }
    return response.json() as Promise<T>;
  }

  async delete(path: string): Promise<void> {
    const response = await fetch(`${this.baseUrl}${path}`, {
      method: 'DELETE',
    });
    if (!response.ok) {
      throw new Error(`API Error: ${response.status}`);
    }
  }
}

export const apiClient = new ApiClient(API_URL);

Term Tip: Env Vars / JSON

  • Environment Variable: Runtime configuration injected outside code.
  • JSON: The common data format used between frontend and backend APIs.
  • HTTP Status Code: Numeric response signals for success/failure.
  • Deep dive: Environment and Runtime, CORS troubleshooting

3Create React Hooks

Create the src/hooks/useProjects.ts file:

src/hooks/useProjects.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiClient } from '../api/client';

// Type definitions
interface Project {
  id: string;
  name: string;
  description?: string;
  created_at: string;
  updated_at: string;
}

interface ProjectCreate {
  name: string;
  description?: string;
}

interface ProjectListResponse {
  items: Project[];
  total: number;
}

// Hooks
export function useProjects() {
  return useQuery({
    queryKey: ['projects'],
    queryFn: () => apiClient.get<ProjectListResponse>('/api/v1/projects'),
  });
}

export function useCreateProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (data: ProjectCreate) =>
      apiClient.post<Project, ProjectCreate>('/api/v1/projects', data),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] });
    },
  });
}

export function useDeleteProject() {
  const queryClient = useQueryClient();

  return useMutation({
    mutationFn: (id: string) => apiClient.delete(`/api/v1/projects/${id}`),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] });
    },
  });
}

4Use in Components

Ask AI to update your existing components to use the API connection:

AI Prompt

Copy and use

"Update the existing App.tsx to connect with the API. Changes: 1. Use useProjects, useCreateProject, useDeleteProject hooks instead of local state 2. Show loading state (isLoading) 3. Show error state (isError) 4. Disable buttons during create/delete (isPending) Keep the existing UI, only change the data source to the API. Hooks to use: - useProjects() - { data, isLoading, isError } - useCreateProject() - { mutate, isPending } - useDeleteProject() - { mutate, isPending }"

Show your existing code to the AI when requesting the update.

5Verify Connection

When you create a project from the frontend, it will be saved via the backend API.

Frontend :5173
1. Enter name in form
2. Click "Create" button
3. Verify it appears in the list
Backend :8000
1. Receives POST request
2. Saves to memory
3. Returns 201 response
Connection Successful!

If the project you created from the frontend also appears in the backend's /docs GET API, you're all set!

Getting a CORS Error?

If you see a "CORS" error in the browser console, check the backend's CORS settings:

  1. Check allow_origins in the backend's main.py
  2. ["http://localhost:5173"] must be included
  3. Restart the backend server

For detailed solutions, see the CORS Troubleshooting guide.

Connection Completion Checklist

0/4 done

Last updated: February 22, 2026 · Version: v0.0.1

Send Feedback

Opens a new issue page with your message.

Open GitHub Issue