Chapter 19 Quiz: Full-Stack Application Development

Test your understanding of full-stack integration concepts. Try to answer each question before revealing the answer.


Question 1

What does CORS stand for, and why does the browser enforce it?

Show Answer CORS stands for **Cross-Origin Resource Sharing**. The browser enforces it as a security mechanism to prevent malicious websites from making unauthorized requests to APIs on different origins. Without CORS, any website could make requests to your banking API using your stored cookies, potentially performing actions on your behalf. CORS requires the server to explicitly declare which origins are allowed to access its resources through response headers like `Access-Control-Allow-Origin`.

Question 2

In a React + FastAPI application running in development, the React app is at http://localhost:5173 and the API is at http://localhost:8000. Are these the same origin or different origins? Why?

Show Answer These are **different origins**. An origin is defined by the combination of scheme (protocol), host, and port. While both use `http` and `localhost`, they have different ports (5173 vs. 8000). Any difference in scheme, host, or port makes them different origins. This is why CORS configuration is required for the frontend to communicate with the backend during development.

Question 3

What is the purpose of the allow_credentials=True setting in FastAPI's CORS middleware?

Show Answer The `allow_credentials=True` setting allows the browser to include credentials (cookies, authorization headers, TLS client certificates) in cross-origin requests. Without this setting, even if CORS is configured to allow the frontend's origin, the browser will strip cookies and authentication headers from the request. This is essential when using httpOnly cookies for JWT storage or when the frontend needs to send the `Authorization` header with bearer tokens across origins.

Question 4

Why should you NOT set Content-Type header manually when uploading files with FormData in JavaScript?

Show Answer When using `FormData`, the browser automatically sets the `Content-Type` header to `multipart/form-data` with a unique **boundary** string that separates the different parts of the request body. If you manually set `Content-Type`, you either omit the boundary (causing the server to fail parsing the request) or set it to an incorrect value like `application/json`. The browser must generate the boundary itself because it corresponds to the actual encoding of the form data.

Question 5

What is the difference between an optimistic update and a pessimistic update in the context of state synchronization?

Show Answer An **optimistic update** immediately updates the UI before the server confirms the change, assuming the operation will succeed. If the server rejects the change, the UI rolls back to the previous state. This makes the app feel faster because the user sees instant feedback. A **pessimistic update** waits for the server to confirm the change before updating the UI. The user sees a loading state while waiting. This is safer because the UI always reflects the actual server state, but it feels slower. Optimistic updates are best for operations that rarely fail (toggling a checkbox, updating text). Pessimistic updates are better for operations that might fail (payment processing, operations with complex validation).

Question 6

In the JWT authentication flow described in this chapter, what information is stored inside the JWT token?

Show Answer The JWT token contains a **payload** (also called claims) with: - `sub` (subject): The user's ID, encoded as a string - `exp` (expiration): A timestamp indicating when the token expires The token also includes a **header** specifying the signing algorithm (HS256) and a **signature** created using the secret key. The token does NOT contain the user's password. The server can verify the token's authenticity and extract the user ID without making a database query, though it typically fetches the full user object from the database using the extracted ID.

Question 7

Why is it important to validate data on both the frontend AND the backend?

Show Answer **Frontend validation** provides immediate feedback to the user, improving the user experience. However, it can be bypassed entirely — a user can disable JavaScript, modify the DOM, or send requests directly to the API using tools like `curl` or Postman. **Backend validation** is the authoritative check that protects data integrity and security. It cannot be bypassed because all data must pass through the server. Both are needed: frontend validation for UX (fast feedback), backend validation for security (data protection). Never rely solely on frontend validation for security-critical checks.

Question 8

What is the purpose of a reverse proxy like Nginx in a production full-stack deployment?

Show Answer A reverse proxy like Nginx serves multiple purposes: 1. **Routing**: Directs API requests (`/api/*`) to the FastAPI backend and all other requests to the React static files 2. **Static file serving**: Efficiently serves the built React frontend files (HTML, CSS, JS) 3. **SSL/TLS termination**: Handles HTTPS encryption so backend services can use plain HTTP internally 4. **Load balancing**: Distributes requests across multiple backend instances 5. **CORS elimination**: When both frontend and API are served from the same origin through Nginx, CORS is no longer needed 6. **WebSocket proxying**: Upgrades HTTP connections to WebSocket connections and proxies them to the backend 7. **Security**: Hides backend server details and can add security headers

Question 9

Explain why Vite requires the VITE_ prefix for environment variables exposed to the frontend.

Show Answer The `VITE_` prefix is a **security feature**. Environment variables without the prefix (like `DATABASE_URL` or `SECRET_KEY`) are only available during the build process on the server. Variables with the `VITE_` prefix are embedded into the JavaScript bundle that is sent to the browser. Without this prefix requirement, a developer might accidentally expose server-side secrets (database passwords, API keys) in the client-side JavaScript bundle, where they would be visible to anyone who opens the browser's developer tools. The explicit prefix forces developers to consciously choose which variables should be public.

Question 10

How does WebSocket authentication differ from HTTP authentication in browser-based applications?

Show Answer In HTTP requests, you can include custom headers like `Authorization: Bearer ` with every request. The browser's WebSocket API (`new WebSocket(url)`) does **not** support custom headers. Therefore, WebSocket authentication typically uses one of these approaches: 1. **Query parameter**: Pass the token in the URL: `ws://server/ws?token=`. Simple but the token appears in server logs and URL history. 2. **First message**: Connect without auth, then send the token as the first WebSocket message. The server validates it before accepting further messages. 3. **Cookie-based**: If authentication uses httpOnly cookies, the browser automatically includes them during the WebSocket handshake. The chapter demonstrates the query parameter approach for simplicity.

Question 11

What is the difference between a monorepo and separate repositories for a full-stack application? Name one advantage of each.

Show Answer A **monorepo** stores frontend and backend code in the same repository. **Advantage**: Atomic commits across the stack — when you change an API endpoint and its corresponding frontend component, both changes are in a single commit, making it easier to keep them synchronized. The AI assistant can also see both codebases in a single context. **Separate repositories** store frontend and backend in different repos. **Advantage**: Independent deployment — you can deploy a frontend fix without redeploying the backend, and vice versa. Each team can have its own release cycle, CI/CD pipeline, and access controls.

Question 12

What happens if a JWT token expires while a user is actively using the application?

Show Answer When the JWT expires, the next API request the user makes will fail with a **401 Unauthorized** response because the backend's token validation will detect that the `exp` claim is in the past. The frontend should detect this 401 response and either: 1. **Redirect to login**: Clear the stored token and send the user to the login page to re-authenticate. 2. **Use a refresh token**: If the application implements refresh tokens, automatically request a new access token using the refresh token (which has a longer expiration). This provides a seamless experience without forcing the user to re-enter credentials. The chapter's implementation uses approach #1 (redirect to login) for simplicity.

Question 13

Why should allow_origins=["*"] not be used in production CORS configuration?

Show Answer Setting `allow_origins=["*"]` allows **any** website on the internet to make requests to your API. This is dangerous because: 1. A malicious website could make API requests on behalf of a user who is logged in (if using cookies for auth) 2. It increases the attack surface for CSRF-like attacks 3. It provides no protection against data exfiltration through compromised third-party scripts In production, you should list only the specific origins that need access (e.g., `["https://myapp.com", "https://www.myapp.com"]`). This ensures that only your legitimate frontend can interact with your API. Note: `allow_origins=["*"]` also cannot be combined with `allow_credentials=True` per the CORS specification, which would break cookie-based authentication.

Question 14

Describe the role of Pydantic's BaseSettings class in environment configuration.

Show Answer Pydantic's `BaseSettings` (from the `pydantic-settings` package) provides: 1. **Automatic loading**: Reads values from environment variables and `.env` files automatically 2. **Type validation**: Ensures each variable matches its declared type (str, int, bool, etc.) at startup time, not at runtime when the variable is first used 3. **Required vs. optional**: Fields without default values are required — the app fails to start if they are missing, giving an immediate and clear error 4. **Default values**: Fields with defaults serve as fallbacks when the environment variable is not set 5. **Type coercion**: Automatically converts string environment variables to the appropriate Python types This catches configuration errors at application startup rather than at runtime, which is much easier to debug.

Question 15

What is the ConnectionManager pattern used for in WebSocket implementations?

Show Answer The `ConnectionManager` is a server-side class that: 1. **Tracks active connections**: Maintains a data structure (typically a dictionary mapping room/channel IDs to sets of WebSocket connections) of all currently connected clients 2. **Handles connect/disconnect**: Adds new connections when clients connect and removes them when clients disconnect 3. **Enables broadcasting**: Provides a method to send a message to all connections in a specific room, or to all connections globally 4. **Manages rooms/channels**: Groups connections so messages can be targeted to specific subsets of users (e.g., all users in a chat room, all users viewing the same project) Without a ConnectionManager, you would need to pass WebSocket references between different parts of your application, leading to tightly coupled and hard-to-maintain code.

Question 16

In the API client code from Section 19.3, why is the base URL stored in an environment variable rather than hardcoded?

Show Answer The API base URL changes between environments: - **Development**: `http://localhost:8000` - **Staging**: `https://api-staging.myapp.com` - **Production**: `https://api.myapp.com` If the URL were hardcoded, you would need to change the code and rebuild the frontend for each environment. Using an environment variable (`VITE_API_URL`) allows the same code to run in any environment — only the configuration changes. This is a core principle of the Twelve-Factor App methodology: strict separation of configuration from code.

Question 17

What is the "impedance mismatch" problem in full-stack development?

Show Answer The impedance mismatch problem refers to the differences in how each layer of the stack represents and handles data: - **Frontend (JavaScript)**: Uses camelCase naming, JavaScript objects, and component state - **Backend (Python)**: Uses snake_case naming, Pydantic models, and dictionaries - **Database (SQL)**: Uses snake_case column names, rows, and specific SQL types (TIMESTAMP, ARRAY, etc.) Data must be translated at each boundary. A `created_at` column in PostgreSQL becomes `created_at` in the Python model, but should be `createdAt` in the JavaScript frontend. Date values are stored as timestamps in the database, serialized as ISO strings in JSON, and displayed as formatted strings in the UI. These translations are where bugs commonly hide — a missing field conversion, wrong date format, or naming mismatch can cause subtle errors.

Question 18

Why does the file upload backend endpoint read the entire file into memory before validating its size?

Show Answer In the example code, `contents = await file.read()` reads the entire file into memory, then checks `len(contents) > MAX_FILE_SIZE`. This is the **simple but not optimal** approach. It means a malicious user could send a very large file that consumes server memory before being rejected. A better production approach would use **streaming validation**: read the file in chunks, tracking the total size, and reject the upload as soon as the size limit is exceeded without loading the entire file into memory. FastAPI's `UploadFile` supports this through its `read(size)` method with a chunk size parameter. The chapter uses the simpler approach for clarity, noting that production code should use cloud storage (like S3) with its own size limit enforcement.

Question 19

What is the purpose of try_files $uri $uri/ /index.html; in the Nginx configuration?

Show Answer This Nginx directive handles client-side routing for a single-page application (SPA). When a user navigates to a URL like `/dashboard/tasks`, Nginx first tries to find: 1. `$uri` — A file at that exact path (e.g., `/dashboard/tasks`) 2. `$uri/` — A directory at that path (e.g., `/dashboard/tasks/`) 3. `/index.html` — Falls back to the main HTML file Since the React SPA has only one HTML file (`index.html`) and handles all routing in JavaScript via React Router, requests for paths like `/dashboard/tasks` need to be served `index.html`. Without this directive, refreshing the browser on any route other than `/` would return a 404 error because there is no actual file at `/dashboard/tasks` on the server.

Question 20

What are the tradeoffs between storing JWT tokens in localStorage versus httpOnly cookies?

Show Answer **localStorage:** - Vulnerable to **XSS** (cross-site scripting) attacks — if an attacker injects JavaScript into the page, they can read the token with `localStorage.getItem('token')` and steal it - Not vulnerable to **CSRF** (cross-site request forgery) because the token must be explicitly added to request headers - Works easily with APIs on different domains - Simple to implement **httpOnly cookies:** - Protected from **XSS** — JavaScript cannot access httpOnly cookies, so even injected scripts cannot steal the token - Vulnerable to **CSRF** — the browser automatically sends cookies with requests, so a malicious site could trigger authenticated requests (mitigated with CSRF tokens or `SameSite` cookie attribute) - Requires the API to be on the same domain or a subdomain (or careful CORS configuration) - More complex to implement, especially for cross-origin setups For most applications, httpOnly cookies with `SameSite=Strict` provide stronger security.

Question 21

In a Docker Compose setup, why does the backend service use db:5432 as the database host instead of localhost:5432?

Show Answer In Docker Compose, each service runs in its own container with its own network namespace. `localhost` inside the backend container refers to the backend container itself, not the database container. Docker Compose creates a network where services can reach each other by their **service name**. The database service is named `db` in the `docker-compose.yml`, so the backend connects to it using the hostname `db`. Docker's internal DNS resolves `db` to the database container's IP address on the shared Docker network.

Question 22

What is the purpose of the depends_on key in Docker Compose, and what does it NOT guarantee?

Show Answer `depends_on` controls **startup order** — it ensures that the `db` service container is started before the `backend` service container. However, it does **NOT** guarantee that the database is **ready to accept connections** when the backend starts. The PostgreSQL container might be started but still initializing its database files. To handle this, you need either: 1. **Health checks** with `condition: service_healthy` — the dependent service waits until the health check passes 2. **Retry logic** in the backend — the application retries database connections on startup 3. **Wait scripts** like `wait-for-it.sh` — a script that blocks until the database port is accepting connections Most production setups use a combination of health checks and application-level retry logic.

Question 23

Why should the backend generate unique filenames for uploaded files rather than using the original filename?

Show Answer Using original filenames causes several problems: 1. **Collisions**: Two users uploading files with the same name (e.g., `photo.jpg`) would overwrite each other's files 2. **Security**: A malicious filename like `../../etc/passwd` could potentially write to unexpected locations (path traversal attack) 3. **Character encoding**: Original filenames may contain special characters, spaces, or non-ASCII characters that cause issues with filesystems or URLs 4. **Predictability**: If filenames are predictable, an attacker could guess the URL of other users' uploads Using a UUID-based filename (e.g., `a1b2c3d4e5f6.jpg`) eliminates all these issues. The original filename can be stored in the database for display purposes while the file itself uses the unique name on disk.

Question 24

What is the difference between proxy_pass and serving static files in the Nginx configuration?

Show Answer **`proxy_pass`** forwards the request to another running server (the FastAPI backend). Nginx acts as an intermediary: it receives the request from the client, forwards it to the backend at `http://127.0.0.1:8000`, receives the backend's response, and returns it to the client. The backend must be running as a separate process. **Static file serving** (`root` + `try_files`) reads files directly from the filesystem and returns them to the client. No other server is involved. Nginx reads the built React files (HTML, CSS, JS) from `/var/www/frontend/dist` and serves them directly. This is much faster than proxying because there is no inter-process communication. The Nginx configuration uses static serving for the React frontend (fast, no running process needed) and proxy_pass for API requests (needs the FastAPI process to handle dynamic logic).

Question 25

Explain the concept of "source of truth" in the context of a full-stack application. Where does the source of truth live for each of the following: user authentication status, task data, and UI state like whether a modal is open?

Show Answer The **source of truth** is the authoritative location for a piece of data — the place you trust when there is a conflict. - **User authentication status**: The source of truth is the **backend/database**. The backend knows whether the JWT is valid and whether the user account is active. The frontend's auth state is a cache that can become stale (e.g., if an admin disables the account while the user is logged in). - **Task data**: The source of truth is the **database**. The backend validates and stores task data. The frontend's task list is a cache that may be out of date. If there is a conflict (optimistic update fails), the frontend must defer to the server's response. - **UI state (modal open/closed)**: The source of truth is the **frontend**. Whether a modal is open is purely a UI concern that the backend has no knowledge of or need for. This state lives in React component state or a frontend state management library. The general principle: persistent, shared data has its source of truth on the server; ephemeral, local UI state has its source of truth on the client.