HTML, CSS, JavaScript — the three languages of the browser

Every web page, no matter how fancy, eventually becomes three things in the browser:

HTML — the structure
The skeleton of the page. Tags like <header>, <article>, <p>, <button> describe what the content is. HTML carries semantics: this is a heading, this is a list, this is a navigation block.
CSS — the styling
What it looks like. Color, layout, spacing, typography, animation. CSS is rule-based: "every paragraph inside a card has a 16px font and 1.6 line-height."
JavaScript — the behavior
What it does. Reactivity. Network calls. Animation logic. Form validation. Anything dynamic. JavaScript is the only programming language that runs in the browser.

Frameworks like React don't replace these — they sit on top of them. React generates HTML and uses JavaScript to update it; Tailwind generates CSS. The substrate is always HTML/CSS/JS.

The frontend framework landscape

You don't write React from scratch every time. You don't write CSS from scratch every time. You pick a framework that does most of the wiring and concentrate on the parts unique to your app.

React

The component library. Build UI by composing reusable pieces ("components"). Dominant on most surveys. Industrial-strength but not opinionated about routing, data fetching, or build setup — that's why frameworks are built on top of it.

Next.js

The framework around React. Adds routing, data fetching, server rendering, image optimization, an opinionated project layout. The default for most new commercial apps. Made by Vercel.

Vue

The friendlier alternative. Different syntax, similar shape. Big in Europe and Asia. Pairs with Nuxt the way React pairs with Next.

Svelte / SvelteKit

The compile-step minimalist. Writes very little JavaScript at runtime by doing the work at build time. Beloved by people who've used React for years.

Astro

The content-first one. Ships zero JavaScript by default. Great for marketing sites, blogs, docs. Lets you drop React/Vue/Svelte in only where you need interactivity.

Remix / TanStack Start

The newer challengers. Different opinions about how server and client should talk. Worth knowing the names.

The backend — runtimes and languages

The frontend has one runtime (the browser) and one language (JavaScript). The backend is wide-open. Common choices:

Node.js (JavaScript on the server)
The default for most modern web apps. Same language as your frontend. Huge ecosystem (npm). Runs on virtually every host.
Python (FastAPI, Django, Flask)
The default for AI/ML adjacent work, data pipelines, scripts. Excellent libraries. Slightly worse cold-start performance than Node.
Go
Compiled, fast, great concurrency. Used at Google, Cloudflare, Docker. Fewer libraries, simpler language.
Ruby (Rails)
Productive, opinionated, a bit retro. Still common at companies founded in the 2010s.
Bun, Deno
Newer JavaScript runtimes that fix Node's older sins. Faster, more secure, more batteries-included. Worth knowing about; not yet defaults.

For a solo founder or small team in 2025, Node.js plus a TypeScript-first framework (Next.js, Hono, FastAPI if you prefer Python) is the safest default by a wide margin. The agent has seen the most code.

APIs — REST and GraphQL

The frontend talks to the backend over an API (Application Programming Interface). The API is a contract: "if you send me X, I'll give you Y." Two main flavors:

Most APIs you'll meet are RESTful, more or less. The convention is resources + verbs: a URL identifies a thing, the HTTP method (GET, POST, PUT, DELETE) says what to do with it.

HTTP
GET /api/tasks → list all tasks GET /api/tasks/abc123 → get one task POST /api/tasks → create a task PUT /api/tasks/abc123 → replace a task PATCH /api/tasks/abc123 → update a task DELETE /api/tasks/abc123 → delete a task Response body is JSON.

The advantage: simple, predictable, browser-friendly, every tool understands it. The disadvantage: clients often need data from many resources at once, leading to chatty interfaces.

An alternative where the client asks for exactly the fields it wants and the server returns exactly those. One endpoint (usually /graphql) handles everything via a query language.

GRAPHQL
query { tasks(workspace: "ws_123") { id title status assignee { name email } } }

The advantage: one round-trip for complex screens, no over-fetching. The disadvantage: more setup, harder caching, an extra concept to learn. Many teams that adopted GraphQL have since moved back to REST.

Databases — where the data lives

Behind the API. Never directly accessed by the browser. Two broad categories:

Relational (SQL)

Postgres, MySQL, SQLite. Tables with rows and columns. Strong types, joins, transactions. The default for most apps because most apps have structured data with relationships.

Document (NoSQL)

MongoDB, DynamoDB. JSON-like documents in collections. Flexible schemas. Used to be trendy as a "Postgres alternative;" the consensus has shifted back toward Postgres for most workloads.

Key-Value

Redis, Memcached. Just keys and values. Fast. Used as a cache, for sessions, for rate limits — alongside a real database, not instead of one.

Vector (AI)

Pinecone, Weaviate, pgvector. Stores embeddings — number-array representations of text/images. Used for semantic search and retrieval-augmented generation. New in the stack since the LLM boom.

Postgres is the default — and what an ORM is

For 90% of new projects in 2025, the right answer is Postgres, hosted somewhere managed (Neon, Supabase, Render). Battle-tested, cheap to start, scales much further than you'll need.

You probably won't write raw SQL all the time. An ORM (Object-Relational Mapper) lets you query the database using your language's objects:

TS
// raw SQL const tasks = await db.query("SELECT * FROM tasks WHERE status = 'todo'"); // Prisma (an ORM) const tasks = await prisma.task.findMany({ where: { status: 'todo' } });

Same query, different ergonomics. ORMs (Prisma, Drizzle, SQLAlchemy, ActiveRecord) save time, hide cost — they can quietly generate slow queries. Most teams use one anyway.

Auth — proving who you are

Auth is short for two related things:

Authentication (authn)
Proving who you are. The login flow. Passwords, magic links, social login.
Authorization (authz)
What you're allowed to do once you're in. Permissions, roles, ownership.

The patterns you'll meet:

Sessions (the classic)

After login, the server creates a record (a "session") and gives the browser a cookie identifying it. Every subsequent request, the browser sends the cookie back; the server looks up the session. Stateful — the server remembers. Battle-tested, simple, secure with httpOnly cookies. Used by Rails, Django, Laravel, NextAuth's database-session mode.

JWT (JSON Web Tokens)

The server signs a token after login containing the user's identity. The browser sends it back on every request. Stateless — the server doesn't remember; it just verifies the signature. Useful in distributed systems where multiple servers can't share session state. Pronounced "jot."

OAuth ("Sign in with…")

A protocol for letting users log in with another service. You don't see their password — you get a token from Google/GitHub/Apple saying "this is them." OAuth 2.0 is the version everything uses now (RFC 6749). Almost always implemented with a library, never from scratch.

Magic links / OTP / passkeys

Modern alternatives to passwords. Magic links email a one-time URL; click it, you're in. OTP is a one-time passcode sent via email or SMS. Passkeys use the device's biometrics and a cryptographic key — the post-password future. All three remove the "users pick bad passwords" failure mode.

Environment variables and secrets

Two categories of configuration any app has:

Public config
API base URLs, feature flags, the app's name. Usually fine to be visible in the frontend bundle.
Secrets
Database passwords, API keys, signing keys. Catastrophic if leaked. Must never appear in client-side code, in commits, or in screenshots.

Both live in environment variables — values the code reads at runtime that vary by environment. Locally they live in a .env file (git-ignored); in production they're entered into the deployment platform's UI or CLI.

ENV
# .env.local — your laptop only, never committed DATABASE_URL="postgres://user:pass@localhost/tasklane_dev" AUTH_SECRET="dev-secret-not-for-prod" RESEND_API_KEY="re_dev_..." # All of these are different in staging and prod.
End of level

Wrap-up

Jargon recap

HTML / CSS / JS
Structure / styling / behavior — the three languages of the browser.
Frontend framework
React, Vue, Svelte. Build UI from components.
Meta-framework
Next.js, Nuxt, SvelteKit. Adds routing, server rendering, project shape.
Backend / runtime
Node, Python, Go. The language and engine that runs server code.
API
Contract between frontend and backend. REST or GraphQL.
Database
Postgres, MySQL, MongoDB, SQLite. Where data lives.
ORM
A library that lets you query a DB using objects instead of raw SQL.
Auth (authn / authz)
Proving who you are / what you're allowed to do.
Env var / Secret
Configuration / sensitive configuration. Never in code, never in commits.

You should now be able to

Mini-exercise

Pick an app you use daily (Twitter, Notion, Linear, your bank). Sketch the stack you think it has — frontend framework, backend language, database, auth method, where it's hosted. You won't always be right, but the act of sketching forces the right questions.