--- tags: - woql - tutorial - beginner title: Explore a Real Dataset — Star Wars Tutorial nextjs: metadata: title: Explore a Real Dataset — Star Wars Tutorial description: Clone, query, branch, and diff a Star Wars database in 15 minutes. Learn TerminusDB's git-for-data workflow on real data. keywords: terminusdb tutorial, clone database, star wars dataset, branch diff merge, git for data example openGraph: images: https://assets.terminusdb.com/docs/technical-documentation-terminuscms-og.png alternates: canonical: https://terminusdb.org/docs/explore-a-real-dataset/ media: [] lastUpdated: "2026-04-30" --- Clone a pre-populated Star Wars database to your local TerminusDB instance and explore it with queries, branches, and diffs — in 15 minutes. {% callout title="Prerequisites" %} - **TerminusDB running on localhost:6363.** Verify: `curl -s -u admin:root http://localhost:6363/api/info` should return JSON containing `"authority": "admin"`. If you get "connection refused", [start TerminusDB first](/docs/get-started/#step-1). - **Completed the [First 10 Minutes quickstart](/docs/get-started/)** — you should be comfortable with branches and diffs. This tutorial builds on those concepts with a richer dataset. {% /callout %} ## What you will build You will clone a complete Star Wars database (characters, films, planets, starships, species) from a public server, run queries that traverse document relationships, create a speculative branch ("what if Anakin turned to the Dark Side?"), and see a field-level structural diff of your changes. ## Step 1 — Clone the Star Wars database Pull the entire Star Wars dataset from the public templates server to your local instance in one command: {% http-example method="POST" path="/api/clone/admin/star-wars" headers='{"Authorization-Remote":"Basic cHVibGljOnB1YmxpYw=="}' fixture="star-wars" %} {"remote_url": "https://data.terminusdb.org/public/star-wars", "label": "Star Wars", "comment": "Cloned from public templates server"} {% http-expected %} {"@type":"api:CloneResponse","api:status":"api:success"} {% /http-expected %} {% /http-example %} You just pulled a complete Star Wars database — characters, films, planets, starships — from a public TerminusDB server to your local instance. No account needed, no sign-up, no credentials. The data is now yours to query, branch, and modify. ## Step 2 — Explore what you have List the document types defined in the schema: {% http-example method="GET" path="/api/document/admin/star-wars/local/branch/main?graph_type=schema&as_list=true" /%} You will see types including `People`, `Film`, `Planet`, `Starship`, `Vehicle`, and `Species`. List the characters (called `People` in the SWAPI schema): {% http-example method="GET" path="/api/document/admin/star-wars/local/branch/main?type=People&count=5" /%} You now have over 80 characters plus films, planets, ships, and species. All of Star Wars, versioned and queryable. Let's ask it some questions. ## Step 3 — Query the data Which characters appear in "A New Hope" (Episode IV)? In a relational database, you would write a JOIN across a junction table: `SELECT c.name FROM characters c JOIN film_characters fc ON ... JOIN films f ON ... WHERE f.title = 'A New Hope'`. In TerminusDB, documents link directly to other documents — a Film has a `character` property that points to People documents. You traverse the link, not a junction table: {% http-example method="POST" path="/api/woql/admin/star-wars/local/branch/main" %} {"query": {"@type": "And", "and": [{"@type": "Triple", "subject": {"@type": "NodeValue", "variable": "Film"}, "predicate": {"@type": "NodeValue", "node": "label"}, "object": {"@type": "DataValue", "data": "A New Hope"}}, {"@type": "Triple", "subject": {"@type": "NodeValue", "variable": "Film"}, "predicate": {"@type": "NodeValue", "node": "character"}, "object": {"@type": "NodeValue", "variable": "Character"}}, {"@type": "Triple", "subject": {"@type": "NodeValue", "variable": "Character"}, "predicate": {"@type": "NodeValue", "node": "label"}, "object": {"@type": "DataValue", "variable": "CharacterName"}}]}} {% /http-example %} Expected output includes character names: Luke Skywalker, Leia Organa, Han Solo, Obi-Wan Kenobi, Chewbacca, R2-D2, C-3PO, and more (18 characters total). Notice what happened: the query says "find a Film with this label, follow its `character` link to People documents, then get their `label`." Three hops through the graph — Film → character → People → label. No junction tables, no foreign key declarations, no JOINs. The relationships are part of the data structure itself, and traversing them is how you query. **TypeScript equivalent:** ```typescript import TerminusClient from "@terminusdb/terminusdb-client"; const client = new TerminusClient.WOQLClient("http://localhost:6363", { user: "admin", organization: "admin", key: "root", }); client.db("star-wars"); const WOQL = TerminusClient.WOQL; const query = WOQL.and( WOQL.triple("v:Film", "label", WOQL.string("A New Hope")), WOQL.triple("v:Film", "character", "v:Character"), WOQL.triple("v:Character", "label", "v:CharacterName") ); const result = await client.query(query); console.log(result.bindings.map((b) => b["CharacterName"]["@value"])); ``` ## Step 4 — Branch and modify What if Darth Vader had never turned to the dark side? Let's update his record on a branch and see what TerminusDB tracks. Create a branch called `what-if`: {% http-example method="POST" path="/api/branch/admin/star-wars/local/branch/what-if" %} {"origin": "admin/star-wars/local/branch/main"} {% http-expected %} {"@type":"api:BranchResponse","api:status":"api:success"} {% /http-expected %} {% /http-example %} Now modify Anakin Skywalker's record on the branch — rewriting him as if he fell to the Dark Side. First, fetch his full document: ```bash curl -s -u admin:root \ "http://localhost:6363/api/document/admin/star-wars/local/branch/what-if?id=terminusdb:///star-wars/People/11" > anakin.json ``` Edit `anakin.json` — change four fields to tell the Dark Side story: - `"label"`: `"Anakin Skywalker"` → `"Darth Vader"` - `"eye_color"`: `"blue"` → `"yellow"` - `"mass"`: `"84"` → `"120"` - `"skin_colors"`: `"fair"` → `"pale"` Then PUT the modified document back: ```bash curl -s -u admin:root -X PUT \ "http://localhost:6363/api/document/admin/star-wars/local/branch/what-if?author=admin&message=What+if+Anakin+turned+to+the+Dark+Side" \ -H "Content-Type: application/json" \ -d @anakin.json ``` **Expected response:** ```json ["terminusdb:///star-wars/People/11"] ``` You just rewrote history — on a branch. Main still has Anakin Skywalker (blue eyes, fair skin, mass 84). Your `what-if` branch has Darth Vader (yellow eyes, pale skin, mass 120). Notice the `@id` stays the same (`People/11`) — TerminusDB tracks object identity through changes, not content. Let's see exactly what changed. ## Step 5 — See what changed (the diff) This is the moment. In any other database, answering "what changed between these two versions?" means writing audit triggers, maintaining changelog tables, or exporting both states and diffing them externally. In TerminusDB, you ask the database directly: {% http-example method="POST" path="/api/diff/admin/star-wars" %} {"before_data_version": "main", "after_data_version": "what-if"} {% http-expected %} [{"@id": "People/11", "eye_color": {"@op": "SwapValue", "@before": "blue", "@after": "yellow"}, "label": {"@op": "SwapValue", "@before": "Anakin Skywalker", "@after": "Darth Vader"}, "mass": {"@op": "SwapValue", "@before": "84", "@after": "120"}, "skin_colors": {"@op": "SwapValue", "@before": "fair", "@after": "pale"}}] {% /http-expected %} {% /http-example %} This diff is **structural, not textual**. TerminusDB is not comparing strings line by line — it knows the document schema, understands which field changed, what the old value was, and what the new value is. Each change is a typed operation (`SwapValue`) that can be applied, reversed, or composed with other patches programmatically. You sent a full document replacement, but TerminusDB detected only the four fields that actually changed: `eye_color`, `label`, `mass`, and `skin_colors`. Compare this to the alternative: export both database states as JSON, run a generic diff tool, then parse the text output to figure out what actually changed. Or maintain an audit table with triggers that fire on every update. Or write custom comparison logic in your application. TerminusDB replaces all of that with one API call. The database *is* the version history — diffs are a native operation, not an afterthought bolted on top. --- ## What you just did In 15 minutes, you: 1. **Cloned** a complete Star Wars database from a public server — one command, no account 2. **Queried** relationships across documents (films to characters) using WOQL 3. **Branched** the database and made a speculative change ("what if Anakin turned to the Dark Side?") 4. **Diffed** the branch against main and saw field-level changes These four operations — clone, query, branch, diff — are the core of TerminusDB. Every operation you just ran works at any scale: 200 documents or 2 million. {% callout type="note" title="Prefer a business dataset?" %} Try [Explore an Ecommerce Dataset →](/docs/explore-ecommerce-dataset) — the same workflow with customers, orders, and products. {% /callout %} ## Next steps - [Merge your branch back to main](/docs/first-15-minutes/#step-7--merge-the-branch) — complete the git-for-data cycle - [Write your own schema](/docs/schema-reference-guide/) — add validation and type safety - [Time-travel to previous commits](/docs/time-travel-to-previous-commits/) — view any previous state of your database - [WOQL query language](/docs/woql-getting-started/) — learn the full query language - [Connect with TypeScript](/docs/connect-with-the-javascript-client/) — use the SDK in your application