Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungRefreshed repository pull requests page generally available(22.09.2026 um 03:25 Uhr)
Sichere ProgrammierungThe Joy of Learning the Basics Again(22.09.2026 um 03:28 Uhr)
Sichere ProgrammierungZero-Code OpenTelemetry Tracing for Dagster(22.09.2026 um 03:39 Uhr)
Linux Tipps & Hardening`prime-all`(22.09.2026 um 02:28 Uhr)
IT Security Toolsopensoho v0.15.2(22.09.2026 um 03:33 Uhr)
IT Security NachrichtenUS Proposes AI Incident Alert System in Talks With China, Bessent Says(22.09.2026 um 04:01 Uhr)
Sichere ProgrammierungRefreshed repository pull requests page generally available(22.09.2026 um 03:25 Uhr)
Sichere ProgrammierungThe Joy of Learning the Basics Again(22.09.2026 um 03:28 Uhr)
Sichere ProgrammierungZero-Code OpenTelemetry Tracing for Dagster(22.09.2026 um 03:39 Uhr)
Linux Tipps & Hardening`prime-all`(22.09.2026 um 02:28 Uhr)
IT Security Toolsopensoho v0.15.2(22.09.2026 um 03:33 Uhr)
IT Security NachrichtenUS Proposes AI Incident Alert System in Talks With China, Bessent Says(22.09.2026 um 04:01 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

Day 47 of #100DayOfCode — Creating Frontend of Auth System

For day 47, the goal was to create the frontend UI of the Authentication System using (TSX) and TailwindCSS, and connect it to its backend. Link to creating the Auth System Backend on Day 40 Day 40 of #100DayOfCode — Building a M…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

For day 47, the goal was to create the frontend UI of the Authentication System using (TSX) and TailwindCSS, and connect it to its backend.



Link to creating the Auth System Backend on Day 40











TL;DR



A walkthrough of building a React TypeScript frontend for a Node.js auth system, covering why interfaces come before components, how 8 states in a single App component drive the entire UI without a router or context, and how three sequential Axios calls to POST /auth/register, POST /auth/login, and GET /users/:id connect the frontend to a JWT-protected backend.









Table of Contents




  1. Why React with TypeScript instead of JavaScript

  2. Why TypeScript Interfaces Before the Components?

  3. The Register Component

  4. The Login Component

  5. The Dashboard Component

  6. The App Component and Its States

  7. The Handler Functions and API Calls

  8. Putting It All Together

  9. Final Result









Enabling Backend ↔ Frontend Communication With CORS



To make the frontend talk with the backend, we need to install cors and mention it in the server.ts



First, install cors:




npm install cors






Then, mention it in the server.ts:




// app.js
import cors from "cors";
app.use(cors()); // add this before your routes






This ensures the browser doesn’t block API requests.









Why React with TypeScript instead of JavaScript



Using TypeScript with React was a better choice for this project because when your frontend is tightly coupled to a backend API, you need to know exactly what shape the data coming from that API looks like. Defining interfaces like User, Form, and Status means TypeScript catches mismatches between what the API returns and what the components expect at compile time rather than silently breaking the UI at runtime. On top of that, with props flowing from App down into three separate components, TypeScript ensures you never pass the wrong value or forget a required prop — mistakes that JavaScript would only surface as bugs in the browser.









Why TypeScript Interfaces Before the Components?



Before writing a single component, I defined four sets of interfaces at the top of the file. Here's why that matters.






Data Interfaces






interface Form {
name: string;
email: string;
password: string;
}

interface User {
id: number;
name: string;
email: string;
}

interface Status {
message: string;
}






These three interfaces describe the shape of the data flowing through the app.





  • Form represents what the user types into input fields. It is shared across Register and Login. Both forms use the same state object in App, so having one interface for both keeps things consistent.


  • User represents what comes back from the backend API when you fetch a user. The backend returns an object with id, name, and email. This interface mirrors that exactly. If the backend ever adds a field, you update it here, and TypeScript immediately tells you every place that needs updating.


  • Status is the simplest one — it just holds a message string used to show success or error feedback in the UI.






Prop Interfaces






interface RegisterProps {
form: Form;
setForm: (form: Form) => void;
onRegister: () => void;
loading: boolean;
status: Status | null;
setCurrentView: (view: string) => void;
}






This is where TypeScript pays off the most. Every component in this app receives data from App via props. Without prop interfaces, you could pass the wrong type and only find out at runtime. With prop interfaces, TypeScript will tell you at compile time if you forget to pass a prop, pass the wrong type, or misname something.



The LoginProps and DashboardProps interfaces follow the same pattern; each one declares exactly what that component expects to receive, nothing more, nothing less.









The Register Component






const Register = ({ form, setForm, onRegister, loading, status, setCurrentView }: RegisterProps) => {






Register is a purely presentational component; it does not fetch any data or manage any state of its own. It receives everything it needs from App via props and just renders a form.



What it receives:





  • form — the current values of the input fields (name, email, password)


  • setForm — the function to update those values as the user types


  • onRegister — the function to call when the Register button is clicked (this is handleRegister from App)


  • loading — a boolean that disables the button and shows "Registering..." while the API call is in progress


  • status — the success or error message to display after the API responds


  • setCurrentView — used by the "Already have an account? Login" link to switch to the Login view



What it does to the UI:

When the user fills in the fields and clicks Register, it calls onRegister, which triggers an API call in App. While waiting, loading becomes true and the button text changes to "Registering..." and becomes disabled — preventing double submissions. Once the API responds, status gets populated, and a message appears below the button.







The Login Component





const Login = ({ form, setForm, onLogin, loading, status, setCurrentView }: LoginProps) => {





Login is structurally identical to Register, but with one important difference — it only has email and password fields. There is no name field because the login endpoint (POST /api/auth/login) only accepts email and password. Sending the name to it would be unnecessary.



What it receives:





  • form — same shared form state from App


  • setForm — updates the form state as the user types


  • onLogin — calls handleLogin from App when the Login button is clicked


  • loading — disables the button and shows "Logging in..." during the API call


  • status — shows the error message if login fails (wrong password, user not found, etc.)


  • setCurrentView — used by the "Don't have an account? Register" link



What it does to the UI:

On a successful login, the user never sees a status message; they are immediately redirected to the Dashboard because handleLogin calls setCurrentView("dashboard") on success. The status message only appears when something goes wrong, like an incorrect password or a network error.







The Dashboard Component





const Dashboard = ({ users, loggedInUser, selectedUser, onSelectUser, onLogout }: DashboardProps) => {





Dashboard is the most complex component visually, but like Register and Login, it does not manage any state or make any API calls itself. Everything is passed down from App.



What it receives:





  • users — the array of all users fetched from GET /api/users right after login


  • loggedInUser — the currently authenticated user object, used to show "Welcome back, [name]"


  • selectedUser — a single user object fetched from GET /api/users/:id when the View button is clicked


  • onSelectUser — calls handleSelectUser in App with the clicked user's ID


  • onLogout — calls handleLogout in App to clear all state and return to Login



What it renders:

It shows two panels side by side. The left panel is the users list — every user from the API is rendered as a row with a View button. The right panel only appears when selectedUser is not null — it shows the details of whichever user was last clicked. This is controlled purely by the state in App, not by any local state in Dashboard.







The App Component and Its States



App is the brain of the entire frontend. Every piece of data and every handler function lives here and gets passed down to the components.




const [currentView, setCurrentView] = useState<string>("login");






This is the single state that controls which component is visible. It starts as "login", so the user sees the Login form first. It switches to "register" or "dashboard" based on user actions and API responses. This replaces the need for a routing library entirely.




const [token, setToken] = useState<string | null>(null);






This holds the JWT token returned by the login API. When it is null, the user is unauthenticated. When it holds a string, the user is logged in. It is also used as a guard; the Dashboard only renders when token is not null, so even if currentView is somehow set to "dashboard", nothing shows without a token.




const [loggedInUser, setLoggedInUser] = useState<User | null>(null);






This holds the user object returned by the login API. It is used in the Dashboard to show "Welcome back, [name]". It is separate from token because the token is just a string used for auth headers, while this holds the actual user data for display.




const [users, setUsers] = useState<User[]>([]);






This holds the array of all users fetched immediately after login. It starts as an empty array and gets populated by the GET /api/users call inside handleLogin. It feeds the users' list in the Dashboard.




const [selectedUser, setSelectedUser] = useState<User | null>(null);






This holds the single user object fetched when someone clicks View in the Dashboard. It starts as null — the detail panel is hidden. Once a user is clicked, it gets populated with that user's data, and the detail panel appears.




const [form, setForm] = useState<Form>({ name: "", email: "", password: "" });






This is the single form state shared between Register and Login. Both components read from and write to this same object. When a view switch happens or a submission succeeds, the form is reset to empty strings to clear the input fields.




const [loading, setLoading] = useState<boolean>(false);






This becomes true at the start of any API call and false when it finishes, whether it succeeded or failed. It is passed to Register and Login to disable their buttons during the call, preventing the user from submitting twice.




const [status, setStatus] = useState<Status | null>(null);






This holds the last feedback message — either a success message like "Registered! Please login." or an error message from the API like "Email already in use." It is set to null at the start of every API call to clear any previous message before showing a new one.









The Handler Functions and API Calls






handleRegister — POST /api/auth/register






const handleRegister = async () => {
setLoading(true);
setStatus(null);
try {
await axios.post(`${API_URL}/auth/register`, {
name: form.name,
email: form.email,
password: form.password,
});
setStatus({ message: "Registered! Please login." });
setForm({ name: "", email: "", password: "" });
setCurrentView("login");
} catch (error) {
const err = error as AxiosError<{ message: string }>;
setStatus({ message: err.response?.data?.message || "Registration failed." });
}
setLoading(false);
};






What it does:

This function sends the user's name, email, and password to POST /api/auth/register. The backend validates the data, hashes the password, saves the user to MongoDB, and returns the created user object.



Why await without destructuring:

Unlike the login call, we do not need anything from the response body here. We just need to know it succeeded. So we await the promise but do not capture the return value.



Effect on the frontend:

On success, the form clears, a success message appears, and the view switches to Login. The user is not auto-logged in; they must log in manually after registering. On failure, the error message from the backend (like "Email already in use") is shown in the status paragraph below the button.







handleLogin — POST /api/auth/login + GET /api/users





const handleLogin = async () => {
setLoading(true);
setStatus(null);
try {
const { data } = await axios.post(`${API_URL}/auth/login`, {
email: form.email,
password: form.password,
});
setToken(data.token);
setLoggedInUser(data.user);
setForm({ name: "", email: "", password: "" });
const { data: usersData } = await axios.get(`${API_URL}/users`, {
headers: { Authorization: `Bearer ${data.token}` },
});
setUsers(usersData);
setCurrentView("dashboard");
} catch (error) {
const err = error as AxiosError<{ message: string }>;
setStatus({ message: err.response?.data?.message || "Login failed." });
}
setLoading(false);
};





What it does:

This function makes two API calls sequentially — first a POST to authenticate, then a GET to load the dashboard data.



First call — POST /api/auth/login:

Sends email and password. The backend verifies the password against the hashed version in MongoDB, and if correct, generates and returns a JWT token along with the user object. The token is stored in the token state and the user object in loggedInUser.



Second call — GET /api/users:

This is a protected route — the backend's auth middleware checks the Authorization header before allowing access. So we pass Bearer ${data.token} in the header. Note we use data.token here (from the login response), not the token state — because React state updates are asynchronous and token state may not have updated yet by the time this second call fires.



Why two calls in one handler:

The dashboard needs the users' list to be ready the moment it appears. If we switched to the dashboard first and fetched users inside a useEffect there, the user would see a blank list for a moment. Fetching before switching views means the data is ready before the dashboard renders.



Effect on the frontend:

On success, the token and user are stored, the form clears, users are loaded, and the view switches to Dashboard. On failure, the error from the backend appears (wrong password, user not found, etc.), and the view stays on Login.







handleSelectUser — GET /api/users/:id





const handleSelectUser = async (id: number) => {
try {
const { data } = await axios.get(`${API_URL}/users/${id}`, {
headers: { Authorization: `Bearer ${token}` },
});
setSelectedUser(data);
} catch {
console.log("Failed to fetch user");
}
};





What it does:

When the user clicks View on any row in the users list, this function fires with that user's id. It calls GET /api/users/:id — another protected route — with the stored token in the header.



Why fetch by ID instead of using the list data:

The users' list only contains basic info (name, email). A detailed view would typically show more fields. By fetching the individual user, you get the full document from the backend, which may contain additional fields not included in the list response.



Effect on the frontend:

On success, selectedUser is populated with the returned user object, and the detail panel appears on the right side of the Dashboard. On failure, a message is logged to the console (this could be upgraded to show a UI error in a production app).







Putting It All Together



The entire frontend flow looks like this:




App loads → currentView = "login" → Login component renders

User submits login form → handleLogin fires.

POST /api/auth/login → token + user received

GET /api/users → users list received

currentView = "dashboard" → Dashboard renders with data ready

User clicks View → handleSelectUser fires.

GET /api/users/:id → single user received → detail panel appears

User clicks Logout → all state cleared → currentView = "login"






No routing library, no global state management, no context. Just one currentView state and props flowing downward — which is exactly the right level of complexity for a project at this scale.









Final Result:






Register



Register









Login



Login









Dashboard (After successfully logging in)



Dashboard









Wrap Up



This session was all about understanding how authentication works end-to-end, from a user typing their credentials into a form, to a JWT being generated on the backend, to MongoDB storing the user, to the frontend receiving the token and using it to access protected routes. Putting the frontend and backend together and seeing them actually communicate made it feel like a proper full-stack app, even if it is a simple one.



Thanks for reading. Feel free to share your thoughts!

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten Day 47 of #100DayOfCode — Creating Frontend of Auth System

Thematisch verwandte Begriffe: 100DayOfCode, Creating, Frontend, Auth · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-49449 | Joplin is an open source note-taking and to-do application that organise…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick