GROQ in twenty minutes
There is no join, because there is no second table. A query says which documents and then what to return, and everything that looks like syntax is one of those two halves made more specific.
GROQ is a query language for documents, and the fastest way to learn it is to stop thinking in tables. There is no join, because there is no second table: there is a set of documents, and a way to walk from one to another.
A filter, and then a projection
A query has two halves. The first says which documents: `*[_type == "post"]`. The second says what comes back: `{ title, slug }`. Everything else — ordering, slicing, dereferencing — is one of those halves made more specific. Reading a query out loud as "the posts, newest ten, with the author's name" is not a mnemonic; it is the grammar.
The half people skip is the projection. `*[_type == "post"]` returns whole documents, which is fine against a dataset of forty and expensive against forty thousand. A projection is not an optimisation you add later; it is the second half of the sentence you were already writing.
A query that returns fields nobody reads pays for them twice — once over the wire, and once in the render.
Dereferencing is the whole trick
- `author->name` follows a reference and answers one field of it.
- `categories[]->title` does it for every item of a list.
- `^` is the parent, which is how a nested projection reaches back outwards.
- `$slug` is a parameter, so one query serves every page instead of one per page.
Once references and projections are both familiar, most of what you would have written a bespoke endpoint to do becomes a query string inside a component — which is also the version of it that caches.
*[_type == "post" && slug == $slug][0]{ title, excerpt, "author": author->name, "categories": categories[]->title }
Read that one back in English: the post whose slug is this, the first one, with its title and excerpt, its author's name, and the titles of its categories. Every operator in it is one of the ten this implementation supports.
Related posts
Your content model is the product decision
Every content system fails the same way: a field that grew a second meaning, and a template that reads it both ways. The model is the one thing you will still be living with in three years.
Draft, publish, and the two-row trick
Achar stores a draft as a second document whose id begins `drafts.` rather than a flag on one row. It looks like duplication, and it is the reason a headline can be rewritten for a week without touching what the site serves.
Portable Text, and why rich text should be data
Rich text stored as HTML is a string with structure in it that only a browser can read. Stored as an array of blocks it is data: queryable, transformable, and renderable by something that is not a browser.
