DEV Community

Abdul Fadiga
Abdul Fadiga

Posted on

API

Complete React API Guide & Practical Tutorial

Welcome! This comprehensive guide is designed to help you, as a React intern, understand how to connect your React application to a backend server. We will start from the very basics of what an API is, and then move quickly into practical work integrating with the exact backend we have built for our e-commerce platform (Megamakert).


Part 1: The Basics - What is an API?

An API (Application Programming Interface) is essentially a bridge that allows two applications to talk to each other. In our case, your React frontend needs to talk to our Node.js backend to do things like logging in users, fetching products, or creating orders.

HTTP Methods (The "Verbs")

When talking to an API, you send requests using specific "Methods" to indicate what you want to achieve:

  • GET: Used to fetch data from the server (e.g., getting a list of products).
  • POST: Used to send new data to the server (e.g., creating a new user or logging in).
  • PUT / PATCH: Used to update existing data.
  • DELETE: Used to remove data.

Status Codes

When the server responds, it sends a number indicating how the request went:

  • 200 OK / 201 Created: Success! The request worked.
  • 400 Bad Request: The data sent from React was invalid.
  • 401 Unauthorized: The user needs to log in to do this.
  • 500 Internal Server Error: The backend crashed or has a bug.

Part 2: Authentication (Login & Register)

In React, the standard way to make HTTP requests built into the browser is the fetch API.

Our backend handles authentication (Login, Register, Logout). Here is how you use it from React.

Setup Base URL

First, it's a good idea to establish a base URL so you don't have to retype http://localhost:5000 constantly.

// In a file like src/api/config.js
export const API_BASE_URL = 'http://localhost:5000/api'; // (Replace with the correct port in .env)
Enter fullscreen mode Exit fullscreen mode

Registering a New User (POST Request)

We use POST to send form data (like email and password) from React to the backend.

import React, { useState } from 'react';
import { API_BASE_URL } from '../api/config';

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

  const handleRegister = async (e) => {
    e.preventDefault(); // Prevent page reload

    try {
      const response = await fetch(`${API_BASE_URL}/auth/register`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json', // We are sending JSON
        },
        body: JSON.stringify({ email, password })
      });

      const data = await response.json();

      if (response.ok) {
        alert("Registration successful!");
      } else {
        alert("Registration failed: " + data.message);
      }
    } catch (error) {
      console.error("Network error:", error);
    }
  };

  return (
    <form onSubmit={handleRegister}>
      <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="Enter email" />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="Enter password" />
      <button type="submit">Complete Registration</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Logging In and Saving the Token (POST Request)

When a user logs in, the backend responds with a JWT (JSON Web Token). We need to save this token in React so we can include it in future secure requests.

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

  const handleLogin = async (e) => {
    e.preventDefault();

    try {
      const response = await fetch(`${API_BASE_URL}/auth/login`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email, password })
      });

      const data = await response.json();

      if (response.ok) {
        // SUCCESS: Save the token and refresh token into localStorage
        localStorage.setItem('accessToken', data.accessToken);
        localStorage.setItem('refreshToken', data.refreshToken);
        alert("Logged in!");
      } else {
        alert("Login failed: " + data.message);
      }
    } catch (error) {
      console.error("Error logging in", error);
    }
  };
    // ... form rendering similar to above
}
Enter fullscreen mode Exit fullscreen mode

Making an Authenticated Request (e.g., Logout)

Certain routes must be protected. The backend will ONLY let us run them if we send the JWT. In React, we attach the token to the Authorization header. Let's see how logout works:

const handleLogout = async () => {
    // 1. Get the token we saved during login
    const token = localStorage.getItem('accessToken');

    try {
      const response = await fetch(`${API_BASE_URL}/auth/logout`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          // 2. Add the token to the headers!
          'Authorization': `Bearer ${token}` 
        }
      });

      if (response.ok) {
        alert("Logged out successfully!");
        // 3. Clear the token from storage since it's no longer valid
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
      }
    } catch (error) {
      console.error("Error logging out", error);
    }
};
Enter fullscreen mode Exit fullscreen mode

Part 3: Practical Work - Fetching and Creating Products

Now for the practical work! We will build a single page ProductManager.jsx component that does two things:

  1. GET: Fetches a list of products from our Megamakert backend and displays them. (Public)
  2. POST: Allows us to create a new product and send it to the backend. (Requires the user to be logged in AND be an Admin)

Here is the complete component:

import React, { useState, useEffect } from 'react';
import { API_BASE_URL } from '../api/config';

export function ProductManager() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  // States for our "Create Product" form
  const [newProductName, setNewProductName] = useState('');
  const [newProductPrice, setNewProductPrice] = useState('');
  const [newProductCategory, setNewProductCategory] = useState('');
  const [creating, setCreating] = useState(false);

  // --- 1. Fetching Data (The GET Request) ---
  useEffect(() => {
    // This runs once when the component appears on the screen
    async function fetchProducts() {
      try {
        const response = await fetch(`${API_BASE_URL}/products`);
        const data = await response.json();
        if (response.ok) setProducts(data.products || data);
      } catch (err) {
        console.error(err);
      } finally {
        setLoading(false);
      }
    }
    fetchProducts();
  }, []); // Empty array = run once on mount

  // --- 2. Sending Data (The Authenticated POST Request) ---
  const handleCreateProduct = async (e) => {
    e.preventDefault(); // Stop the page from reloading on form submit
    setCreating(true);

    // Get our saved token
    const token = localStorage.getItem('accessToken');

    if (!token) {
      alert("You must be logged in to create a product!");
      setCreating(false);
      return;
    }

    const newProductData = {
      name: newProductName,
      price: Number(newProductPrice), // Ensure it's a number
      category: newProductCategory,
    };

    try {
      const response = await fetch(`${API_BASE_URL}/products`, {
        method: 'POST', 
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${token}` // Mandatory for protected routes!
        },
        body: JSON.stringify(newProductData) 
      });

      const data = await response.json();

      if (response.ok) {
        alert("Product created successfully!");
        // Update our local UI list immediately without reloading the page
        setProducts(prevProducts => [...prevProducts, data.product || data]);

        // Reset the form fields
        setNewProductName('');
        setNewProductPrice('');
        setNewProductCategory('');
      } else {
        alert("Failed to create product: " + (data.message || data.error));
      }
    } catch (err) {
      alert("Network error while creating product.");
      console.error(err);
    } finally {
      setCreating(false);
    }
  };

  return (
    <div>
      <h2>Product List</h2>
      {loading ? (
        <p>Loading...</p>
      ) : (
        <ul style={{ marginBottom: '2rem' }}>
          {products.length === 0 && <p>No products found.</p>}
          {products.map(product => (
            <li key={product.id || product._id}>
              <strong>{product.name}</strong> - ${product.price} ({product.category})
            </li>
          ))}
        </ul>
      )}

      <hr />

      <h2>Create New Product</h2>
      <form onSubmit={handleCreateProduct} style={{ display: 'flex', flexDirection: 'column', gap: '10px', maxWidth: '300px' }}>
        <input 
          type="text" 
          placeholder="Product Name" 
          value={newProductName} 
          onChange={(e) => setNewProductName(e.target.value)} 
          required 
        />
        <input 
          type="number" 
          placeholder="Price (e.g. 19.99)" 
          value={newProductPrice} 
          onChange={(e) => setNewProductPrice(e.target.value)} 
          required 
          step="0.01"
        />
        <input 
          type="text" 
          placeholder="Category ID or Name" 
          value={newProductCategory} 
          onChange={(e) => setNewProductCategory(e.target.value)} 
          required 
        />

        <button type="submit" disabled={creating}>
          {creating ? 'Creating...' : 'Create Product'}
        </button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

💡 Practical Mini-Tasks for the Intern

Now it's your turn to practice! Here are a few things to try:

  1. Test the GET request: Make sure your backend server is running (npm run dev). Try to load this component. Do you see the products? If the backend crashes or there's an error, open your browser's DevTools -> Network tab and see what failed!
  2. Test the POST request: Try creating a product WITHOUT being logged in (or logged in as a normal user instead of an Admin). What status code does the server return? Does the alert box show you the error?
  3. Enhance the App (Challenge): The Megamakert backend features a Delete Product route (DELETE /api/products/:id). Add a "Delete" button next to each product in the <ul> list, and write a function handleDeleteProduct(productId) that uses fetch with method: 'DELETE' to remove it from the database!

Top comments (0)