<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://www.stuffaboutcode.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.stuffaboutcode.com/" rel="alternate" type="text/html" /><updated>2026-08-10T17:14:15+01:00</updated><id>https://www.stuffaboutcode.com/feed.xml</id><title type="html">&lt;Stuff about=&quot;code&quot; /&gt;
</title><subtitle>Code, hardware and project notes by Martin O&apos;Hanlon.</subtitle><author><name>Martin O&apos;Hanlon</name></author><entry><title type="html">Bringing stuffaboutcode.com back to life</title><link href="https://www.stuffaboutcode.com/posts/bringing-stuffaboutcode-back-to-life/" rel="alternate" type="text/html" title="Bringing stuffaboutcode.com back to life" /><published>2026-08-02T11:30:00+01:00</published><updated>2026-08-02T11:30:00+01:00</updated><id>https://www.stuffaboutcode.com/posts/bringing-stuffaboutcode-back-to-life</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/bringing-stuffaboutcode-back-to-life/"><![CDATA[<p>This blog stopped in 2018. Not deliberately — I just drifted away from it.</p>

<p>Along the way I moved into a job where writing and communicating <em>is</em> the work, and it turns out that when you write for a living you have a finite amount of writing in you. Every post I might have put here went to the organisation paying me instead. That’s a reasonable trade while it lasts, but eight years of it left me with a blog full of Raspberry Pi and Minecraft projects and nothing after 2018.</p>

<p><img src="/assets/img/2026/08/old-stuffaboutcode-blogger.png" alt="The old stuffaboutcode.com, running on Blogger" /></p>

<p>That’s what it looked like. Blogger, a theme I’d picked in about 2012, and 136 posts slowly rotting — images hotlinked from other people’s servers that had since disappeared, <code class="language-plaintext highlighter-rouge">goo.gl</code> short links pointing at a service Google has since switched off, and code blocks that had lost their indentation somewhere in Blogger’s editor years ago.</p>

<h2 id="what-i-actually-wanted">What I actually wanted</h2>

<p>Two things.</p>

<p>Somewhere to put my own ideas again, obviously. But also somewhere I control, that I can <strong>cross-post to</strong>. A lot of what I write now lives on someone else’s platform, and platforms go away — I’ve just spent a fortnight proving that by chasing down dead links from 2012. If I publish something elsewhere I want a copy here with a canonical link pointing back to the original, so the search engines credit the right place and I still have it in ten years when the original has 404’d.</p>

<p>Longevity, basically. Plain Markdown files in a git repo will outlive any CMS I could pick.</p>

<h2 id="the-drudge-work">The drudge work</h2>

<p>Migrating 136 posts off Blogger is not interesting work. It is:</p>

<ul>
  <li>exporting from Google Takeout and working out that the format changed in 2018, so the standard <code class="language-plaintext highlighter-rouge">jekyll-import</code> tool doesn’t read it any more</li>
  <li>converting HTML-with-inline-styles into clean Markdown</li>
  <li>rescuing code blocks from <code class="language-plaintext highlighter-rouge">&lt;span style="font-family: Courier New"&gt;</code> soup, with the indentation intact, because these are Python posts and indentation is the program</li>
  <li>pulling 208 images off Google’s servers before they vanish too</li>
  <li>writing a redirect for every single old URL, because there are fourteen years of inbound links out there</li>
</ul>

<p>That’s a week of tedium, and it’s exactly the kind of thing a coding agent is good at. So I used one — Claude Code — and spent my time on the parts that actually needed me: what the thing should look like, what to keep, what to throw away, and whether the output was any good.</p>

<h2 id="keeping-myself-in-the-loop">Keeping myself in the loop</h2>

<p>The way I work with a coding agent is that I stay in the process. Not “generate a website” and hope — review every phase, and be the one who decides. A few examples from this migration, including the ones where I was the reason it worked.</p>

<p><strong>The brief was wrong and the agent said so.</strong> I’d written “203 posts” in the plan. The export had 136. Rather than quietly building 136 and moving on, it cross-checked the label counts against the design spec — raspberry-pi 95, python 77, minecraft 43 — found they matched the export exactly, and told me the 203 figure couldn’t be right. It was my mistake, from a stale note.</p>

<p><strong>I caught the thing the tests couldn’t.</strong> After the theme went in, 124 automated checks passed. I opened the page and it looked nothing like the design — completely unstyled. The stylesheet was being served as an HTML document, because Jekyll had wrapped it in a page layout. It returned HTTP 200 at the right URL with a plausible file size, so every automated check was happy. The checks were reading the source file, not the built one. It took me two seconds to see and it would have shipped otherwise.</p>

<p><strong>I vetoed a fix that was correct but wrong.</strong> Images broke on the staging URL, and the agent fixed it by rewriting every image reference in all 92 affected posts:</p>

<div class="language-markdown highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">![](</span><span class="sx">{{</span> <span class="nn">'/assets/img/2012/04/responsivedesign-mobile.png'</span> | relative_url }})
</code></pre></div></div>

<p>That works. It’s also a permanent tax on writing every future post, to solve a problem that only exists on a temporary URL. I said no, and it moved the fix into the build step instead. Posts stayed as plain Markdown.</p>

<p><strong>And the loop runs both ways.</strong> Three posts had genuinely broken indentation — not from the migration, broken in the original since 2013. The agent normalised what it could prove and refused those three, because Python itself couldn’t work out the nesting. I fixed them by hand. It re-checked, and found my fix had flattened two other blocks I hadn’t noticed. Three rounds before it was clean. I’d have shipped mine.</p>

<p>None of that is the agent being clever or me being clever. It’s just that the failure modes are different, so between us we caught things neither would alone.</p>

<h2 id="open-source">Open source</h2>

<p>The last benefit: the whole site is now public at <a href="https://github.com/martinohanlon/stuffaboutcode">github.com/martinohanlon/stuffaboutcode</a>.</p>

<p>Every post is a Markdown file. The theme is one stylesheet and about 200 lines of vanilla JavaScript — no framework, no npm. Push to the repo and GitHub Actions builds and deploys it. If you want to see how something here is done, you can just go and look, which feels right for a blog that has always been about showing people how things work.</p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="blogging" /><category term="ai" /><summary type="html"><![CDATA[This blog stopped in 2018. Not deliberately — I just drifted away from it.]]></summary></entry><entry><title type="html">RAG vs GraphRAG — Two Retrievers, One Question</title><link href="https://www.stuffaboutcode.com/posts/rag-vs-graphrag-two-retrievers-one-question/" rel="alternate" type="text/html" title="RAG vs GraphRAG — Two Retrievers, One Question" /><published>2026-07-22T01:00:30+01:00</published><updated>2026-07-22T01:00:30+01:00</updated><id>https://www.stuffaboutcode.com/posts/rag-vs-graphrag-two-retrievers-one-question</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/rag-vs-graphrag-two-retrievers-one-question/"><![CDATA[<p>Ask for “a highly rated action movie about travelling to other planets” and two systems can hand you two very different answers. One reads the plot of every film and returns whatever sounds closest. The other knows which films share a genre, who acted in them, and how thousands of people actually rated them.</p>

<p>Same question, same data, different retrieval. I built a small demo to make that difference impossible to miss, and it runs on exactly the concepts and dataset you work through in <a href="https://graphacademy.neo4j.com/courses/genai-fundamentals">Neo4j &amp; GenAI Fundamentals</a>.</p>

<p><img src="/assets/img/2026/07/rag-vs-graphrag-two-retrievers-one-question-1.png" alt="rag vs graphrag demo" /></p>

<h2 id="what-rag-and-graphrag-actually-do">What RAG and GraphRAG actually do</h2>

<p>Retrieval-augmented generation (RAG) gives a large language model the context it needs to answer a question. Instead of relying on what the model memorised during training, you retrieve relevant data and pass it in alongside the question.</p>

<p>The most common form is vector RAG. You embed your text as vectors, store them, and at query time find the chunks that are semantically closest to the question. It is good at “find me something that reads like this”.</p>

<p><img src="/assets/img/2026/07/rag-vs-graphrag-two-retrievers-one-question-2.png" alt="rag architecture" /></p>

<p>GraphRAG adds the missing dimension: relationships. A vector search can tell you a film’s plot is similar to your query, but it cannot tell you the film’s genre, its cast, or its average rating unless that text happens to sit in the same chunk. A graph can. GraphRAG takes the results of a vector search and traverses the graph to pull in connected facts, so the model answers with structure, not just similarity.</p>

<p><img src="/assets/img/2026/07/rag-vs-graphrag-two-retrievers-one-question-3.png" alt="graphrag architecture" /></p>

<h2 id="the-recommendations-dataset">The recommendations dataset</h2>

<p>The demo runs on Neo4j’s public dataset, the same one used throughout the course. It models movies, people connected to them, and a lot of ratings.</p>

<p><img src="/assets/img/2026/07/rag-vs-graphrag-two-retrievers-one-question-4.png" alt="movies graph data model" /></p>

<p>A <code class="language-plaintext highlighter-rouge">Movie</code> is <code class="language-plaintext highlighter-rouge">IN_GENRE</code> of one or more genres, a <code class="language-plaintext highlighter-rouge">Person</code> <code class="language-plaintext highlighter-rouge">ACTED_IN</code> or <code class="language-plaintext highlighter-rouge">DIRECTED</code> it, and a <code class="language-plaintext highlighter-rouge">USER</code> <code class="language-plaintext highlighter-rouge">RATED</code> it. Each movie also carries a plot summary, which is embedded as a 1536-dimension vector and stored in a vector index called <code class="language-plaintext highlighter-rouge">moviePlots</code>.</p>

<p>That single graph holds everything both retrievers need. Vector RAG uses only the plot embeddings. GraphRAG uses the embeddings and the relationships around them.</p>

<h2 id="seeing-the-difference">Seeing the difference</h2>

<p>The <a href="https://github.com/martinohanlon/rag-x-graphrag">RAG vs GraphRAG demo</a> is a terminal application that sends your question to two agents at once and shows their answers side by side.</p>

<ul>
  <li>The <strong>RAG</strong> agent uses a <code class="language-plaintext highlighter-rouge">VectorRetriever</code>. It finds the movies whose plot embeddings are nearest to your query and passes those plots to the LLM.</li>
  <li>The <strong>GraphRAG</strong> agent uses a <code class="language-plaintext highlighter-rouge">VectorCypherRetriever</code>. It starts from the same vector hits, then runs a Cypher query to enrich them with genres, cast, directors, and user ratings before the LLM ever sees them.</li>
</ul>

<figure class="embed embed--youtube">
  <iframe src="https://www.youtube-nocookie.com/embed/izlF3hulft8" title="YouTube video" loading="lazy" referrerpolicy="strict-origin-when-cross-origin" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</figure>

<p>Both retrievers are built with the <a href="https://neo4j.com/docs/neo4j-graphrag-python/current/">neo4j-graphrag</a> Python package, so the only meaningful difference between them is how much of the graph they retrieve. Ask for the highest-rated action film about other planets, and plain vector RAG returns something plot-similar. GraphRAG returns something plot-similar that is also an action film with a genuinely high average rating, because it retrieved the ratings and genres to reason over.</p>

<h2 id="run-it-yourself">Run it yourself</h2>

<p>The <a href="https://github.com/martinohanlon/rag-x-graphrag#readme">installation instructions are on GitHub</a>. The demo connects to the public dataset out of the box, so all you need is Python and an OpenAI API key to try it on your own questions.</p>

<h2 id="learn-to-build-it-yourself">Learn to build it yourself</h2>

<p>The concepts underneath the demo — embeddings, vector indexes, retrievers, and graph-enhanced retrieval, are what make it work, and they are exactly what <a href="https://graphacademy.neo4j.com/courses/genai-fundamentals/">Neo4j &amp; GenAI Fundamentals</a> teaches. You will build the same <code class="language-plaintext highlighter-rouge">VectorRetriever</code> and <code class="language-plaintext highlighter-rouge">VectorCypherRetriever</code> against this dataset, write the retrieval Cypher yourself, and see the context change as you do.</p>

<blockquote>
  <p><strong><a href="https://graphacademy.neo4j.com/courses/genai-fundamentals/">Neo4j &amp; GenerativeAI Fundamentals</a></strong> — Learn how Neo4j and GraphRAG can support your Generative AI projects</p>
</blockquote>

<p>If the side-by-side answers made you curious about why the graph wins, the course is where you find out how to build it.</p>

<hr />

<p><em>Originally published on <a href="https://graphacademy.neo4j.com/blog/rag-vs-graphrag-demo">Neo4j GraphAcademy</a> on 22 July 2026.</em></p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="graphrag" /><category term="neo4j" /><category term="ai" /><summary type="html"><![CDATA[Ask for “a highly rated action movie about travelling to other planets” and two systems can hand you two very different answers. One reads the plot of every film and returns whatever sounds closest. The other knows which films share a genre, who acted in them, and how thousands of people actually rated them.]]></summary></entry><entry><title type="html">A Real Neo4j Instance for Every Learner</title><link href="https://www.stuffaboutcode.com/posts/a-real-neo4j-instance-for-every-learner/" rel="alternate" type="text/html" title="A Real Neo4j Instance for Every Learner" /><published>2026-07-03T01:00:31+01:00</published><updated>2026-07-03T01:00:31+01:00</updated><id>https://www.stuffaboutcode.com/posts/a-real-neo4j-instance-for-every-learner</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/a-real-neo4j-instance-for-every-learner/"><![CDATA[<p>Reading about a graph database is not the same as using one. You can follow every explanation of a clause perfectly and still freeze the first time you face an empty query editor. When you start a hands-on course on GraphAcademy, you get a real Neo4j database of your own.</p>

<p><img src="/assets/img/2026/07/a-real-neo4j-instance-for-every-learner-1.png" alt="A GraphAcademy lesson, Reading Graphs, with the lesson text on the left and a query pane on the right running a Cypher MATCH against Toy Story, showing the returned graph and the node's properties" /></p>

<p>Here is how that works under the hood.</p>

<h2 id="a-database-created-just-for-you">A database created just for you</h2>

<p>When you enter a hands-on course, GraphAcademy provisions a Neo4j instance on <a href="https://console.neo4j.com?attribution=graphacademy">Aura</a>, Neo4j’s fully managed cloud service. It is created through the <a href="https://neo4j.com/docs/aura/platform/api/overview/">Aura management API</a>, and it belongs to you alone. It is your instance, with your own connection URI, username, and password, and you can connect to it from the browser, from Neo4j Browser, or from your own code.</p>

<p><img src="/assets/img/2026/07/a-real-neo4j-instance-for-every-learner-2.png" alt="A sequence diagram: the learner opens a hands-on lesson, GraphAcademy asks the Aura management API to create an instance with a usecase, region and size, Aura returns a connection URI, username and password, GraphAcademy stores an Instance node and seeds the data, and the learner gets a running database ready to use" /></p>

<p>You never see a billing page or a provisioning wizard. You open a lesson, and a running database is waiting for you.</p>

<h2 id="every-instance-starts-empty">Every instance starts empty</h2>

<p>A freshly provisioned Aura instance is blank. No nodes, no relationships, no schema. How it is populated depends entirely on what the course is teaching.</p>

<p>From that blank slate, data arrives in one of two ways.</p>

<h2 id="two-ways-the-data-arrives">Two ways the data arrives</h2>

<p><strong>Some courses prepare the data for each lesson.</strong> A lesson can carry a reset script that puts the database into the exact state it needs before you begin, so you always start from a known point regardless of what the previous lesson left behind. <a href="https://graphacademy.neo4j.com/courses/neo4j-fundamentals/">Neo4j Fundamentals</a> works this way: it starts you on a small movie graph, and its querying lessons read, match, and modify that graph, resetting to a clean baseline as you move between them.</p>

<p><strong>Other courses come pre-loaded with a use-case dataset.</strong> When a course needs a realistic graph to explore, we seed one for you the moment your instance is created. Our application development courses, for example, all start against the same movie recommendations dataset, so you can write meaningful queries against real relationships from the very first lesson instead of importing data.</p>

<p><img src="/assets/img/2026/07/a-real-neo4j-instance-for-every-learner-3.png" alt="The movie recommendations data model: a Person node with ACTED_IN and DIRECTED relationships to a Movie, the Movie IN_GENRE a Genre, and a User who RATED the Movie" /></p>

<p>The seed runs once, automatically, in the background. When you first join a course, you may see a message that your instance is being prepared.</p>

<h2 id="it-pauses-and-cleans-up-after-itself">It pauses and cleans up after itself</h2>

<p>Instances do not run forever. When you stop using one, Aura automatically pauses it after a period of inactivity. When you come back and open the next lesson, GraphAcademy detects the paused instance and resumes it for you.</p>

<p>Each instance also has a fixed lifetime and once an instance expires it is terminated and its resources are released. Come back to a course after a long break and you simply get a fresh one.</p>

<h2 id="graphacademy-runs-on-neo4j-too">GraphAcademy runs on Neo4j too</h2>

<p>There is a nice symmetry here: the GraphAcademy platform that hands you a Neo4j database is itself built on Neo4j.</p>

<p>Your instances are not tracked in a separate table off to the side. They live in the same graph that models you as a learner. When GraphAcademy provisions a database, it writes an <code class="language-plaintext highlighter-rouge">Instance</code> node and connects it to your <code class="language-plaintext highlighter-rouge">User</code> node with a <code class="language-plaintext highlighter-rouge">HAS_INSTANCE</code> relationship:</p>

<p><img src="/assets/img/2026/07/a-real-neo4j-instance-for-every-learner-4.png" alt="Two nodes: a User node labelled You, joined by a HAS_INSTANCE relationship to an Instance node whose usecase is 'recommendations'" /></p>

<p>The <code class="language-plaintext highlighter-rouge">Instance</code> node stores everything the platform needs to manage it: the use-case it holds, its connection details, its seeding status, and its expiry date. Because it is a graph, operational questions become traversals rather than joins across tables. Finding every instance due to expire is a single query from the <code class="language-plaintext highlighter-rouge">Instance</code> nodes.</p>

<h2 id="try-it-yourself">Try it yourself</h2>

<p>The fastest way to understand any of this is to start a course and watch your own instance come to life. <a href="https://graphacademy.neo4j.com/courses/neo4j-fundamentals">Neo4j Fundamentals</a> is a good place to begin: it walks you through graph thinking and gives you a live database to experiment in from the start.</p>

<hr />

<p><em>Originally published on <a href="https://graphacademy.neo4j.com/blog/your-own-neo4j-instance">Neo4j GraphAcademy</a> on 3 July 2026.</em></p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="neo4j" /><category term="learning" /><summary type="html"><![CDATA[Reading about a graph database is not the same as using one. You can follow every explanation of a clause perfectly and still freeze the first time you face an empty query editor. When you start a hands-on course on GraphAcademy, you get a real Neo4j database of your own.]]></summary></entry><entry><title type="html">Can I use Aura Agents to create an MCP enabled teacher?</title><link href="https://www.stuffaboutcode.com/posts/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher/" rel="alternate" type="text/html" title="Can I use Aura Agents to create an MCP enabled teacher?" /><published>2026-04-21T14:04:57+01:00</published><updated>2026-04-21T14:04:57+01:00</updated><id>https://www.stuffaboutcode.com/posts/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher/"><![CDATA[<p>If you have used an agent to code you will know they are great at providing solutions to problems. Unfortunately they are not as good at teaching you to do it yourself.</p>

<p>Fine tuned to deliver solutions, they lack good context relating to how to teach complex subjects, often missing key points, providing inconsistent results, and don’t build confidence.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Human]
Create a function which connects to my Neo4j databases and runs a 
Cypher statement.

[AI] 
Sure, here is a new function, run_cypher(driver, database, cypher).

[Human]
How does this work?

[AI]
It runs a cypher statement using the neo4j.GraphDatabase driver 
instance and database name and returns a list of Record objects.

[Human]
But how does it actually work? What are the concepts I need to know? 
How do I do this myself?
</code></pre></div></div>

<p>I wanted to change this and provide coding agents (VS Code, Cursor, etc) with access to Neo4j’s <a href="https://graphacademy.neo4j.com?ref=martin-aura-agents">GraphAcademy</a> content and teaching approaches.</p>

<p>GraphAcademy is Neo4j’s free online training platform and provides learners with knowledge and skills to use Neo4j to solve real world problems, including a new course on <a href="https://graphacademy.neo4j.com/courses/aura-agents/?ref=martin-aura-agents">Building Agents on Neo4j Aura</a>.</p>

<h2 id="the-plan">The plan</h2>

<ol>
  <li>Create a <a href="https://neo4j.com/product/neo4j-graph-database">Neo4j graph database</a> on <a href="https://neo4j.com/product/auradb/">Aura</a> of <a href="https://graphacademy.neo4j.com?ref=martin-aura-agents">GraphAcademy</a> lessons.</li>
  <li>Build an <a href="https://neo4j.com/product/aura-agent/">Aura agent</a> with tools to search, retrieve, and format lessons.</li>
  <li>Expose the agent as an MCP server that can connect to an agent.</li>
</ol>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-1.png" alt="GraphAcademy MCP POC architecture" /></p>

<h2 id="the-graph-database">The graph database</h2>

<p><a href="http://github.com/neo4j-graphacademy/courses">GraphAcademy’s lesson content is open source</a> and written in Asciidoc.</p>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-2.png" alt="GraphAcademy lessons" /></p>

<p>I used <a href="https://github.com/asciidoctor/asciidoctor-reducer">asciidoctor-reducer</a> to turn the lessons into single files containing all the text, code, examples, and questions by injecting attributes and resolving any includes.</p>

<p>Each lesson was chunked into sections, embeddings were created using <a href="https://developers.openai.com/api/docs/models/text-embedding-3-small">OpenAI’s text-embedding-3-small model</a>, and stored in Neo4j Aura with associated meta data about the lesson (course structure, slugs, names, urls, etc).</p>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-3.png" alt="Graph model" /></p>

<p>With the data in Neo4j I could search to find complete lessons based on something I want to learn (a <code class="language-plaintext highlighter-rouge">$subject</code>) e.g. <em>Teach me about filtering with Cypher WHERE clauses</em>.</p>

<div class="language-cypher highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">WITH</span> <span class="n">ai.text.embed</span><span class="ss">(</span>
  <span class="n">$subject</span><span class="ss">,</span> 
  <span class="s1">'OpenAI'</span><span class="ss">,</span> 
  <span class="ss">{</span> <span class="py">token:</span> <span class="n">$token</span><span class="ss">,</span> <span class="py">model:</span> <span class="s1">'text-embedding-3-small'</span> <span class="ss">}</span>
<span class="ss">)</span> <span class="k">AS</span> <span class="n">userEmbedding</span>

<span class="k">MATCH</span><span class="w"> </span><span class="ss">(</span><span class="py">c:</span><span class="n">Chunk</span><span class="ss">)</span>
<span class="n">SEARCH</span> <span class="n">c</span> <span class="k">IN</span><span class="w"> </span><span class="ss">(</span>
  <span class="n">VECTOR</span> <span class="k">INDEX</span> <span class="n">chunkText</span>
  <span class="n">FOR</span> <span class="n">userEmbedding</span>
  <span class="k">LIMIT</span> <span class="n">$k</span>
<span class="ss">)</span> <span class="n">SCORE</span> <span class="k">AS</span> <span class="n">score</span>
<span class="k">WHERE</span> <span class="n">score</span> <span class="o">&gt;</span> <span class="mf">0.7</span>

<span class="k">MATCH</span><span class="w"> </span><span class="ss">(</span><span class="n">c</span><span class="ss">)</span><span class="o">&lt;-</span><span class="ss">[</span><span class="nc">:HAS_CHUNK</span><span class="ss">]</span><span class="o">-</span><span class="ss">(</span><span class="py">l:</span><span class="n">Lesson</span><span class="ss">)</span>

<span class="k">RETURN</span> 
  <span class="n">l.slug</span> <span class="k">as</span> <span class="n">slug</span><span class="ss">,</span> 
  <span class="n">l.title</span> <span class="k">as</span> <span class="n">title</span><span class="ss">,</span> 
  <span class="n">l.url</span> <span class="k">as</span> <span class="n">url</span><span class="ss">,</span> 
  <span class="n">l.text</span> <span class="k">as</span> <span class="n">lesson</span><span class="ss">,</span> 
  <span class="nf">avg</span><span class="ss">(</span><span class="n">score</span><span class="ss">)</span> <span class="k">as</span> <span class="n">score</span>

<span class="k">ORDER</span> <span class="k">BY</span> <span class="n">score</span> <span class="k">DESC</span>
</code></pre></div></div>

<p>The Cypher query:</p>

<ol>
  <li>Creates an embedding of the <code class="language-plaintext highlighter-rouge">$subject</code> using the same OpenAI text-embedding-3-small model that was used to create embeddings of the lesson chunks.</li>
  <li>Searches the <code class="language-plaintext highlighter-rouge">chunkText</code> vector index and return chunks that have a similarity <code class="language-plaintext highlighter-rouge">score &gt; 0.7</code>.</li>
  <li>Matches the lessons for the chunks.</li>
  <li>Returns the lesson data, aggregating by the average score of the lesson chunks found. Averaging the score ensures single high scoring chunks don’t result in the query returning less relevant lessons as the top match.</li>
</ol>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-4.png" alt="Results of the cypher to search lessons" /></p>

<h2 id="aura-agent">Aura Agent</h2>

<p>Aura Agents allow you to create a fully functional GraphRAG agent connected to your Neo4j Aura database.</p>

<p>I used an <a href="https://neo4j.com/product/aura-agent/">Aura agent</a> to provide the entry point to the lessons and generate responses to queries and questions.</p>

<p>Aura Agents require:</p>

<ul>
  <li>a name — to indicate what the agents does.</li>
  <li>a prompt — that describes the agent’s purpose, approach, and instructions.</li>
  <li>a set of tools — to retrieve data from Neo4j as context to the questions.</li>
</ul>

<p>The prompt instructs the agent to present the GraphAcademy lessons as a learning experience, including ensuring there is an objective, that misconceptions are challenged, and options given for next steps.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Find and present Neo4j GraphAcademy lessons and appropriate progression 
to the follow on topics.

Provide lessons that:

- are standalone and meet a learning objective
- include examples and code snippets where relevant
- show outcomes and results of actions
- where there are potential misconceptions pose questions 
  and answers that challenge them
- have recommendations for next steps.
- provide a link to the full lesson to learn more.
- give options to either: continue to the next lesson, explore 
  related topics, or get more information.

Use the tool "get lesson for a chunk" after "search lesson chunks".
</code></pre></div></div>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-5.png" alt="Neo4j Aura agent setup" /></p>

<p>The agent accesses the lesson data through its tools. The tools are chosen by the agent based on the task. The agent will use as many of the tools as required to complete the task.</p>

<p>The agent only needs access to a relatively smaller number of tools which search and retrieve the lesson:</p>

<ul>
  <li><strong>search for a lesson</strong> — semantic search for lesson chunks</li>
  <li><strong>get a complete lesson</strong> — gets a complete lesson</li>
  <li><strong>get next lesson</strong> — gets the next lesson in the sequence</li>
</ul>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-6.png" alt="Aura agent tools" /></p>

<p>Using the smallest number of tools to provide access to the data often results in better outcomes and tool use.</p>

<p>The agent will use the tools to find and present teaching content about Neo4j.</p>

<p>When given a query such as “How to use WITH in Cypher”, the agent will search for a lesson, get the complete lesson for the chunk returned, and provide a lesson.</p>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-7.png" alt="The agent preview answering &quot;How to use WITH in Cypher&quot; alongside its list of enabled tools" /></p>

<p>The lessons are already structured for the purpose and the prompt is enough for the agent to provide good responses.</p>

<p>The agent will provide follow on tasks and allow the user to progress through to the next lesson in the course.</p>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-8.png" alt="The end of a lesson, with recommendations for next steps and three numbered options to continue" /></p>

<h2 id="mcp-server">MCP Server</h2>

<p>It’s important we meet developers were they are and to do that I need to provide external access to my agent.</p>

<p>An Aura agent can be accessed externally via a HTTPS endpoint or as an MCP server.</p>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-9.png" alt="The agent's Access settings, with External selected and &quot;Enable MCP server&quot; switched on" /></p>

<p>Agent applications (VS Code, Cursor, etc) typically have the ability to interact with MCP servers to complete tasks.</p>

<p>You can <a href="https://neo4j.com/developer/genai-ecosystem/aura-agent-getting-started/#_part_4_deploying_the_agent_to_rest_mcp">add your Aura agent as an MCP server</a> to you agent application allowing it to use the responses your Aura agent as context to your queries.</p>

<p>When asking the VS Code agent to <em>“Teach me how to run a Cypher statement using the Neo4j Python driver”,</em> the agent will find lessons from GraphAcademy and create responses using the lessons content and structure.</p>

<p><img src="/assets/img/2026/04/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-10.gif" alt="Aura Agent used in VS Code" /></p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[HUMAN]
Teach me how to run a Cypher statement using the Neo4j Python driver

[AI]
I’ll quickly pull the official Neo4j learning guidance first, then I’ll give 
you a concise, practical Python example you can run immediately.

[TOOL]
Ran Neo4j Learning
Input:
{
  "query": "How to run a Cypher statement using the Neo4j Python driver
}
Output:
Here's a lesson on how to run a Cypher statement using the Neo4j Python driver,
including connection setup and query execution.
...continued...

[AI]
Great use case. Here is the simplest way to run a Cypher statement with the 
Neo4j Python driver.

1. Install the driver
pip install neo4j

2. Use this minimal script:

from neo4j import GraphDatabase

URI = "neo4j+s://&lt;your-instance-id&gt;.databases.neo4j.io"
USER = "neo4j"
PASSWORD = "&lt;your-password&gt;"

def run_query():
    # Create a driver (connection factory)
    driver = GraphDatabase.driver(URI, auth=(USER, PASSWORD))

    try:
        # execute_query is the easiest API in driver v5+
        records, summary, keys = driver.execute_query(
            "RETURN 'Hello, Neo4j!' AS message"
        )

        for record in records:
            print(record["message"])

        print("Query ran successfully.")
        print("Database:", summary.database)
    finally:
        driver.close()

if __name__ == "__main__":
    run_query()

3. Run it
python your_script.py

Parameterized Cypher example (recommended):

records, summary, keys = driver.execute_query(
    "RETURN $name AS name",
    name="Martin"
)
print(records[0]["name"])

Why this matters:

1. Use parameters like $name instead of string concatenation for safety 
   and performance.
2. For Neo4j Aura, use neo4j+s in the URI.
3. Close the driver when done.

If you want, I can also give you:

1. A read/write transaction example with session.execute_read and 
   session.execute_write.
2. A small reusable Neo4j client class you can drop into your project.
</code></pre></div></div>

<p>The response from the agent is structured as a lesson, includes good progression from a simple example, adding features, explaining the approaches used, and giving options to continue learning.</p>

<p>All the code and resources are in the repository <a href="https://github.com/martinohanlon/ga-mcp-poc">github.com/martinohanlon/ga-mcp-poc</a>.</p>

<h2 id="next-steps">Next steps</h2>

<p>This proof-of-concept is just the beginning, an MCP server is part of our ongoing development of GraphAcademy. We are keen to meet developers where they are, and some of them are in code development environments.</p>

<p>You can learn the skills to create your own graph, MCP server, and Aura agent in the <a href="https://graphacademy.neo4j.com?ref=martin-aura-agents">GraphAcademy</a> courses <a href="https://graphacademy.neo4j.com/courses/llm-vectors-unstructured/?ref=martin-aura-agents">Introduction to Vector Indexes and Unstructured Data</a>, <a href="https://graphacademy.neo4j.com/courses/genai-mcp-build-custom-tools-python/?ref=martin-aura-agents">Building GraphRAG Python MCP tools</a>, and <a href="https://graphacademy.neo4j.com/courses/aura-agents/?ref=martin-aura-agents">Building Agents in Neo4j Aura</a>.</p>

<hr />

<p><em>Originally published on <a href="https://neo4j.com/blog/graph-database/can-i-use-aura-agents-to-create-an-mcp-enabled-teacher-2/">Neo4j</a> on 21 April 2026.</em></p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="neo4j" /><category term="agents" /><category term="mcp" /><summary type="html"><![CDATA[If you have used an agent to code you will know they are great at providing solutions to problems. Unfortunately they are not as good at teaching you to do it yourself.]]></summary></entry><entry><title type="html">Constructing Knowledge Graphs With Neo4j GraphRAG for Python</title><link href="https://www.stuffaboutcode.com/posts/constructing-knowledge-graphs-with-neo4j-graphrag-for-python/" rel="alternate" type="text/html" title="Constructing Knowledge Graphs With Neo4j GraphRAG for Python" /><published>2025-12-03T15:34:59+00:00</published><updated>2025-12-03T15:34:59+00:00</updated><id>https://www.stuffaboutcode.com/posts/constructing-knowledge-graphs-with-neo4j-graphrag-for-python</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/constructing-knowledge-graphs-with-neo4j-graphrag-for-python/"><![CDATA[<p>A knowledge graph is an organized representation of real-world entities and their relationships. <a href="https://neo4j.com/blog/knowledge-graph/what-is-knowledge-graph/">Knowledge graphs</a> provide a structured way to represent entities, their attributes, and their relationships, allowing for a comprehensive and interconnected understanding of the information.</p>

<p>Creating knowledge graphs from unstructured data can be complex, involving multiple steps of data query, cleansing, and transforms. You can use the text analysis capabilities of LLMs to help automate knowledge graph creation.</p>

<p>The <a href="https://neo4j.com/docs/neo4j-graphrag-python/current/">Neo4j GraphRAG for Python (neo4j_graphrag) package</a> includes a Knowledge Graph Builder to help you convert your unstructured and structured data.</p>

<h2 id="knowledge-graph-builder">Knowledge Graph Builder</h2>

<p>The <a href="https://neo4j.com/docs/neo4j-graphrag-python/current/user_guide_kg_builder.html"><code class="language-plaintext highlighter-rouge">SimpleKGPipeline</code></a> class provides a pipeline that implements a series of steps to create a knowledge graph from unstructured data:</p>

<ol>
  <li>Load the text</li>
  <li>Split the text into chunks</li>
  <li>Create embeddings for each chunk</li>
  <li>Extract entities from the chunks using an LLM</li>
  <li>Write the data to a Neo4j database</li>
</ol>

<p><img src="/assets/img/2025/12/constructing-knowledge-graphs-with-neo4j-graphrag-for-python-1.png" alt="The SimpleKGPipeline steps, left to right: a document into a data loader, then a text splitter, chunk embedder, and entity and relation extractor, which draws on a schema builder and a lexical graph builder, then a graph pruner, KG writer and entity resolver, both reading and writing a Neo4j database" /></p>

<p>For example, you could turn the Neo4j Wikipedia page into a graph representing Neo4j the organization and the database.</p>

<p><img src="/assets/img/2025/12/constructing-knowledge-graphs-with-neo4j-graphrag-for-python-2.png" alt="The Neo4j Wikipedia article on the left, an arrow labelled SimpleKGBuilder, and on the right a small graph: a Neo4j GraphDatabase node DEVELOPED_BY a Neo4j Inc Company node and IMPLEMENTED_IN a Java ProgLanguage node" /></p>

<p>The <code class="language-plaintext highlighter-rouge">SimpleKGPipeline</code> only requires a Neo4j connection, an embedding model, and an LLM to turn your documents into a knowledge graph.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">os</span>
<span class="kn">from</span> <span class="n">dotenv</span> <span class="kn">import</span> <span class="n">load_dotenv</span>
<span class="nf">load_dotenv</span><span class="p">()</span>

<span class="kn">import</span> <span class="n">asyncio</span>

<span class="kn">from</span> <span class="n">neo4j</span> <span class="kn">import</span> <span class="n">GraphDatabase</span>
<span class="kn">from</span> <span class="n">neo4j_graphrag.llm</span> <span class="kn">import</span> <span class="n">OpenAILLM</span>
<span class="kn">from</span> <span class="n">neo4j_graphrag.embeddings</span> <span class="kn">import</span> <span class="n">OpenAIEmbeddings</span>
<span class="kn">from</span> <span class="n">neo4j_graphrag.experimental.pipeline.kg_builder</span> <span class="kn">import</span> <span class="n">SimpleKGPipeline</span>

<span class="n">neo4j_driver</span> <span class="o">=</span> <span class="n">GraphDatabase</span><span class="p">.</span><span class="nf">driver</span><span class="p">(</span>
    <span class="n">os</span><span class="p">.</span><span class="nf">getenv</span><span class="p">(</span><span class="sh">"</span><span class="s">NEO4J_URI</span><span class="sh">"</span><span class="p">),</span>
    <span class="n">auth</span><span class="o">=</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="nf">getenv</span><span class="p">(</span><span class="sh">"</span><span class="s">NEO4J_USERNAME</span><span class="sh">"</span><span class="p">),</span> <span class="n">os</span><span class="p">.</span><span class="nf">getenv</span><span class="p">(</span><span class="sh">"</span><span class="s">NEO4J_PASSWORD</span><span class="sh">"</span><span class="p">))</span>
<span class="p">)</span>
<span class="n">neo4j_driver</span><span class="p">.</span><span class="nf">verify_connectivity</span><span class="p">()</span>

<span class="n">llm</span> <span class="o">=</span> <span class="nc">OpenAILLM</span><span class="p">(</span>
    <span class="n">model_name</span><span class="o">=</span><span class="sh">"</span><span class="s">gpt-4o</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">model_params</span><span class="o">=</span><span class="p">{</span>
        <span class="sh">"</span><span class="s">temperature</span><span class="sh">"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
        <span class="sh">"</span><span class="s">response_format</span><span class="sh">"</span><span class="p">:</span> <span class="p">{</span><span class="sh">"</span><span class="s">type</span><span class="sh">"</span><span class="p">:</span> <span class="sh">"</span><span class="s">json_object</span><span class="sh">"</span><span class="p">},</span>
    <span class="p">}</span>
<span class="p">)</span>

<span class="n">embedder</span> <span class="o">=</span> <span class="nc">OpenAIEmbeddings</span><span class="p">(</span>
    <span class="n">model</span><span class="o">=</span><span class="sh">"</span><span class="s">text-embedding-ada-002</span><span class="sh">"</span>
<span class="p">)</span>

<span class="n">kg_builder</span> <span class="o">=</span> <span class="nc">SimpleKGPipeline</span><span class="p">(</span>
    <span class="n">llm</span><span class="o">=</span><span class="n">llm</span><span class="p">,</span>
    <span class="n">driver</span><span class="o">=</span><span class="n">neo4j_driver</span><span class="p">,</span>
    <span class="n">neo4j_database</span><span class="o">=</span><span class="n">os</span><span class="p">.</span><span class="nf">getenv</span><span class="p">(</span><span class="sh">"</span><span class="s">NEO4J_DATABASE</span><span class="sh">"</span><span class="p">),</span>
    <span class="n">embedder</span><span class="o">=</span><span class="n">embedder</span><span class="p">,</span>
    <span class="n">from_pdf</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">)</span>

<span class="n">pdf_file</span> <span class="o">=</span> <span class="sh">"</span><span class="s">.my_document.pdf</span><span class="sh">"</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span><span class="n">kg_builder</span><span class="p">.</span><span class="nf">run_async</span><span class="p">(</span><span class="n">file_path</span><span class="o">=</span><span class="n">pdf_file</span><span class="p">))</span>
<span class="nf">print</span><span class="p">(</span><span class="n">result</span><span class="p">.</span><span class="n">result</span><span class="p">)</span>
</code></pre></div></div>

<p>You can learn how to use and customize the <code class="language-plaintext highlighter-rouge">SimpleKGPipeline</code> in a new <a href="https://graphacademy.neo4j.com">GraphAcademy</a> course: <a href="https://graphacademy.neo4j.com/courses/genai-graphrag-python/">Constructing Knowledge Graphs with Neo4j GraphRAG for Python</a>.</p>

<p><img src="/assets/img/2025/12/constructing-knowledge-graphs-with-neo4j-graphrag-for-python-3.png" alt="Course banner: Neo4j GraphAcademy, Constructing Knowledge Graphs with Neo4j GraphRAG for Python — learn how to use Generative AI and LLMs to convert unstructured data into knowledge graphs" /></p>

<p>You’ll also learn how to:</p>

<ul>
  <li>Create text splitters and define chunks</li>
  <li>Implement custom data loaders</li>
  <li>Define a schema for your lexical (unstructured) graph to ensure that you’re extracting the data you need</li>
  <li>Add structured data alongside your unstructured data</li>
  <li>Create <a href="https://neo4j.com/essential-graphrag/">GraphRAG</a> pipelines and retrievers to access your knowledge graph</li>
</ul>

<h2 id="summary">Summary</h2>

<p>Knowledge graphs help you organize and make sense of your data. Learn how to create them in the GraphAcademy <a href="https://graphacademy.neo4j.com/courses/genai-graphrag-python/">Constructing Knowledge Graphs with Neo4j GraphRAG for Python</a> course.</p>

<hr />

<p><em>Originally published on <a href="https://neo4j.com/blog/developer/knowledge-graphs-neo4j-graphrag-for-python/">Neo4j Developer Blog</a> on 3 December 2025.</em></p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="graphrag" /><category term="neo4j" /><category term="python" /><summary type="html"><![CDATA[A knowledge graph is an organized representation of real-world entities and their relationships. Knowledge graphs provide a structured way to represent entities, their attributes, and their relationships, allowing for a comprehensive and interconnected understanding of the information.]]></summary></entry><entry><title type="html">Run Cypher From Java</title><link href="https://www.stuffaboutcode.com/posts/run-cypher-from-java/" rel="alternate" type="text/html" title="Run Cypher From Java" /><published>2025-05-13T19:20:03+01:00</published><updated>2025-05-13T19:20:03+01:00</updated><id>https://www.stuffaboutcode.com/posts/run-cypher-from-java</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/run-cypher-from-java/"><![CDATA[<p>Are you a Java developer looking to take advantage of graphs and Neo4j?</p>

<p>There’s a new <a href="https://graphacademy.neo4j.com">GraphAcademy</a> course called <a href="https://graphacademy.neo4j.com/courses/drivers-java/">Using Neo4j with Java</a>, where you can learn how to integrate the Neo4j Java driver into your Java application.</p>

<p><img src="/assets/img/2025/05/run-cypher-from-java-1.png" alt="Course banner: Using Neo4j with Java — learn how to interact with Neo4j using the Neo4j Java driver, with the Java logo on a dark blue background" /></p>

<p>The most common thing you will need to do when using Neo4j with Java is run a Cypher query and parse the results. The process is really simple:</p>

<ol>
  <li>Import the driver</li>
  <li>Connect to a server</li>
  <li>Verify the connection</li>
  <li>Execute a Cypher query</li>
  <li>Parse the results</li>
  <li>Close the connection</li>
</ol>

<h2 id="import-the-driver">Import the Driver</h2>

<p>The driver is distributed via Maven, you can find snippets and example POM files on the <a href="https://central.sonatype.com/artifact/org.neo4j.driver/neo4j-java-driver/overview">Maven Central Repository — neo4j-java-driver package</a>.</p>

<p>You’ll need to import the GraphDatabase and AuthTokens classes from the Neo4j driver package:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">org.neo4j.driver.GraphDatabase</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.neo4j.driver.AuthTokens</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">App</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="no">NEO4J_URI</span> <span class="o">=</span> <span class="s">"bolt://localhost:7687"</span><span class="o">;</span>
        <span class="nc">String</span> <span class="no">NEO4J_USERNAME</span> <span class="o">=</span> <span class="s">"neo4j"</span><span class="o">;</span>
        <span class="nc">String</span> <span class="no">NEO4J_PASSWORD</span> <span class="o">=</span> <span class="s">"mypassword"</span><span class="o">;</span>

        <span class="c1">// Create a new Neo4j driver instance</span>

        <span class="c1">// Verify the connection </span>

        <span class="c1">// Execute a Cypher query</span>

        <span class="c1">// Parse the results</span>

        <span class="c1">// Close the connection</span>

    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="connect-to-a-server">Connect to a Server</h2>

<p>Create an instance of the <code class="language-plaintext highlighter-rouge">GraphDatabase.driver</code> class, passing your credentials:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="c1">// Create a new Neo4j driver instance</span>
        <span class="kt">var</span> <span class="n">driver</span> <span class="o">=</span> <span class="nc">GraphDatabase</span><span class="o">.</span><span class="na">driver</span><span class="o">(</span>
                <span class="no">NEO4J_URI</span><span class="o">,</span>
                <span class="nc">AuthTokens</span><span class="o">.</span><span class="na">basic</span><span class="o">(</span>
                    <span class="no">NEO4J_USERNAME</span><span class="o">,</span>
                    <span class="no">NEO4J_PASSWORD</span><span class="o">)</span>
            <span class="o">);</span>
</code></pre></div></div>

<h2 id="verify-the-connection">Verify the Connection</h2>

<p>You can test the connection by calling the <code class="language-plaintext highlighter-rouge">verifyConnectivity</code> method:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="c1">// Verify the connection </span>
        <span class="n">driver</span><span class="o">.</span><span class="na">verifyConnectivity</span><span class="o">();</span>
</code></pre></div></div>

<p>The driver will raise an exception if the connection cannot be made.</p>

<h2 id="execute-a-cypher-query">Execute a Cypher Query</h2>

<p>The <code class="language-plaintext highlighter-rouge">executableQuery</code> method executes a Cypher query and returns the results:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="c1">// Execute a Cypher query</span>
        <span class="kt">var</span> <span class="n">result</span> <span class="o">=</span> <span class="n">driver</span><span class="o">.</span><span class="na">executableQuery</span><span class="o">(</span>
            <span class="s">"MATCH (c:Customer) RETURN c.name AS name, c.age AS age"</span>
            <span class="o">).</span><span class="na">execute</span><span class="o">();</span>
</code></pre></div></div>

<h2 id="parse-the-results">Parse the Results</h2>

<p>The <code class="language-plaintext highlighter-rouge">execute</code> method fetches a list of records and loads them into memory:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="c1">// Parse the results</span>
        <span class="kt">var</span> <span class="n">records</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">records</span><span class="o">();</span>
        <span class="n">records</span><span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">r</span> <span class="o">-&gt;</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"name"</span><span class="o">));</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"age"</span><span class="o">));</span>
        <span class="o">});</span>
</code></pre></div></div>

<p>You can iterate through the records and use <code class="language-plaintext highlighter-rouge">get</code> to retrieve return values.</p>

<h2 id="close-the-connection">Close the Connection</h2>

<p>Once you finish with the driver, call <code class="language-plaintext highlighter-rouge">close</code> to release any resources:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>        <span class="c1">// Close the connection</span>
        <span class="n">driver</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
</code></pre></div></div>

<p>Here’s the complete code that makes a connection, executes a query, and parses the results:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="nn">org.neo4j.driver.GraphDatabase</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">org.neo4j.driver.AuthTokens</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">class</span> <span class="nc">App</span> <span class="o">{</span>
    <span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">String</span> <span class="no">NEO4J_URI</span> <span class="o">=</span> <span class="s">"bolt://localhost:7687"</span><span class="o">;</span>
        <span class="nc">String</span> <span class="no">NEO4J_USERNAME</span> <span class="o">=</span> <span class="s">"neo4j"</span><span class="o">;</span>
        <span class="nc">String</span> <span class="no">NEO4J_PASSWORD</span> <span class="o">=</span> <span class="s">"mypassword"</span><span class="o">;</span>

        <span class="c1">// Create a new Neo4j driver instance</span>
        <span class="kt">var</span> <span class="n">driver</span> <span class="o">=</span> <span class="nc">GraphDatabase</span><span class="o">.</span><span class="na">driver</span><span class="o">(</span>
                <span class="no">NEO4J_URI</span><span class="o">,</span>
                <span class="nc">AuthTokens</span><span class="o">.</span><span class="na">basic</span><span class="o">(</span>
                    <span class="no">NEO4J_USERNAME</span><span class="o">,</span>
                    <span class="no">NEO4J_PASSWORD</span><span class="o">)</span>
            <span class="o">);</span>

        <span class="c1">// Verify the connection </span>
        <span class="n">driver</span><span class="o">.</span><span class="na">verifyConnectivity</span><span class="o">();</span>

        <span class="c1">// Execute a Cypher query</span>
        <span class="kt">var</span> <span class="n">result</span> <span class="o">=</span> <span class="n">driver</span><span class="o">.</span><span class="na">executableQuery</span><span class="o">(</span>
            <span class="s">"MATCH (c:Customer) RETURN c.name AS name, c.age AS age"</span>
            <span class="o">).</span><span class="na">execute</span><span class="o">();</span>

        <span class="c1">// Parse the results</span>
        <span class="kt">var</span> <span class="n">records</span> <span class="o">=</span> <span class="n">result</span><span class="o">.</span><span class="na">records</span><span class="o">();</span>
        <span class="n">records</span><span class="o">.</span><span class="na">forEach</span><span class="o">(</span><span class="n">r</span> <span class="o">-&gt;</span> <span class="o">{</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"name"</span><span class="o">));</span>
            <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"age"</span><span class="o">));</span>
        <span class="o">});</span>

        <span class="c1">// Close the connection</span>
        <span class="n">driver</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>

    <span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>

<h2 id="learn-more">Learn More</h2>

<p>You can learn more about how to use the Neo4j Java driver in the <a href="https://graphacademy.neo4j.com/courses/drivers-java">Using Neo4j with Java course on GraphAcademy</a>.</p>

<p>The course will get you started developing Java applications with Neo4j and covers topics such as passing parameters to queries, dealing with graph data types, and managing transactions.</p>

<hr />

<p><em>Originally published on the <a href="https://neo4j.com/blog/developer/run-cypher-from-java/">Neo4j Developer Blog</a> on 13 May 2025.</em></p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="neo4j" /><category term="cypher" /><category term="java" /><summary type="html"><![CDATA[Are you a Java developer looking to take advantage of graphs and Neo4j?]]></summary></entry><entry><title type="html">Learning Neo4j Your Way</title><link href="https://www.stuffaboutcode.com/posts/learning-neo4j-your-way/" rel="alternate" type="text/html" title="Learning Neo4j Your Way" /><published>2023-08-08T02:10:15+01:00</published><updated>2023-08-08T02:10:15+01:00</updated><id>https://www.stuffaboutcode.com/posts/learning-neo4j-your-way</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/learning-neo4j-your-way/"><![CDATA[<p>Hi, I recently joined the Neo4j developer relations team as the Technical Curriculum Developer.</p>

<p>My role is centered around developing Neo4j educational resources to support developers. I have a strong background in computer science education. My previous role was creating online learning experiences for the Raspberry Pi Foundation, and although I worked in software development and architecture for many years, my knowledge of Neo4j was limited.</p>

<p>Therefore, one of my first priorities was to educate myself in Neo4j.</p>

<blockquote>
  <p>Learning something new is hard! Multiple factors affect how hard it is, such as the resources available to you, your experience, knowledge and skills, and your own motivation. Applying a structure to your learning can help you achieve your objective.</p>
</blockquote>

<p>Come with me on an exploration of how I structured my learning using Neo4j’s developer relations output and educational content.</p>

<h2 id="make-your-learning-effective">Make Your Learning Effective</h2>

<p>Most people have an understanding of how they like to obtain knowledge and learn new skills. You may not be able to articulate how you like to learn, but you will be aware of the ways of learning that excite you or bore you!</p>

<blockquote>
  <p>Do you relish the opportunity to sit down with a text book or fear and loath it?</p>
</blockquote>

<p>I personally thrive when given a variety of learning opportunities and particularly when it’s accessible in short pieces with time to practice and get hands-on.</p>

<p>Neo4j’s investment in developer support and education resources is impressive, and there are plenty of resources and opportunities to learn and acquire new skills.</p>

<h2 id="create-a-goal">Create a Goal</h2>

<p>To structure and support my learning, I set myself these initial learning objectives:</p>

<ol>
  <li>Understand the benefits and use cases of graphs and graph databases</li>
  <li>Be able to design a graph data model to fulfill a typical use case</li>
  <li>Understand the options for loading data into a Neo4j database</li>
  <li>Use Cypher to load, query, and manipulate data in a graph database</li>
  <li>Use simple Graph Data Science tools and techniques to answer questions</li>
  <li>Interact with a Neo4j database using Python</li>
</ol>

<p>To make my learning effective, I also wanted to create my own project. A project excites me, it allows me to structure my learning so I get the opportunity to get hands-on, and it motivates me to continue.</p>

<p>From my initial understanding of graphs and Neo4j, I had an idea about mapping the physical world, understanding the relationship between places and how you move between them.</p>

<p>I have snowboarded for over 30 years, and I love nothing more than exploring the mountains. Large ski resorts are a complex system of interconnected trails and lifts.</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-1.png" alt="A section of the Grand Massif ski area in France" /></p>

<p>I planned to use a Neo4j graph database to allow me to answer the following questions:</p>

<ul>
  <li>What lifts and trails are there?</li>
  <li>What trails have a specified difficulty?</li>
  <li>What trails are accessible from a specific lift?</li>
  <li>Is there an accessible route between 2 points?</li>
  <li>What is the shortest path between 2 points?</li>
</ul>

<h2 id="a-learning-pathway">A Learning Pathway</h2>

<p>To help me achieve my goals, I created a learning pathway:</p>

<ol>
  <li>Fundamentals — Understanding Neo4j and graphs</li>
  <li>Data modeling — How to structure data in a graph</li>
  <li>Importing data — The options and processes for moving data into Neo4j</li>
  <li>Query and analysis — Answering questions and running queries</li>
  <li>Programming and integration — Integrating a graph database into an application</li>
</ol>

<p>The <a href="https://graphacademy.neo4j.com/categories/beginners/">Beginners collection of courses</a> on Neo4j’s GraphAcademy is an example of a learning pathway that supports you in obtaining the skills and knowledge required to complete the <a href="https://graphacademy.neo4j.com/courses/neo4j-certification/">Neo4j Certified Professional</a> certification.</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-2.png" alt="The beginner pathway of courses on Graph Academy" /></p>

<p>The beginner courses were my starting point for obtaining fundamental knowledge and skills.</p>

<h3 id="fundamentals">Fundamentals</h3>

<p>I started my learning with the <a href="https://graphacademy.neo4j.com/courses/neo4j-fundamentals/">Neo4j Fundamentals</a> and <a href="https://graphacademy.neo4j.com/courses/cypher-fundamentals/">Cypher Fundamentals</a> courses on <a href="https://graphacademy.neo4j.com/">GraphAcademy</a>. These courses introduce graph theory, the Neo4j graph database, and the Cypher query language. These courses gave me the initial knowledge and skills needed to interact with Neo4j and understand the key use cases.</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-3.png" alt="The seven bridges of Königsberg, a rite of passage for anyone learning about graphs" /></p>

<p>I wanted to increase my knowledge of the graph database sector, so I also started reading the complementary ebook <a href="https://neo4j.com/graph-databases-book/">Graph Databases by Ian Robinson, Jim Webber, and Emil Eifrem</a>.</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-4.png" alt="The cover of the O'Reilly book Graph Databases, second edition, by Ian Robinson, Jim Webber and Emil Eifrem" /></p>

<p>Once I had secured my fundamental knowledge of Neo4j, I looked for sources of ski resort data for my project. I discovered that trails and lifts were included in <a href="https://www.openstreetmap.org/">OpenStreetMap</a> (OSM).</p>

<p>I wrote an <a href="http://overpass-turbo.eu/">Overpass Turbo</a> query to export the lift (aerialway) and trail (piste) data in JSON.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[out:json][timeout:25];
area[name="Val Thorens"]-&gt;.a;
(  
  way["piste:type"](area.a);  
  way["aerialway"](area.a);  
);
out body;
&gt;;
out skel qt;
</code></pre></div></div>

<p>My plan was to use Overpass Turbo to export the data from OSM, create a Python program to convert it to CSV, and load it into Neo4j. I envisaged a graph of trail, lift, and position nodes using relationships to store how you would progress from one place to the next.</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-5.png" alt="A pipeline diagram: a ski piste map labelled overpass turbo, an arrow marked JSON to the Python logo, an arrow marked CSV to the Neo4j logo" /></p>

<h3 id="data-modeling">Data Modeling</h3>

<p>To understand graph data modeling and the potential for graph databases, I completed the <a href="https://graphacademy.neo4j.com/courses/modeling-fundamentals/">Graph Data Modeling Fundamentals</a> course, which explores how to design a Neo4j graph and best practices.</p>

<p>I learned about storing geospatial data in Neo4j from the <a href="https://www.youtube.com/watch?v=djMsdSxvd2E">Geospatial Data in your Graph</a> livestream on <a href="https://www.twitch.tv/neo4j">Neo4j’s Twitch channel</a>.</p>

<p>Neo4j Developer Advocate William Lyon has created a useful <a href="https://lyonwj.com/blog/spatial-cypher-cheat-sheet">Spatial Cypher Cheat Sheet</a> about managing geospatial data using Cypher.</p>

<p>The Twitch channel often has presenters from the Neo4j community and has helped me to expand my knowledge of the use of Neo4j and graph databases. The stream on <a href="https://www.youtube.com/watch?v=n5mo4bqBEPY">GraphTFT — a team builder app for Teamfight tactics</a> is a great example of how Neo4j can be used to understand relationships.</p>

<p>I developed a data model for my project, which incorporated what I have learned about graph databases, Neo4j, and the ski resort data I exported from OpenStreetMap.</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-6.png" alt="A data model: Lift and Trail nodes, each carrying name and type properties, both joined by IS_ROUTE to a Route node, which joins to a Position node by START, END and ON_ROUTE, with Position nodes chained to each other by NEXT" /></p>

<h3 id="importing-data">Importing Data</h3>

<p>I completed the <a href="https://graphacademy.neo4j.com/">Graph Academy</a> course <a href="https://graphacademy.neo4j.com/courses/importing-data/">Importing CSV Data into Neo4j</a> as my starting point for learning about importing data. The course gave me an understanding of the options available, how to use <a href="https://data-importer.neo4j.io/">Neo4j’s Data Importer</a>, and an overview of the <a href="https://neo4j.com/docs/cypher-manual/current/clauses/load-csv/"><code class="language-plaintext highlighter-rouge">LOAD CSV</code></a> statement in Cypher.</p>

<p>I created a <a href="https://github.com/martinohanlon/trail_and_run/blob/main/data/parse_data.py">Python program to generate CSV files</a> from the OSM data, which I could import into Neo4j and generate the data model I had created.</p>

<p>I signed up for <a href="https://neo4j.com/cloud/platform/aura-graph-database/">Neo4j’s AuraDB</a> and set up a free Neo4j database in the cloud in a couple of minutes.</p>

<p>Using the <a href="https://data-importer.neo4j.io/connection/connect">data importer</a> allowed me to quickly create a data model and get started on exploring the data, but as I looked to expand the data model, I found I needed to modify the source CSV files to support more complex relationships.</p>

<p>At this point, I had a learning opportunity. I could either:</p>

<ol>
  <li>Modify my Python program to get the data into the right shape and continue to use Data Importer.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">LOAD CSV</code> and Cypher to manipulate the existing CSV data at import.</li>
</ol>

<p>While changing my Python program would have been quicker and easier I chose to use Cypher. My knowledge of Cypher is limited, and it gave me an opportunity to practice what I had learned and develop my knowledge.</p>

<blockquote>
  <p>Choosing a path which exposes gaps in your learning will help you stretch yourself and expand your knowledge and skills.</p>
</blockquote>

<p>Neo4j’s documentation includes useful information on data importing, such as the <a href="https://neo4j.com/docs/getting-started/data-import/">Import your data into Neo4j</a> guide, the <a href="https://neo4j.com/docs/cypher-manual/current/introduction/">Cypher manual</a>, and the <a href="https://neo4j.com/docs/cypher-manual/current/functions/load-csv/">LOAD CSV reference</a>.</p>

<p>I generated a <a href="https://github.com/martinohanlon/trail_and_run/blob/main/load/load_data.cypher">data load process in Cypher</a> which took the CSV data and built the data model.</p>

<p>This code snippet loads lift data from a <a href="https://github.com/martinohanlon/trail_and_run/blob/main/data/flaine_lift_data.csv">CSV file</a> and adds the data to the graph.</p>

<div class="language-cypher highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// lifts</span>
<span class="k">LOAD</span> <span class="k">CSV</span> <span class="k">WITH</span> <span class="k">HEADERS</span>
<span class="k">FROM</span> <span class="s1">'file:///test_lift_data.csv'</span>
<span class="k">AS</span> <span class="n">row</span>
<span class="k">MATCH</span><span class="w"> </span><span class="ss">(</span><span class="py">r:</span><span class="n">Route</span> <span class="ss">{</span><span class="py">id:</span> <span class="nf">toInteger</span><span class="ss">(</span><span class="n">row.id</span><span class="ss">)})</span>
<span class="k">SET</span> <span class="py">r:</span><span class="n">Lift</span><span class="ss">,</span>
<span class="n">r.name</span> <span class="o">=</span> <span class="n">row.name</span><span class="ss">,</span>
<span class="n">r.type</span> <span class="o">=</span> <span class="n">row.type</span><span class="ss">,</span>
<span class="n">r.capacity</span> <span class="o">=</span> <span class="nf">toInteger</span><span class="ss">(</span><span class="n">row.capacity</span><span class="ss">),</span>
<span class="n">r.occupancy</span> <span class="o">=</span> <span class="nf">toInteger</span><span class="ss">(</span><span class="n">row.occupancy</span><span class="ss">),</span>
<span class="n">r.oneway</span> <span class="o">=</span> <span class="k">CASE</span> <span class="n">row.oneway</span> <span class="k">WHEN</span> <span class="s2">"yes"</span> <span class="k">THEN</span> <span class="k">true</span> <span class="k">WHEN</span> <span class="s2">"no"</span> <span class="k">THEN</span> <span class="k">false</span> <span class="k">ELSE</span> <span class="k">null</span> <span class="k">END</span><span class="ss">;</span>
</code></pre></div></div>

<p>Loading the data in Cypher allowed me to practice what I had learned and develop my skills.</p>

<h3 id="queries-and-analytics">Queries and Analytics</h3>

<p>I explored the data graph I had created, writing queries to answer the questions I had created, testing against known scenarios, and extending the data range.</p>

<p>I refactored the data model removing duplication and using multiple labels to determine whether a Route node was a Trail or a Lift.</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-7.png" alt="The refactored data model: a single node carrying the Route, Lift and Trail labels together with id, name, type and difficulty properties, joined to a Position node by START, END and ON_ROUTE, with Position nodes chained by NEXT" /></p>

<p>The <a href="https://graphacademy.neo4j.com/courses/cypher-intermediate-queries/">Intermediate Cypher Queries course</a> on <a href="https://graphacademy.neo4j.com">GraphAcademy</a> taught me more advanced Cypher queries and complex patterns.</p>

<p>I paired with my colleagues to learn about how to use shortest path algorithms and add properties such as distance and difficulty to relationships so I could use weighting to find alternative paths.</p>

<blockquote>
  <p>It is easy to forget that pairing is an opportunity for both people to learn new skills but also to find new ways of working.</p>
</blockquote>

<p>The Graph database and Cypher queries I developed allowed me to determine how to navigate ski resorts.</p>

<p>For example, if I was at the top of the “Lapiaz” lift, how could I get to the bottom of the “Lutin” trail?</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-8.png" alt="A piste map of the Grandes Platieres area, with a Start marker at the top of the Lapiaz lift and an End marker at the bottom of the Lutin trail" /></p>

<p>I created a Cypher query to find the start and end positions and used the <code class="language-plaintext highlighter-rouge">shortestPath</code> function to find a route between the 2 points.</p>

<div class="language-cypher highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">MATCH</span><span class="w"> </span><span class="ss">(</span><span class="py">l:</span><span class="n">Lift</span><span class="ss">{</span><span class="py">name:</span> <span class="s2">"Lapiaz"</span><span class="ss">})</span><span class="o">-</span><span class="ss">[</span><span class="nc">:END</span><span class="ss">]</span><span class="o">-&gt;</span><span class="ss">(</span><span class="py">startPos:</span><span class="n">Position</span><span class="ss">)</span>
<span class="k">MATCH</span><span class="w"> </span><span class="ss">(</span><span class="py">endPos:</span><span class="n">Position</span><span class="ss">)</span><span class="o">&lt;-</span><span class="ss">[</span><span class="nc">:END</span><span class="ss">]</span><span class="o">-</span><span class="ss">(</span><span class="py">t:</span><span class="n">Trail</span><span class="ss">{</span><span class="py">name:</span> <span class="s2">"Lutin"</span><span class="ss">})</span>
<span class="k">MATCH</span> <span class="n">path</span> <span class="o">=</span> <span class="nf">shortestPath</span><span class="ss">(</span>
    <span class="ss">(</span><span class="n">startPos</span><span class="ss">)</span><span class="o">-</span><span class="ss">[</span><span class="nc">:NEXT</span><span class="o">*</span><span class="n">..100</span><span class="ss">]</span><span class="o">-&gt;</span><span class="ss">(</span><span class="n">endPos</span><span class="ss">)</span>
<span class="ss">)</span>
<span class="k">RETURN</span> <span class="n">l</span><span class="ss">,</span> <span class="n">path</span><span class="ss">,</span> <span class="n">t</span>
</code></pre></div></div>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-9.png" alt="The query result in Neo4j Browser: a long chain of Position nodes linked by NEXT, running from the red Lapiaz node round to the green Lutin node" /></p>

<h3 id="programming-and-integration">Programming and Integration</h3>

<p>I wanted to use Neo4j within an application to help me learn about the end-to-end use of Neo4j.</p>

<p>I am an experienced Python programmer, and while I would have gained other skills from using a different language, my goal was to understand Neo4j integration options and the drivers, not learn a new language.</p>

<p>The <a href="https://neo4j.com/docs/getting-started/languages-guides/neo4j-python/">Using Neo4j from Python</a> developer guide got me started installing the Python Driver and getting an example application running.</p>

<p>The GraphAcademy course <a href="https://graphacademy.neo4j.com/courses/app-python">Building Neo4j Applications with Python</a> taught me about the driver in more detail, including executing read and write queries, managing transactions, and processing returned data.</p>

<p>I developed a <a href="https://github.com/martinohanlon/trail_and_run/blob/main/app/where_can_I_go.py">Python application</a> to access the ski resort data and show the route options from specific positions.</p>

<p><img src="/assets/img/2023/08/learning-neo4j-your-way-10.png" alt="A small desktop application called Route options: drop-downs to pick a position, set to the END of the Aujon Lift, and below them two suggestions, take trail 'Cornaline' or trail 'Calcedoine', both easy downhills" /></p>

<p>This helped me identify several new areas for my own learning, such as indexing and query optimization.</p>

<p>You can view the complete project code, including the data extract, data model, load process, and Python application, at <a href="https://github.com/martinohanlon/trail_and_run">github.com/martinohanlon/trail_and_run</a>.</p>

<h2 id="certifications-and-next-steps">Certifications and Next Steps</h2>

<p>I now feel ready to complete my <a href="https://graphacademy.neo4j.com/courses/neo4j-certification/">Neo4j Certified Professional</a> certification.</p>

<p>Gaining certification is an important step and demonstrates that you have the skills and knowledge required to develop solutions and support others.</p>

<p>On top of the recognition that comes with certification, assessment is an important part of any learning journey. Assessment allows you to recognize the progression you have made and identify gaps in your skills and knowledge; you can use this information to set your next goal and continuously adapt your learning journey.</p>

<h2 id="learning-with-neo4j">Learning With Neo4j</h2>

<p>If you are starting on a learning journey, I would recommend:</p>

<ul>
  <li>Creating a goal</li>
  <li>Setting some easy-to-understand learning objectives</li>
  <li>Structuring your learning so it appeals to and motivates you</li>
</ul>

<p>The <a href="https://neo4j.com/developer/">Neo4j Developers</a> page is the place to take your first steps with Neo4j. You will find guides, documentation, and events and be able to access the community of Neo4j developers.</p>

<p><a href="http://graphacademy.neo4j.com/">Neo4j GraphAcademy</a> offers a wide range of courses completely free of charge, teaching everything from <a href="https://graphacademy.neo4j.com/courses/neo4j-fundamentals/">Neo4j Fundamentals</a> to <a href="https://graphacademy.neo4j.com/categories/developer/">how to develop software that connects to Neo4j</a>.</p>

<p>I hope you have as much fun learning Neo4j as I have. I would love to hear about your experience learning Neo4j, our educational content, and particularly <a href="http://graphacademy.neo4j.com/">Neo4j GraphAcademy</a>.</p>

<hr />

<p><em>Originally published on <a href="https://neo4j.com/blog/developer/learning-neo4j-your-way/">Neo4j</a> on 8 August 2023.</em></p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="neo4j" /><category term="graph-database" /><category term="learning" /><summary type="html"><![CDATA[Hi, I recently joined the Neo4j developer relations team as the Technical Curriculum Developer.]]></summary></entry><entry><title type="html">Pi Camera stop motion animation</title><link href="https://www.stuffaboutcode.com/posts/pi-camera-stop-motion-animation/" rel="alternate" type="text/html" title="Pi Camera stop motion animation" /><published>2018-06-17T21:23:00+01:00</published><updated>2018-06-17T21:23:00+01:00</updated><id>https://www.stuffaboutcode.com/posts/pi-camera-stop-motion-animation</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/pi-camera-stop-motion-animation/"><![CDATA[<p>In preparation for a Raspberry Pi event I decided to create a simple GUI for creating stop motion animations using the Pi camera module to use for a demo.</p>

<p><img src="/assets/img/2018/06/picamera_setup.jpg" alt="" /></p>

<p>Its a really simple application, you start it up, you click “take image”, you re-position the scene, you click “take image” and so on until you are happy with your animation and you click “save” to store it as an animated gif.</p>

<p><img src="/assets/img/2018/06/df5pzagxuaa3im7.jpg" alt="" /></p>

<p><img src="/assets/img/2018/06/animation1529238449.919678.gif" alt="" /></p>

<p>You can find the source code at <a href="https://gist.github.com/martinohanlon/52a7557a91d9e5b353a278447fbacc34">gist.github.com/martinohanlon</a>.</p>

<p><strong>Install</strong></p>
<ol>
  <li>Connect a camera module</li>
  <li>Enable the camera (Menu &gt; Preferences &gt; Raspberry Pi Configuration, Interfaces, Camera)</li>
  <li>Open a terminal (Menu &gt; Accessories &gt; Terminal), install the modules and download the code:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>pip3 <span class="nb">install </span>guizero
<span class="nb">sudo </span>pip3 <span class="nb">install </span>imageio
wget <span class="nt">-O</span> guizero_stopmotion.py https://gist.githubusercontent.com/martinohanlon/52a7557a91d9e5b353a278447fbacc34/raw/guizero_stopmotion.py
</code></pre></div></div>

<ol>
  <li>Run the program:</li>
</ol>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python3 guizero_stopmotion.py
</code></pre></div></div>

<p><strong>A couple of “interesting” things about this project</strong>
 The gui was created using <a href="https://lawsie.github.io/guizero/">guizero</a> which is a super simple to use library for creating GUI’s, definitely have a look.</p>

<p>Most of the work was finding a way to create animated gifs in Python and working with images in memory rather than stored on disk</p>

<p>When the image is captured from the camera it isn’t stored to a file, it is stored in a numpy array, this means each frame is only stored in memory making it faster:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># create the camera
</span><span class="n">camera</span> <span class="o">=</span> <span class="nc">PiCamera</span><span class="p">(</span><span class="n">resolution</span><span class="o">=</span><span class="sh">"</span><span class="s">640x480</span><span class="sh">"</span><span class="p">)</span>
<span class="n">camera_output</span> <span class="o">=</span> <span class="nc">PiRGBArray</span><span class="p">(</span><span class="n">camera</span><span class="p">)</span>
<span class="bp">...</span>
<span class="c1"># capture the image
</span><span class="n">camera</span><span class="p">.</span><span class="nf">capture</span><span class="p">(</span><span class="n">camera_output</span><span class="p">,</span> <span class="sh">"</span><span class="s">rgb</span><span class="sh">"</span><span class="p">)</span>
<span class="c1"># append the camera image to the list as a numpy array
</span><span class="n">animation</span><span class="p">.</span><span class="n">images</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">camera_output</span><span class="p">.</span><span class="n">array</span><span class="p">)</span>
</code></pre></div></div>

<p>The python module imageio is used to create the gif by passing the frames as a list, but again rather than being written to disk each time it is created as an in memory BytesIO stream:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">gif_output</span> <span class="o">=</span> <span class="nc">BytesIO</span><span class="p">()</span>
<span class="n">imageio</span><span class="p">.</span><span class="nf">mimsave</span><span class="p">(</span><span class="n">gif_output</span><span class="p">,</span> <span class="n">animation</span><span class="p">.</span><span class="n">images</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="sh">"</span><span class="s">gif</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>When the animated gif is displayed in guizero the BytesIO stream has to be open into a PIL Image.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">animation</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">Image</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="n">gif_output</span><span class="p">)</span>
</code></pre></div></div>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="python" /><category term="raspberry-pi" /><summary type="html"><![CDATA[In preparation for a Raspberry Pi event I decided to create a simple GUI for creating stop motion animations using the Pi camera module to use for a demo.]]></summary></entry><entry><title type="html">Get the weather using Python</title><link href="https://www.stuffaboutcode.com/posts/get-weather-using-python/" rel="alternate" type="text/html" title="Get the weather using Python" /><published>2018-06-09T21:58:00+01:00</published><updated>2018-06-09T21:58:00+01:00</updated><id>https://www.stuffaboutcode.com/posts/get-weather-using-python</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/get-weather-using-python/"><![CDATA[<p>I recently spent a hour or so hacking a lucky cat so that it would only wave when it was sunny.</p>

<blockquote>
  <p>At <a href="https://twitter.com/Raspberry_Pi?ref_src=twsrc%5Etfw">@Raspberry_Pi</a> ‘s MakerDay I hacked a lucky cat so it only waves when it’s sunny. <a href="https://twitter.com/martinohanlon/status/1005342439874486273">pic.twitter.com/JUED5nYadt</a></p>

  <p>— Martin O’Hanlon (@martinohanlon) <a href="https://twitter.com/martinohanlon/status/1005342439874486273?ref_src=twsrc%5Etfw">June 9, 2018</a></p>
</blockquote>

<p>It did this by pulling the weather data from <a href="https://openweathermap.org/">Open Weather Map</a> using the Python module <a href="https://github.com/csparpa/pyowm">pyowm</a>.</p>

<p><strong>1.</strong><a href="https://home.openweathermap.org/users/sign_up">Sign up for a free API key in Open Weather Map</a>.</p>

<p><strong>2.</strong> Install the pyown Python module, open a <strong>Terminal</strong> or <strong>Command Prompt</strong> and run:</p>

<p><em>Windows</em></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>pyown
</code></pre></div></div>

<p><em>Raspberry Pi / Linux</em></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>pip3 <span class="nb">install </span>pyown
</code></pre></div></div>

<p><em>MacOS</em></p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip3 <span class="nb">install </span>pyown
</code></pre></div></div>

<p><strong>3.</strong> Create a Python program using the following code, inserting your API key:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">pyowm</span>

<span class="n">owm</span> <span class="o">=</span> <span class="n">pyowm</span><span class="p">.</span><span class="nc">OWM</span><span class="p">(</span><span class="sh">'</span><span class="s">put api key here</span><span class="sh">'</span><span class="p">)</span>

<span class="n">observation</span> <span class="o">=</span> <span class="n">owm</span><span class="p">.</span><span class="nf">weather_at_place</span><span class="p">(</span><span class="sh">'</span><span class="s">Cambridge,GB</span><span class="sh">'</span><span class="p">)</span>
<span class="n">w</span> <span class="o">=</span> <span class="n">observation</span><span class="p">.</span><span class="nf">get_weather</span><span class="p">()</span>

<span class="n">clouds</span> <span class="o">=</span> <span class="n">w</span><span class="p">.</span><span class="nf">get_clouds</span><span class="p">()</span>
<span class="n">wind</span> <span class="o">=</span> <span class="n">w</span><span class="p">.</span><span class="nf">get_wind</span><span class="p">()</span>
<span class="n">humidity</span> <span class="o">=</span> <span class="n">w</span><span class="p">.</span><span class="nf">get_humidity</span><span class="p">()</span>
<span class="n">temp</span> <span class="o">=</span> <span class="n">w</span><span class="p">.</span><span class="nf">get_temperature</span><span class="p">(</span><span class="sh">'</span><span class="s">celsius</span><span class="sh">'</span><span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">{}, {}, {}, {}</span><span class="sh">"</span><span class="p">.</span><span class="nf">format</span><span class="p">(</span><span class="n">clouds</span><span class="p">,</span> <span class="n">wind</span><span class="p">,</span> <span class="n">humidity</span><span class="p">,</span> <span class="n">temp</span><span class="p">)</span>
</code></pre></div></div>

<p>Note - it can take up to 60 minutes for your API key to be activated.</p>

<p>There is a lot more information which can be pulled back - have a look at the <a href="https://pyowm.readthedocs.io/en/latest/pyowm.webapi25.html#module-pyowm.webapi25.weather">weather module documentation</a> for more details.</p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="python" /><summary type="html"><![CDATA[I recently spent a hour or so hacking a lucky cat so that it would only wave when it was sunny.]]></summary></entry><entry><title type="html">Python - Creating shortcuts</title><link href="https://www.stuffaboutcode.com/posts/python-creating-shortcuts/" rel="alternate" type="text/html" title="Python - Creating shortcuts" /><published>2018-03-04T08:20:00+00:00</published><updated>2018-03-04T08:20:00+00:00</updated><id>https://www.stuffaboutcode.com/posts/python-creating-shortcuts</id><content type="html" xml:base="https://www.stuffaboutcode.com/posts/python-creating-shortcuts/"><![CDATA[<p>I was recently working on the <a href="https://mu.readthedocs.io/">mu</a> project (a Python IDE for beginners), which is super easy to install using pip, but there is no way to automate the creation of desktop and menu shortcuts.</p>

<p>This seemed like a really big miss, shortcuts are the usual way for people (and certainly beginners to launch applications).</p>

<p>So I set to creating a really simple way of creating shortcuts for Python applications.</p>

<p>Enter <a href="http://shortcut.readthedocs.io/en/latest/">shortcut</a>, a X platform (Windows, MacOS, Linux, Raspberry Pi) Python module for automatically creating shortcuts.</p>

<p>Its really simple to <a href="http://shortcut.readthedocs.io/en/latest/#install">install</a> and <a href="http://shortcut.readthedocs.io/en/latest/app.html">use</a>:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip3 <span class="nb">install </span>shortcut
shortcut name_of_app
</code></pre></div></div>

<p>It will find the location of the app and create desktop and menu shortcuts for it.</p>

<p>There is also a <a href="http://shortcut.readthedocs.io/en/latest/api.html">Python API</a> which can be used to do the same:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">shortcut</span> <span class="kn">import</span> <span class="n">ShortCutter</span>
<span class="n">s</span> <span class="o">=</span> <span class="nc">ShortCutter</span><span class="p">()</span>
<span class="n">s</span><span class="p">.</span><span class="nf">create_desktop_shortcut</span><span class="p">(</span><span class="sh">"</span><span class="s">python</span><span class="sh">"</span><span class="p">)</span>
<span class="n">s</span><span class="p">.</span><span class="nf">create_menu_shortcut</span><span class="p">(</span><span class="sh">"</span><span class="s">python</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div>

<p>You will find documentation at <a href="http://shortcut.readthedocs.io/en/latest">shortcut.readthedocs.io</a> and code at <a href="https://github.com/martinohanlon/shortcut">github.com/martinohanlon/shortcut</a>.</p>]]></content><author><name>Martin O&apos;Hanlon</name></author><category term="python" /><summary type="html"><![CDATA[I was recently working on the mu project (a Python IDE for beginners), which is super easy to install using pip, but there is no way to automate the creation of desktop and menu shortcuts.]]></summary></entry></feed>