Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .husky/_/commit-msg
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
npx --no-install commitlint --edit "$1"
#!/usr/bin/env sh
. "$(dirname "$0")/h"
3 changes: 2 additions & 1 deletion .husky/_/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
npx lint-staged
#!/usr/bin/env sh
. "$(dirname "$0")/h"
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The goal is to foster consistent, maintainable, and testable code, serving as a

- [Core Principles & General Advice](#core-principles--general-advice)
- [Architectural Patterns & Design Choices](#architectural-patterns--design-choices)
- [Security & Best Practices](#security--best-practices)
- [Language & Framework Specific Remarks](#language--framework-specific-remarks)
- [Containerization & Deployment](#containerization--deployment)
- [Utilities](#utilities)
Expand Down Expand Up @@ -98,6 +99,18 @@ In-depth explanations of high-level architectural patterns and significant desig

---

## Security & Best Practices

Focused on protecting application integrity and sensitive data in modern web environments.

- [**API Key Security: Why `.env` is Not Enough for Frontend React**](./security/api-keys-tutorial-react.md)
- The "Tutorial Trap": Why client-side environment variables are still public.
- Using **Netlify Functions** (Serverless) as a secure backend proxy.
- Architectural benefits: Hiding sensitive logic and protecting API quotas.
- Practical implementation with React 19 hooks (`useTransition`, `useActionState`).

---

## Language & Framework Specific Remarks

Practical advice and common patterns specific to particular technologies.
Expand Down Expand Up @@ -310,6 +323,8 @@ This section outlines the current directory and file structure for the `coding-r
│ ├── pinia-store-encapsulation.md
│ ├── reactivity-shallowref.md
│ └── store-usage-scope.md
── security/ # Security patterns and tutorials
│ └── api-keys-tutorial-react.md # Deep dive into Serverless proxies
├── utils/ # General utility functions and classes
│ ├── boolean-utils.ts
│ ├── money-utils.ts
Expand Down
4 changes: 4 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,7 @@
## CI/CD

- [x] Guide to Containerizing a Modern JavaScript SPA (Vite) with a Multi-Stage Nginx Build

## Security

- [x] Securing API Keys with Serverless Proxies
76 changes: 76 additions & 0 deletions security/api-keys-tutorial-react.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Securing API Keys with Serverless Proxies

## 🛑 The Problem: The "Tutorial Trap"

Most React tutorials instruct developers to store API keys in an `.env` file. While this prevents keys from being committed to GitHub, it is important to understand that **environment variables prefixed with `VITE_` or `REACT_APP_` are embedded into the client-side bundle.**

Any key used in a client-side `fetch()` or `axios` call is still visible to anyone who opens the browser's "Network" tab. For a public API like **TMDB**, this exposes your quota and potentially your account to malicious actors.

---

## 💡 The Solution: Netlify Functions as a Proxy

Instead of calling the TMDB API directly from the React components, I architected a **Backend Proxy** using **Netlify Functions**. This ensures the API key never leaves the server environment.

### 1. The Architecture

- **Frontend:** Sends a request to a local endpoint: `/.netlify/functions/get-movies?query=...`
- **Serverless Function:** Resides on the server, retrieves the `TMDB_API_KEY` from the secure environment variables, and makes the actual request to TMDB.
- **Frontend:** Receives the clean JSON data without ever seeing the secret key.

### 2. Implementation Comparison

#### ❌ The Vulnerable Way (Client-Side)

```javascript
// DON'T DO THIS: The key is leaked to the browser's Network tab!
const API_KEY = import.meta.env.VITE_TMDB_KEY;
const response = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`,
);
```

#### ✅ The Secure Way (Netlify Function)

Create a file at `netlify/functions/get-movies.js`:

```javascript
exports.handler = async (event) => {
const API_KEY = process.env.TMDB_API_KEY; // Safely accessed on the server
const { query } = event.queryStringParameters;

try {
const response = await fetch(
`https://api.themoviedb.org/3/search/movie?api_key=${API_KEY}&query=${query}`,
);
const data = await response.json();

return {
statusCode: 200,
body: JSON.stringify(data),
};
} catch (error) {
return { statusCode: 500, body: error.toString() };
}
};
```

---

## ⚡ Enhancing UX with React 19 Hooks

To make the search experience seamless while the proxy is fetching data, I implemented:

- **`useTransition`**: Manages the UI state during the "search" transition, ensuring the input remains responsive even during network latency.
- **`useActionState`**: Handles the search form submission and state management natively, reducing boilerplate.
- **Skeleton Screens**: Improved perceived performance by replacing traditional loading spinners with layout placeholders that mimic the content structure.

---

## 🛡️ Security Checklist

As a trainer, I emphasize these three pillars for production-grade frontend engineering:

1. **Never trust the client-side**: Sensitive logic and secrets must stay on the server.
2. **Proxy third-party APIs**: Always use serverless functions or a backend to protect your quotas and keys.
3. **Manage Network Resources**: Use `AbortController` to cancel pending requests when a user navigates away or types a new query rapidly. This prevents race conditions and saves bandwidth.