This example shows the complete journey of a login flow, from what happens in the user’s browser to how the backend handles the request. Even though the user only sees a simple form, several steps work together behind the scenes: React stores the typed values, the browser sends them to the backend using fetch, the server checks the credentials, and if they match, it returns a token that the browser saves. Below is the full code for both the frontend and backend, along with the folder structure they live in.
Frontend: This component displays the form, collects what the user types, and sends it to the backend when the login button is pressed.
import { useState } from "react";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const login = async (e) => {
e.preventDefault();
const res = await fetch("/login", {
method: "POST",
headers: { "Content-Type":"application/json" },
body: JSON.stringify({ email, password })
});
const data = await res.json();
localStorage.setItem("token", data.token);
window.location = "/";
};
return (
<form onSubmit={login}>
<h3>Login</h3>
<input placeholder="Email" onChange={e => setEmail(e.target.value)} />
<br />
<input type="password" placeholder="Password" onChange={e => setPassword(e.target.value)} />
<br />
<button>Login</button>
</form>
);
}
Backend: This code listens for requests on /login, checks the credentials, and responds with a token if they match the test user.
import express from "express";
const app = express();
app.use(express.json());
// Fake test user (like a tiny in-memory DB)
const user = {
email: "[email protected]",
password: "1234"
};
// Login endpoint
app.post("/login", (req, res) => {
const { email, password } = req.body;
// Check if email+password match our fake user
if (email === user.email && password === user.password) {
return res.json({ token: "FAKE_TOKEN_ABC123" });
}
// If not valid:
res.status(401).json({ error: "Invalid credentials" });
});
app.listen(3000, () => console.log("Server running on <http://localhost:3000>"));
1️⃣ The user opens /login in the browser
React loads the login page and shows the form.
At this point, nothing has been sent to the server — the data only lives in the browser.
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
2️⃣ The user types in email and password
Every keystroke updates React state. The server still has no idea what the user typed yet — it’s only stored in the browser.
<input placeholder="Email" onChange={e => setEmail(e.target.value)} />
<input type="password" onChange={e => setPassword(e.target.value)} />
3️⃣ The user clicks “Login”
When the button is pressed, React intercepts the form submit. Instead of refreshing the page, it runs the login function.
const login = (e) => {
e.preventDefault(); // stop page reload
// next step: send data to the server
};
4️⃣ The browser sends an HTTP request to the backend