Welcome to the client side!
In the previous chapters, we built a robust backend:
However, if you visit your server now, you just get raw data. A normal user doesn't want to read JSON text; they want to click buttons and see folder icons.
In this chapter, we will build the Frontend Data Layer. This is the "Messenger" that lives in your web browser. It connects the visual interface (buttons) to the backend API (server).
The Backend speaks JSON and HTTP Codes (like 200 OK or 403 Forbidden).
The User speaks Clicks and Visuals.
We need a translation layer that:
Filebrowser (built with Vue.js) divides this job into three parts:
api/files.js): A collection of functions that know exactly how to talk to the backend.store): The application's "Short-term Memory." It holds data like "Who is logged in?" or "What files are in this folder?"router): The map that decides which page to show based on the URL.
This is the specialized messenger. Instead of writing "Make a network request to /api/resources" inside every button in our app, we write a function called fetchFiles. The button just calls fetchFiles.
Imagine you are browsing a folder. The application needs to remember the list of files so it can draw them on the screen. It also needs to remember if you are an Admin or a regular user. This "memory" is called the State.
When you change the URL from /login to /files, the whole page doesn't reload. The Router swaps the "Login Component" for the "Files Component" instantly. It also checks if you have the "ticket" (Authentication) to enter that page.
Let's look at a classic scenario: The user clicks on the "Documents" folder.
GET /api/resources?path=/Documents.
Let's look at frontend/src/api/files.js. This is how the frontend asks the backend for data.
// frontend/src/api/files.js
export async function fetchFiles(source, path, content = false) {
// 1. Construct the URL (e.g., /api/resources?path=/photos)
const apiPath = getApiPath('api/resources', {
path: path,
source: source,
...(content && { content: 'true' })
})
// 2. Perform the network request
const res = await fetchURL(apiPath)
// 3. Convert response to JSON and return it
const data = await res.json()
return adjustedData(data)
}
Explanation:
getApiPath: A helper that formats the URL correctly.fetchURL: A wrapper around the browser's native fetch command. It adds your Authentication Token automatically (from Chapter 2).adjustedData: Cleans up the data (e.g., formatting dates) before the app uses it.Reading is easy. But what about uploading? Uploading requires a progress bar, so the user knows the app hasn't frozen.
Filebrowser uses XMLHttpRequest (an older but powerful browser tool) for uploads because it supports progress monitoring better than the standard fetch in some cases.
// frontend/src/api/files.js
export function post(source, path, content, overwrite, onupload) {
const apiPath = getApiPath("api/resources", { path, source, override: overwrite });
const request = new XMLHttpRequest();
// 1. Open the connection
request.open("POST", apiPath, true);
// 2. Listen for progress (for the loading bar)
request.upload.onprogress = (event) => {
const percent = Math.round((event.loaded / event.total) * 100);
onupload(percent); // Update the UI
};
// 3. Send the file data
request.send(content);
}
Explanation:
onupload: This is a "Callback function." The UI passes this function to the API. Every time a chunk of data is sent, the API calls this function to say "Hey, we are at 50%!".request.send(content): This streams the file binary data to the backend we built in Chapter 3.When you type a URL into the browser, the Router takes over before any data is fetched. It acts as the bouncer.
In frontend/src/router/index.ts, the beforeResolve function runs every time you change pages.
// frontend/src/router/index.ts
router.beforeResolve(async (to, from, next) => {
// 1. Check if the page requires login
if (to.matched.some((record) => record.meta.requiresAuth)) {
// 2. If we don't know the user yet, try to validate the current session
if (!state.user.username) {
await validateLogin();
}
// 3. If still not logged in, kick them out
if (!getters.isLoggedIn()) {
next({ path: "/login" });
return;
}
}
// 4. Allowed! Proceed to page.
next();
});
Explanation:
meta.requiresAuth: Some pages (like /public) don't need a password. This checks if the destination is private.validateLogin: Checks if the browser has a valid cookie/token (from Chapter 2).next(): The command that tells the router "Let them pass."We've talked about the "Store" (or State). In Filebrowser, this is a central object that holds the truth about the application.
While the file content isn't shown in the snippets provided, frontend/src/store/index.ts initializes this memory bank.
// frontend/src/store/index.ts
import { state } from "./state.js";
import { getters } from "./getters.js";
import { mutations } from "./mutations.js";
// Export the centralized brain of the app
export {
state, // The Data (e.g., { user: "admin", theme: "dark" })
getters, // Calculated Data (e.g., isLoggedIn = user !== null)
mutations // Functions to change Data (e.g., setUser("alice"))
};
state.user directly; we call mutations.setCurrentUser(...). This ensures changes are tracked and reactive.In this chapter, we built the invisible machinery of the frontend:
api/files.js) translates user actions into HTTP requests (GET, POST, DELETE).store/index.ts) acts as the application's memory.router/index.ts) manages navigation and protects private pages from unauthenticated users.Now that we can fetch data and manage our location in the app, we need to actually draw the interface. We need buttons, lists, icons, and layout.
Next Chapter: Frontend View Layer
Generated by Code IQ