explainx / blog
A depth-heavy reference guide covering the 50 foundational technical concepts behind every AI product — from JSON and HTTP to embeddings, RAG, and context windows. If you are building with AI tools and hitting unexplained errors, broken APIs, or mysterious failures, these are the 50 things you need to understand. Stop vibing. Start making.

Jun 21, 2026
Someone typed "who is json" into an AI coding tool and the internet lost it. The screenshot — "who is json | Full access" in what looks like Cursor — is the 2026 version of the localhost joke. It is funny. It also reveals something true about vibecoding: people are shipping real products without knowing what JSON is, and that has turned out to be both more fine and more dangerous than either camp wants to admit.
Jun 19, 2026
Claude Code Artifacts lets teams deploy shareable HTML apps from inside a coding session—private by default, shared within your org. Here's how it compares to Lovable, v0, Bolt, and Codex Sites.
Jun 29, 2026
video-use is an open-source skill for Claude Code (and Codex, Hermes, Openclaw) that edits videos via natural language — no timeline scrubbing, no NLE menus. It reads footage as transcript text, reasons over word-level timestamps, calls ffmpeg, self-evaluates every cut, and outputs final.mp4. 11.6k GitHub stars in two months. Here is the full setup and how it works.
The "who is json" moment is not the problem. The problem is building a product on a foundation you cannot see.
When everything works, not knowing what JSON is, what a 401 means, or why your database query is slow does not matter. When things break — and they always break — that invisible foundation becomes the only thing that matters. Your AI tool will generate an error message you cannot read. It will make a security decision you cannot evaluate. It will choose a data architecture you cannot extend. And you will be stuck, dependent on the AI to diagnose a problem neither of you fully understands.
This guide is not about learning to code. It is about building the mental models that make you effective at directing AI tools, catching their mistakes, and extending what they build beyond the first working version.
Fifty concepts. Organized into eight categories. Explained at the depth you need to actually use them — not deeper, not shallower.
What it is: JSON (JavaScript Object Notation) is a text format for structured data. It looks like this:
{
"user": "Yash",
"plan": "pro",
"active": true,
"tags": ["ai", "builder"],
"usage": { "tokens": 142000, "limit": 500000 }
}
Five data types: strings (quoted text), numbers, booleans (true/false), arrays (square brackets, ordered), objects (curly braces, key-value pairs). That is the entire format.
Why it matters: Every API call your AI product makes sends and receives JSON. Every database driver serializes to JSON. Every error message your AI tool generates arrives as JSON. When you get a "SyntaxError: Unexpected token" it means the JSON is malformed — a missing comma, an extra bracket, an unquoted key.
AI maker understanding: You can read a JSON payload in the browser's Network tab, identify the field causing a bug, and tell your AI tool exactly what is wrong rather than pasting the entire error and hoping.
What it is: An API is a defined interface through which one piece of software talks to another. When your app calls the Stripe API to charge a card, the weather API to get a forecast, or the OpenAI API to call a model — those are all API calls.
An API has three parts: the endpoint (the URL you call), the request (what you send), and the response (what you get back). The API documentation tells you the shape of the request and response for each operation.
Why it matters: Your entire AI product is probably a thin orchestration layer over several APIs — an LLM API, a database API, a payments API, an auth API. Every one of those APIs has rate limits, authentication requirements, and specific error formats. When any of them fails, you get an error in that API's format.
AI maker understanding: When an API call fails, you look up the status code and error message in that API's documentation before asking your AI tool to fix it. Most API errors are documented. The AI tool often does not know which version of an API you are calling.
What it is: REST (Representational State Transfer) is the dominant style for web APIs. A REST API structures its endpoints as resources (things) and uses HTTP methods to express actions on those resources:
GET /users → list all users
GET /users/42 → get user 42
POST /users → create a new user
PUT /users/42 → replace user 42
PATCH /users/42 → update specific fields of user 42
DELETE /users/42 → delete user 42
REST APIs are stateless: every request contains everything needed to fulfill it. The server does not remember your previous request.
Why it matters: Nearly every API you call — Stripe, GitHub, Anthropic, Vercel, Supabase, Resend — is a REST API. Understanding the resource model helps you understand what endpoint to call, what data to send, and why a 404 on /users/42 means "no user with ID 42" rather than "the /users endpoint doesn't exist."
What it is: GraphQL is an alternative to REST where you send a query describing exactly the data you want, and the server returns exactly that — no more, no less.
query {
user(id: "42") {
name
email
subscriptions {
plan
status
}
}
}
One endpoint (/graphql), infinite shapes of data. You decide the response structure, not the API designer.
Why it matters: GitHub's API is GraphQL. Shopify's is GraphQL. Contentful is GraphQL. If your AI tool generates code to fetch GitHub data and uses REST when it should use GraphQL (or vice versa), every request will fail. Knowing which style an API uses is step one.
Key difference from REST: In REST, the server decides the response shape. In GraphQL, you decide. In REST, you might call five endpoints to assemble one screen. In GraphQL, you call one endpoint with a shaped query.
What it is: A webhook is an API call that goes in the reverse direction — instead of your app calling the external service, the external service calls your app when something happens.
When a Stripe payment succeeds: Stripe calls POST https://yourapp.com/webhooks/stripe with the payment details. When a GitHub push happens: GitHub calls POST https://yourapp.com/webhooks/github with the commit data. Your app receives the call and reacts.
Why it matters: Real-time updates in your AI product almost always come through webhooks. Email sending (Resend, SendGrid), payments (Stripe), authentication events (Auth0, Clerk), deployment completions (Vercel) — all webhook-driven. If your webhook handler fails, events are silently dropped. There is no polling fallback.
The AI maker trap: Your AI tool can generate a webhook handler. It cannot register the webhook URL in the external service's dashboard. That step requires you. If you skip it, your handler will never receive anything and no error will tell you why.
What it is: HTTP is a request-response protocol: you send a request, you get a response, the connection closes. WebSockets are a persistent two-way connection: both sides can send messages at any time without making a new request.
Real-time chat, live collaborative editing, live price feeds, agent streaming output — all WebSocket use cases.
Why it matters: When Claude Code or Cursor streams its response word by word, that is a WebSocket (or SSE — see below) connection. When your AI product needs to push live updates to users without them refreshing, you need WebSockets or SSE.
SSE (Server-Sent Events): A simpler alternative to WebSockets for one-directional streaming (server → client). Most LLM APIs stream their output via SSE. WebSockets are bidirectional (both sides can send). If you are building a chat interface, SSE is often enough and simpler to implement.
What it is: CSV (Comma-Separated Values) is the simplest structured data format — columns separated by commas, rows separated by newlines:
name,email,plan
Yash,[email protected],pro
Alex,[email protected],free
Other common formats: TSV (tab-separated), JSONL (one JSON object per line — used for LLM training data and structured logs), Parquet (binary columnar format for analytics).
Why it matters: If your AI product handles user data exports, imports, analytics, or fine-tuning datasets, you will encounter CSV and JSONL constantly. The AI tool will generate parsers for them. Knowing the format helps you verify the parser is correct and debug encoding issues (the bane of CSV: commas inside fields, quoted strings, Unicode).
What it is: JSON, CSV, and text APIs transfer text. Images, audio, PDFs, and other binary files cannot be embedded in text directly. Base64 is an encoding that converts binary data into a string of ASCII characters — making it safe to embed in JSON, URLs, or email bodies.
A 100KB image becomes a ~133KB Base64 string (25% overhead from encoding).
Why it matters: Multimodal AI APIs (sending images to vision models) pass images as Base64 strings in the JSON payload. File uploads to S3 or Supabase Storage involve binary streams. When your AI product's image upload breaks, the failure is almost always at the boundary between binary data and the text-based transport layer.
What it is: HTTP defines a set of methods (also called verbs) that describe the intended action of a request:
| Method | Intent | Body? | Safe? | Idempotent? |
|---|---|---|---|---|
| GET | Retrieve data | No | Yes | Yes |
| POST | Create / submit | Yes | No | No |
| PUT | Replace entirely | Yes | No | Yes |
| PATCH | Update partially | Yes | No | Sometimes |
| DELETE | Remove | Sometimes | No | Yes |
| OPTIONS | Describe what's allowed | No | Yes | Yes |
Safe means the request has no side effects (safe to retry). Idempotent means calling it multiple times produces the same result as calling it once.
Why it matters: Sending a POST twice creates two records. Sending a PUT twice is fine. When your AI tool chooses the wrong method — using POST where PUT is correct — you get duplicate records in production. When a CORS preflight (OPTIONS) fails, your POST never fires and you get a mysterious network error.
What it is: Every HTTP response has a three-digit status code communicating the outcome:
2xx — Success:
200 OK — standard success201 Created — resource created successfully204 No Content — success, nothing to return (common on DELETE)3xx — Redirect:
301 Moved Permanently — URL has changed forever302 Found — temporary redirect304 Not Modified — use your cached version4xx — Client Error (you sent something wrong):
400 Bad Request — malformed request, missing fields, invalid data401 Unauthorized — not authenticated (no token, expired token)403 Forbidden — authenticated but not allowed (wrong permissions)404 Not Found — resource does not exist at this URL409 Conflict — state conflict (e.g., duplicate unique field)422 Unprocessable Entity — syntactically valid but semantically wrong429 Too Many Requests — rate limited5xx — Server Error (something on their end broke):
500 Internal Server Error — unhandled exception on the server502 Bad Gateway — upstream server is down503 Service Unavailable — server is overloaded or in maintenance504 Gateway Timeout — upstream took too longWhy it matters: A 401 and a 403 look similar but require completely different fixes. A 401 means add or refresh your auth token. A 403 means your auth is fine but you lack permission — a different problem entirely. Your AI tool will often conflate the two.
What it is: Headers are key-value metadata attached to every HTTP request and response. They carry authentication, content type, cache instructions, CORS permissions, and more.
Common request headers:
Authorization: Bearer eyJhbGciOiJI... ← auth token
Content-Type: application/json ← format of body you're sending
Accept: application/json ← format you want back
X-API-Key: sk-1234 ← API key auth
Common response headers:
Content-Type: application/json ← format of the response body
Cache-Control: max-age=3600 ← cache for 1 hour
Access-Control-Allow-Origin: * ← CORS permission
X-RateLimit-Remaining: 47 ← how many requests you have left
Why it matters: Most authentication problems are header problems. Most CORS errors are missing response headers. Rate limit information is in headers. When your AI tool generates a fetch call that works locally but fails in production, check the headers first.
What it is: A URL has several parts:
https://api.example.com/users?page=2&sort=name&filter=active#section
│ │ │ │ │
scheme host path query parameters fragment
Query parameters (after ?, separated by &) are key-value pairs that filter, paginate, or configure the response. They are part of the URL — bookmarkable, shareable, cacheable.
The path identifies the resource. The query parameters modify how you want it. The fragment (#) is client-only — it is never sent to the server.
Why it matters: Pagination, filtering, and sorting in your AI product almost always go in query parameters. When your AI tool builds a paginated list and page 2 returns the same results as page 1, it is usually because the query parameter is being ignored or incorrectly encoded.
What it is: HTTP transfers data in plain text. Anyone on the same network can read it. HTTPS (HTTP Secure) wraps HTTP in TLS (Transport Layer Security), encrypting all traffic between client and server.
TLS provides:
Why it matters: If your AI product runs over plain HTTP in production, auth tokens, API keys, and user data are transmitted in plaintext. Any network observer can steal them. Vercel, Netlify, and most modern hosts enforce HTTPS automatically. But if you run your own server or use an internal tool, this is not automatic.
The padlock and the certificate: HTTPS does not mean the site is safe. It means the connection is encrypted. A phishing site can have HTTPS. The certificate only proves you are talking to the server you think you are — not that that server is trustworthy.
What it is: Browsers enforce a Same-Origin Policy — by default, a page at https://yourapp.com cannot make API calls to https://api.yourapp.com (different subdomain) or https://stripe.com (different domain). CORS is the mechanism that lets servers explicitly allow cross-origin requests.
The server responds with headers like:
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
The preflight: For non-simple requests (POST with JSON, anything with Authorization header), the browser first sends an OPTIONS request to ask "can I make this call?" If the server does not respond with the right headers, the actual request is never sent.
Why it matters: "CORS error" in the browser console means the server is not sending the right CORS headers — not that your frontend code is wrong. The fix is always on the server side. When your AI tool's frontend calls your AI tool's backend and you get a CORS error in local development, it means the backend server is not configured to allow calls from localhost.
What it is: DNS translates human-readable domain names into IP addresses. When you visit explainx.ai, your computer asks a DNS server: "what IP address is explainx.ai?" The DNS server returns 76.76.21.21 (or whatever). Your computer connects to that IP.
DNS record types:
A — maps a domain to an IPv4 addressAAAA — maps a domain to an IPv6 addressCNAME — maps a domain to another domain (alias)MX — specifies email servers for a domainTXT — arbitrary text, used for domain verificationNS — specifies the authoritative nameservers for a domainWhy it matters: When you connect your custom domain to Vercel, you set a CNAME record pointing yourapp.com to cname.vercel-dns.com. When you add Google Workspace, you add MX records. When you verify domain ownership for any service, you add a TXT record. DNS changes propagate slowly (minutes to 48 hours) — this is why "just wait for DNS to propagate" is standard advice.
TTL: Every DNS record has a Time To Live — how long DNS resolvers cache it before asking again. A TTL of 3600 means changes take up to an hour to propagate everywhere.
What it is: A server at IP address 192.168.1.1 runs many services simultaneously. Ports are numbered (0–65535) channels that route network traffic to the right service.
Standard ports:
80 — HTTP443 — HTTPS5432 — PostgreSQL6379 — Redis3000 — Node.js dev server (convention)8080 — alternative HTTP dev server3306 — MySQLWhen you visit http://localhost:3000, you are connecting to port 3000 on your local machine. When your database connection string says postgresql://localhost:5432/mydb, you are connecting to PostgreSQL on port 5432.
Why it matters: Port conflicts are common in development — two services trying to use port 3000. "Address already in use" errors mean something is already on that port. lsof -i :3000 (on Mac/Linux) shows you what is using a port.
What it is: In web applications, client is the browser (or mobile app) running on the user's device. Server is a computer running your application logic, accessible over the network.
This distinction matters most for:
Security: Code running on the client is visible to anyone who opens DevTools. Never put secrets (API keys, database credentials, private logic) in client-side code. Anyone can read it.
Trust: You cannot trust client-side input. A user can modify what the client sends. All validation must happen on the server.
Performance: Network round-trips between client and server take time. The goal is to minimize them without moving secret logic to the client.
In Next.js specifically: Server Components run on the server (can access databases directly, never exposed to the browser). Client Components run in the browser (can use state, event handlers, browser APIs). Your AI tool will sometimes generate Server Component code using useState (browser-only) or Client Component code accessing the database directly (a security hole). Knowing this distinction lets you catch these mistakes.
What it is: localhost is a hostname that always resolves to your own machine — specifically to the IP address 127.0.0.1 (IPv4) or ::1 (IPv6). These are loopback addresses — traffic never leaves your machine, it loops back to itself.
This is why "there's no place like 127.0.0.1" — localhost is home.
When you run a dev server on localhost:3000, it is only accessible from your machine. No one else on the network can reach it (without a tunnel tool like ngrok or Cloudflare Tunnel).
Why it matters: Webhook development is the main pain point. Stripe, GitHub, and other services need to call a URL to deliver webhook events. They cannot call http://localhost:3000/webhooks/stripe — they have no route to your machine. You need a tunnel (ngrok, Cloudflare Tunnel) that creates a public URL forwarding to your localhost server.
What it is: Environment variables are configuration values stored outside your code — in the shell environment, in a .env file, or in a hosting platform's settings — and injected into your application at runtime.
# .env file (NEVER commit this to git)
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
OPENAI_API_KEY=sk-proj-...
STRIPE_SECRET_KEY=sk_live_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_...
In Node.js, you access them as process.env.OPENAI_API_KEY. In Python, as os.environ.get("OPENAI_API_KEY").
The NEXT_PUBLIC_ prefix: In Next.js, environment variables are server-only by default. Prefixing with NEXT_PUBLIC_ makes them available to client-side code too — but also makes them visible to every user who opens the page. Only use NEXT_PUBLIC_ for values that are genuinely public (publishable API keys, feature flags).
Why it matters: The .env file must be in your .gitignore. One accidental commit of your .env file to a public GitHub repo will result in your API keys being scraped and misused within minutes. Automated bots monitor every public commit for credential patterns.
What it is: A CDN is a network of servers distributed globally. When you deploy your frontend to Vercel or Cloudflare, static assets (images, JS, CSS) are copied to servers in dozens of cities. When a user in Tokyo requests your image, they get it from a Tokyo server — not from your origin server in Virginia. Latency: 5ms vs 200ms.
What lives on CDN vs origin:
Why it matters: When you deploy and your new code is live but users are seeing old UI, CDN caching is the likely culprit. The CDN served a cached version of your page. Vercel's CDN invalidates automatically on deployment. If you are using Cloudflare in front of Vercel, you may need to purge the Cloudflare cache separately.
Cache-Control headers tell CDNs and browsers how long to cache a response. max-age=0, no-store disables caching entirely. max-age=31536000, immutable caches for a year and tells the CDN it will never change.
What it is: A serverless function (also called a Lambda, Edge Function, or Cloud Function) is a single function deployed to the cloud without a persistent server. It starts on demand, runs, and stops. You pay per invocation and execution time, not for 24/7 server uptime.
In Next.js, every file in /app/api/ becomes a serverless function. In Vercel, they run on AWS Lambda or Vercel's own edge runtime.
Limitations:
Why it matters: If your AI product needs to run a long LLM inference (30+ seconds), stream it rather than waiting. If you need to run background jobs (send emails, process uploads), use a queue (Inngest, Trigger.dev) rather than trying to do it in the API route.
What it is: When you deploy to Vercel or Cloudflare, you choose where your code runs:
crypto, native modules). Runs in a container. Higher latency from user.Why it matters: Your AI tool may generate code using fs (filesystem), child_process, or a native npm package — none of which work on the edge runtime. If you see "the module 'fs' is not available on the Edge Runtime" — you either need to switch to the Node.js runtime or rewrite to avoid those APIs.
Geolocation-based features (show content based on user country) work best on the edge. Heavy computation (LLM inference, image processing) needs the Node.js runtime.
What it is: These two words are not interchangeable:
Why it matters: A 401 (Unauthorized) means authentication failed — the user is not logged in or their token is invalid. A 403 (Forbidden) means authorization failed — the user is logged in but does not have permission. These require completely different fixes. Conflating them is the most common auth bug.
What it is: A JWT (pronounced "jot") is a compact, self-contained token that encodes information and can be verified without calling a database. It has three parts, Base64-encoded and joined with dots:
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiI0MiIsInBsYW4iOiJwcm8iLCJleHAiOjE3NTAxNjAwMDB9.HMAC_SIGNATURE
Decoded: header.payload.signature
The payload contains claims (user ID, email, roles, expiration time). The signature (using a secret key) ensures the token has not been tampered with. The server can verify a JWT without a database lookup — it just checks the signature.
Why it matters: When your auth token expires and your user gets logged out unexpectedly, it is because the JWT's exp (expiration) claim was reached. The refresh token flow (exchanging a long-lived refresh token for a new short-lived access token) is the standard fix. Your AI tool will generate this correctly most of the time, but the expiry window and refresh logic are where bugs live.
Never store JWTs in localStorage. They are vulnerable to XSS attacks. Use HttpOnly cookies — the browser sends them automatically but JavaScript cannot access them.
What it is: OAuth 2.0 is the protocol behind "Sign in with Google/GitHub/Apple" buttons. Instead of giving your app a user's password, the user logs in with the external service, which issues your app a token confirming the user's identity.
The flow:
codecode for an access tokenWhy it matters: The code in step 4 is single-use and expires in seconds. If your callback URL is wrong, the user gets a redirect to a page that does not exist. If your server takes too long to exchange the code, it expires and the login fails. When "sign in with Google" is broken, it is almost always a misconfigured redirect URI or an expired code.
What it is: An API key is a secret string that authenticates requests to an external service. When your app calls the Anthropic API, it sends Authorization: Bearer sk-ant-api03-... in the header. Anthropic's server checks that key against its database, identifies your account, and either allows or rejects the request.
Types:
sk_ in Stripe, sk-ant- in Anthropic.pk_ in Stripe.Why it matters: API key exposure is the most common security incident in AI products built fast. One console.log(process.env.ANTHROPIC_API_KEY) in a Client Component ships your secret to every user's browser. Rotate keys immediately if you suspect exposure. Set spending limits on all API accounts — a leaked key with no limit can run up thousands of dollars in minutes.
What it is: HTTP is stateless — every request is independent. Sessions are how servers maintain state across requests (remember that you are logged in).
A cookie is a small piece of data the server sends in a Set-Cookie response header, and the browser automatically includes in every subsequent request to that domain.
Session flow:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=StrictCookie: session=abc123 on every subsequent requestabc123 in the session store to identify the userCookie flags that matter:
HttpOnly — JavaScript cannot read this cookie (blocks XSS)Secure — only sent over HTTPS (blocks network interception)SameSite=Strict — not sent on cross-site requests (blocks CSRF)What it is: Rate limiting restricts how many requests a client can make in a time window. "429 Too Many Requests" is the rate limit status code. Most APIs have rate limits expressed as X requests per Y seconds/minutes/hours.
Common rate limit headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1750160000
Retry-After: 30
Strategies:
Why it matters: If you build a feature that calls an LLM API in a loop without rate limit handling, one bug sends thousands of API calls and triggers your rate limit — or runs up a large bill. Implement exponential backoff: wait 1 second after the first 429, 2 seconds after the second, 4 after the third, and so on.
What it is: SQL injection is an attack where user input is interpreted as SQL code. If your query is:
const query = `SELECT * FROM users WHERE email = '${userInput}'`;
And the user enters ' OR '1'='1, the query becomes:
SELECT * FROM users WHERE email = '' OR '1'='1'
Which returns every user in the database. With a destructive payload, the attacker can delete tables.
The fix: parameterized queries. Never concatenate user input into SQL strings.
// Safe — user input is a parameter, not code
const result = await db.query('SELECT * FROM users WHERE email = $1', [userInput]);
Why it matters: Your AI tool will sometimes generate string-concatenated SQL queries, especially when writing raw SQL rather than using an ORM. Any user input that touches a database query must go through parameterized queries or an ORM that handles this automatically (Prisma, Drizzle, SQLAlchemy).
What it is: The two major database paradigms:
SQL (relational databases): Data lives in tables with defined columns and types. Rows in different tables relate through foreign keys. Queries use SQL. Examples: PostgreSQL, MySQL, SQLite. Best for: structured data with defined relationships, financial records, user data with complex queries.
NoSQL: A catch-all for non-relational databases. Key-value stores (Redis), document stores (MongoDB — JSON documents), graph databases (Neo4j), wide-column stores (Cassandra). Best for: unstructured data, high-volume writes, flexible schemas, caching.
Why it matters: Most AI applications use both. PostgreSQL with pgvector for persistent user data and vector embeddings. Redis for caching and rate limit counters. Knowing which database type fits which problem prevents the classic mistake of trying to run complex relational queries against a document store or storing billions of events in a row-based relational database.
What it is: A primary key uniquely identifies a row in a database table. Every row must have one. Two common approaches:
Auto-incrementing integers: id = 1, 2, 3, 4... Simple, compact, but exposes information (anyone can guess the next ID — if your order is #342, the competitor just placed order #341).
UUIDs (Universally Unique Identifiers): id = "f47ac10b-58cc-4372-a567-0e02b2c3d479" — a random 128-bit value. Not guessable. Can be generated client-side without a database round-trip. Larger storage size.
ULIDs and NanoIDs: Alternatives to UUID with timestamp-ordering (ULID) or shorter strings (NanoID — 21 characters). Many modern projects use these.
Why it matters: If your AI product exposes resource IDs in URLs (/orders/342), sequential integers are a security issue — a user can enumerate all orders. UUIDs in URLs prevent this. Your AI tool will often generate integer primary keys by default; explicitly ask for UUIDs in your schema.
What it is: A foreign key is a column in one table that references the primary key of another table, creating a relationship between them.
CREATE TABLE users (id UUID PRIMARY KEY, email TEXT);
CREATE TABLE posts (
id UUID PRIMARY KEY,
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
content TEXT
);
posts.user_id references users.id. This relationship lets you join tables:
SELECT posts.content, users.email FROM posts JOIN users ON posts.user_id = users.id;
Cascade behavior: ON DELETE CASCADE means if the user is deleted, all their posts are deleted automatically. ON DELETE RESTRICT prevents deletion if related records exist. ON DELETE SET NULL sets the foreign key to null.
Why it matters: If you delete a user without CASCADE set up, and the app tries to display their orphaned posts, it crashes. Choosing the right cascade behavior is an architectural decision your AI tool will make for you — often incorrectly for your specific use case.
What it is: An index is a data structure that speeds up queries on specific columns. Without an index, a query like SELECT * FROM users WHERE email = '[email protected]' scans every row in the table (O(n)). With an index on email, it is a binary tree lookup (O(log n)).
The cost: indexes take disk space and slow down writes (every INSERT/UPDATE must update the index too). You trade write performance for read performance.
What to index:
WHERE email = ?)ORDER BY created_at)Why it matters: A table with 100 rows is fast without indexes. The same table with 10 million rows, queried without an index, will time out in production. Your AI tool will rarely add indexes to generated schemas. When your query goes from instant in development to 30 seconds in production, a missing index is the most likely cause.
What it is: A migration is a script that describes a change to the database schema — adding a table, adding a column, removing a column, changing a data type. Migrations run in sequence and are version-controlled alongside your code.
-- 0003_add_subscription_tier.sql
ALTER TABLE users ADD COLUMN subscription_tier TEXT DEFAULT 'free';
CREATE INDEX idx_users_subscription_tier ON users(subscription_tier);
Why migrations matter: When your code expects a column that does not exist in production because the migration was not run, every API call that touches that table throws a 500. When you roll back code without rolling back the migration, columns the old code does not know about cause confusion. Migrations make schema changes reproducible and reversible.
With Prisma: prisma migrate dev generates and applies migrations from your schema changes. prisma migrate deploy runs pending migrations in production. Never edit a migration file after it has been applied.
What it is: An ORM maps database tables to code objects, letting you write queries in your programming language instead of SQL.
// Prisma ORM — TypeScript
const user = await prisma.user.findUnique({
where: { email: '[email protected]' },
include: { posts: true },
});
vs raw SQL:
SELECT users.*, posts.* FROM users
LEFT JOIN posts ON posts.user_id = users.id
WHERE users.email = '[email protected]';
Common ORMs: Prisma (TypeScript), Drizzle (TypeScript, SQL-first), SQLAlchemy (Python), ActiveRecord (Ruby).
The tradeoff: ORMs make common queries fast to write and type-safe. They make complex queries harder — sometimes generating inefficient SQL. The N+1 query problem (fetching a list of users, then querying for each user's posts individually) is the classic ORM trap. Your AI tool will sometimes generate N+1 queries; recognize them by seeing hundreds of identical queries in your logs.
What it is: Opening a database connection is expensive — it involves network handshakes, authentication, and resource allocation. A connection pool maintains a set of pre-opened connections and reuses them across requests.
Without pooling: every request opens a connection, uses it, closes it. With a pool: connections are checked out, used, and returned.
Why it matters in serverless: Each serverless function invocation may try to open its own database connection. With thousands of concurrent invocations, you exhaust the database's connection limit (PostgreSQL default: 100). PgBouncer and Supabase Pooler solve this for PostgreSQL. Neon and PlanetScale have built-in connection pooling. Your AI tool may generate a database client that opens a new connection per request — correct for a long-running server, fatal for serverless.
What it is: JavaScript is single-threaded. If a database query takes 200ms and you block the thread waiting, nothing else can happen for 200ms. Promises are JavaScript's way of representing a future value. Async/await is syntax sugar over Promises.
// This blocks — bad
const user = database.query('SELECT * FROM users WHERE id = 1'); // 200ms wait
// This does not block
const user = await database.query('SELECT * FROM users WHERE id = 1');
// While waiting, other requests can be handled
await can only be used inside an async function. A function that uses await returns a Promise.
The mistake: Forgetting await:
const user = prisma.user.findUnique({ where: { id: '42' } });
// user is a Promise object, not a user record
console.log(user.email); // undefined — the query hasn't run yet
This is one of the most common bugs in AI-generated code. If you see undefined where you expect a database value, check for a missing await.
What it is: When code throws an error, execution stops and the error propagates up the call stack until something catches it. In async code, unhandled Promise rejections crash your server or silently fail in the browser.
try {
const user = await prisma.user.findUnique({ where: { id } });
if (!user) throw new Error('User not found');
return user;
} catch (error) {
console.error('Failed to fetch user:', error);
throw error; // re-throw so the caller knows it failed
}
The finally block: Runs regardless of success or failure. Used for cleanup (closing connections, releasing locks).
Why it matters: Without error handling, an unhandled exception in an API route returns a 500 with no useful message. With error handling, you can return a 404 when a record is not found, a 400 when input is invalid, and a 500 only when something truly unexpected happens. Your AI tool will often generate happy-path code without error handling; add it explicitly.
What it is: TypeScript adds static types to JavaScript. Types describe the shape of your data and are checked at compile time — catching bugs before the code runs.
type User = {
id: string;
email: string;
plan: 'free' | 'pro' | 'enterprise';
createdAt: Date;
};
function getUserEmail(user: User): string {
return user.email; // TypeScript knows this exists and is a string
}
Why it matters for AI products: Your AI tool generates TypeScript. The types it generates reflect its assumptions about your data. When the AI generates a type that does not match your actual database schema, TypeScript will flag it — before you ship. Treating TypeScript errors as warnings and using any everywhere defeats the purpose.
The as escape hatch: user as unknown as AdminUser tells TypeScript "trust me, I know better." Use it sparingly. Every as is a place where runtime errors can hide from the type system.
What it is: JavaScript has two "nothing" values:
null — explicitly set to nothing (const user = null means "intentionally empty")undefined — not yet set (const user; — no value assigned)In TypeScript, ? marks a field as optional (can be undefined):
type User = {
email: string;
phone?: string; // might not exist
};
Optional chaining (?.): Safely access nested properties that might be null/undefined:
const city = user?.address?.city; // returns undefined instead of throwing
Nullish coalescing (??): Provide a default:
const plan = user?.plan ?? 'free'; // 'free' if user.plan is null/undefined
Why it matters: "Cannot read properties of undefined (reading 'email')" is the most common JavaScript runtime error. It means you accessed a property on something that was null or undefined. In AI-generated code, this usually means an API response was assumed to have a field that sometimes does not exist, or an async operation was not awaited.
What it is: In a frontend application, state is data that can change and, when changed, causes the UI to re-render. In React, useState manages local component state.
const [count, setCount] = useState(0);
// count is the current value
// setCount is the function to update it
A side effect is anything that interacts with the world outside the component — fetching data, updating the DOM directly, setting up subscriptions. useEffect in React runs side effects after render.
useEffect(() => {
fetch('/api/user').then(r => r.json()).then(setUser);
}, []); // empty array = run once on mount
Why it matters: Infinite render loops (the most common React bug) happen when a useEffect updates state without proper dependency management. If your AI product's dashboard refreshes infinitely or a component keeps re-fetching data in a loop, the useEffect dependency array is wrong.
What it is: Caching stores the result of an expensive operation so subsequent requests can use the stored result instead of repeating the operation.
Layers of caching in a web app:
Cache invalidation — deciding when a cached value is stale and should be refreshed — is famously hard. "There are only two hard things in computer science: cache invalidation and naming things." Stale cache is why your AI product sometimes shows users data that changed hours ago. Time-based invalidation (TTL — cache for N seconds, then expire) is the standard solution.
What it is: Software lives in multiple environments during its lifecycle:
Why it matters: An action that is safe in development (wiping the database to test a migration, logging all API payloads) is catastrophic in production. The most important practice: production database connections should never be in a .env file that anyone casually runs. Separate credentials for each environment.
The .env.local convention: Development secrets. .env.production for production secrets (managed via hosting platform environment variables, not committed to git).
What it is: CI/CD is the practice of automating the steps between writing code and shipping it to users.
CI (Continuous Integration): On every push, automated tests run. If tests fail, the code is not merged. This prevents broken code from reaching the main branch.
CD (Continuous Deployment): When code is merged, it automatically deploys to staging or production. Vercel does this by default — push to main, the site updates.
Why it matters: Without CI, one developer's untested code breaks production and the team spends Saturday debugging. Without CD, deployment is a manual, error-prone ceremony. Both are things your AI tool can help set up (GitHub Actions for CI, Vercel for CD) but you need to understand them to configure the right triggers and failure conditions.
What it is: Logs are timestamped records of events in your running application. Every console.log, console.error, and console.warn in Node.js goes to your application logs.
Log levels:
DEBUG — verbose, only in developmentINFO — normal events (user logged in, payment processed)WARN — unexpected but recoverable (retry after rate limit)ERROR — failures that need attention (database query failed)Why it matters: When your AI product has a bug in production that you cannot reproduce locally, logs are the only visibility you have. In Vercel: Functions → Logs. In Cloudflare: Workers → Logs. The pattern "I can't reproduce it locally" usually means an environment-specific condition (different env var, different data, race condition) that only logs can reveal.
Structured logging: Logging JSON objects rather than plain strings makes logs searchable: console.log({ event: 'payment_failed', userId, error: e.message }).
What it is: A Docker container packages your application with all its dependencies (runtime, libraries, configuration) into a portable unit that runs identically everywhere. "It works on my machine" becomes irrelevant — the container is the machine.
A Dockerfile describes how to build the image:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
Why it matters: If your AI product has a complex backend (custom Python inference, specific system library versions, GPU requirements), Docker is how you ensure it runs the same in development and production. Vercel and most PaaS platforms handle this for you. But when you need a custom runtime or a non-standard stack, you write a Dockerfile.
What it is: Large language models do not process text character by character or word by word. They process tokens — chunks of text that roughly correspond to common word fragments.
"vibecoding" → ["vibe", "coding"] — 2 tokens
"JSON" → ["JSON"] — 1 token
"antidisestablishmentarianism" → ["anti", "dis", "estab", "lish", "ment", "arian", "ism"] — 7 tokens
A rough rule: 1 token ≈ ¾ of an English word. 100 tokens ≈ 75 words.
Why pricing is in tokens: Anthropic charges $3/M input tokens, $15/M output tokens (Sonnet 4.6). A typical conversation with 10 exchanges might use 2,000 tokens. A Claude Code session on a large codebase might use 200,000 tokens. A loop engineering session overnight might use 5,000,000 tokens.
Why it matters: Knowing roughly how many tokens your AI workflow uses helps you estimate cost, stay within context windows, and understand why long conversations degrade in quality (the model has less capacity to "remember" early context when the context window fills).
What it is: The context window is the maximum number of tokens an LLM can process in a single call — the total of what you send it (system prompt + conversation history + retrieved documents) plus what it generates in response.
Current context windows:
What goes in the context window:
Why it matters: When a Claude Code session gets very long and the model starts "forgetting" things you told it earlier or making contradictory decisions, it is because the context window is filling up and earlier content is being compressed or dropped. /compact in Claude Code summarizes and compresses the context. Understanding context limits helps you design your AI prompts more efficiently.
What it is: An embedding is a numerical representation of text (or an image, or audio) as a vector — a list of hundreds or thousands of numbers. Texts with similar meanings have vectors that are close together in this high-dimensional space.
"dog" → [0.23, -0.17, 0.84, ..., 0.02] (1536 numbers)
"puppy" → [0.25, -0.15, 0.81, ..., 0.04] ← close to "dog"
"database" → [0.61, 0.43, -0.22, ..., 0.77] ← far from "dog"
Vector search: Store embeddings in a vector database (pgvector in PostgreSQL, Pinecone, Weaviate, Qdrant). At query time, embed the user's question and find the stored vectors closest to it. Return the documents those vectors came from.
Why it matters: Semantic search (find documents similar in meaning, not just containing the same keywords), recommendation systems, duplicate detection, and RAG all run on embeddings. If your AI product has a "search your data" feature, it probably uses vector search.
What it is: A large language model has a knowledge cutoff — it does not know about events after its training data was collected, and it does not know about your private data. RAG is the technique of retrieving relevant documents at query time and including them in the context window, giving the model access to current, private, or domain-specific knowledge.
The RAG pipeline:
Why it matters: Every AI product with a "chat with your data" feature uses RAG. Every AI assistant that needs to know about recent events uses RAG. The quality of your AI product depends critically on:
RAG failures look like confident, wrong answers — the model retrieved a slightly irrelevant document and hallucinated the rest. When your AI product gives plausible but incorrect answers, retrieval quality is the first thing to debug.
A user opens your AI product. Here is what happens — and which of these 50 concepts is at work:
All 50 concepts. One user action. Now you can read the system you are building.
If you stop here and only learn three things:
HTTP status codes (concept 10) — read the three-digit number before reading the error message. It tells you what category of problem you have before any debugging.
Environment variables (concept 19) — your secrets live here, not in your code. One .env committed to a public repo ends the project. Check your .gitignore before the first git push.
Async/await and missing awaits (concept 37) — if a database value is undefined when it should not be, there is probably a missing await on the query. This is the single most common bug in AI-generated JavaScript/TypeScript.
Everything else compounds. But start here.