Skip to Content
Lesson 2

Connecting React and Express

Workplace Context

You’ve successfully explained the high-level architecture to your team. Now it’s time to get practical. Your next task is to create a proof-of-concept demonstrating how the React frontend can fetch data from the Express backend. You’ll need to handle API keys and server URLs securely, manage the asynchronous nature of network requests, and figure out a way to solve the inevitable CORS issue during local development without permanently exposing your API to the world.


Lesson Objectives

  • Use fetch and axios to make API requests from a React application to an Express backend.
  • Securely manage environment variables for both client-side and server-side applications.
  • Implement a development proxy to mitigate CORS issues without disabling security features in production.
  • Apply state management techniques in React to handle loading, data, and error states for asynchronous operations.

Making API Requests from React

To communicate with a backend server, a React application needs an HTTP client to make network requests. The two most common choices are the browser’s built-in fetch API and the third-party library axios.

Using the fetch API

The fetch API is a modern, promise-based interface for making HTTP requests. It’s available in all modern browsers, so it requires no external libraries.

A basic GET request looks like this:

// src/components/DataFetcher.jsx import React, { useState, useEffect } from 'react'; function DataFetcher() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { fetch('http://localhost:5000/api/data') // Your API endpoint .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(data => { setData(data); setError(null); }) .catch(error => { setError(error.message); setData(null); }) .finally(() => { setLoading(false); }); }, []); // Empty dependency array means this runs once on mount if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return ( <div> <h1>Data from API</h1> <pre>{JSON.stringify(data, null, 2)}</pre> </div> ); } export default DataFetcher;

Using axios

axios is a popular library that provides a more ergonomic and powerful API for making HTTP requests. Key advantages include:

  • Automatic transformation of response data to JSON.
  • Better error handling (it throws an error for any 4xx or 5xx status code).
  • A simpler API for sending complex requests (e.g., POST requests with JSON data).

First, install it: npm install axios

Then, the same request can be refactored:

// src/components/DataFetcher.jsx with axios import React, { useState, useEffect } from 'react'; import axios from 'axios'; function DataFetcher() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { axios.get('http://localhost:5000/api/data') // Your API endpoint .then(response => { setData(response.data); setError(null); }) .catch(error => { setError(error.message); setData(null); }) .finally(() => { setLoading(false); }); }, []); // ... same return logic as before }

Managing Environment Variables

Hardcoding URLs, API keys, or other configuration values directly into your source code is a bad practice. It makes your application inflexible and insecure. The standard solution is to use environment variables.

Server-Side (Node.js/Express)

In a Node.js environment, you can use the dotenv package to load variables from a .env file into process.env.

  1. Install dotenv: npm install dotenv
  2. Create a .env file in your project root:
    PORT=5000 MONGO_URI=mongodb://... API_SECRET=a_very_secret_key
  3. Load it at the very top of your main server file: require('dotenv').config();

Now you can access these variables anywhere in your backend code: const port = process.env.PORT;

Client-Side (Create React App)

Create React App has built-in support for environment variables. To avoid accidentally exposing server-side secrets to the client, it requires that all client-side variables be prefixed with REACT_APP_.

  1. Create a .env file in your React project root:
    REACT_APP_API_URL=http://localhost:5000
  2. Access it in your React components: const apiUrl = process.env.REACT_APP_API_URL;

Now, your fetch or axios call can be made dynamic: axios.get($/api/data).


Solving CORS in Development: The Proxy

In Lesson 1, you learned about CORS. When your React app at localhost:3000 tries to fetch from your API at localhost:5000, the browser will block the request due to the Same-Origin Policy.

While you can configure CORS on the server to allow localhost:3000, a more common and secure approach during development is to use a proxy.

A proxy tricks the browser into thinking it’s only communicating with one origin. Your React app makes a request to its own development server, which then “proxies” or forwards that request to the real backend API. Since the browser only ever talks to the React dev server, no cross-origin request is ever made from its perspective, and CORS errors are avoided entirely.

There are a few different approaches to setting up a proxy, depending on your project structure. Add a proxy key to your package.json file to see how it works:

client/package.json
{ "name": "client", "version": "0.1.0", "private": true, "dependencies": { ... }, "proxy": "http://localhost:5000" }

Now, you can change your API requests to be relative paths. The React development server will intercept any unknown request and forward it to the proxy URL.

// No more hardcoded full URL! useEffect(() => { axios.get('/api/data') // This is proxied to http://localhost:5000/api/data .then(response => setData(response.data)) .catch(error => setError(error.message)); }, []);

Important: The proxy is a development-only feature. In production, you will need to handle this differently, either by serving the client and API from the same domain or by configuring CORS on the server properly.


Summary

In this lesson, you connected the “R” and the “E” in the MERN stack. You learned how to use both the fetch API and axios to make asynchronous requests from a React client to an Express server. We covered the critical practice of managing configuration with environment variables for both the client and server, ensuring that sensitive information is kept out of source code. Most importantly, you learned how to use a development proxy to seamlessly bypass CORS issues without compromising security, and you practiced handling the three key states of any network request: loading, success (data), and error.


References

Additional Resources