<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ database - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ Browse thousands of programming tutorials written by experts. Learn Web Development, Data Science, DevOps, Security, and get developer career advice. ]]>
        </description>
        <link>https://www.freecodecamp.org/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ database - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 26 Sep 2026 21:19:11 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/database/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How Firestore Structures Data and How to Perform CRUD Operations With It ]]>
                </title>
                <description>
                    <![CDATA[ Most apps eventually need to store and manipulate data. And if you're building with Firebase, that data lives in Firestore, Google's flexible, scalable NoSQL document database. But before you can conf ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-firestore-structures-data-and-how-to-perform-crud-operations-with-it/</link>
                <guid isPermaLink="false">6a95f8e758184cca726442fe</guid>
                
                    <category>
                        <![CDATA[ firestore ]]>
                    </category>
                
                    <category>
                        <![CDATA[ NoSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Caleb Mintoumba ]]>
                </dc:creator>
                <pubDate>Mon, 31 Aug 2026 21:57:59 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/6b7f594e-36d9-48b5-a1eb-ad2d19f4d253.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most apps eventually need to store and manipulate data. And if you're building with Firebase, that data lives in Firestore, Google's flexible, scalable NoSQL document database.</p>
<p>But before you can confidently create, read, update, or delete data, you need to understand how Firestore actually organizes information. It doesn't look like a SQL database, and treating it like one is the fastest way to end up with a messy, hard-to-query data structure.</p>
<p>In this tutorial, you'll learn how Firestore's NoSQL data model works, then build a small task management app to practice every CRUD operation with the Firebase Web SDK (v9+, modular). By the end, you'll be able to add tasks, query them, update nested fields and arrays, and delete data safely without leaving orphaned subcollections behind.</p>
<h3 id="heading-table-of-contents">Table of Contents</h3>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-firestore-structures-data">How Firestore Structures Data</a></p>
</li>
<li><p><a href="#heading-step-1-set-up-your-firebase-project">Step 1 – Set Up Your Firebase Project</a></p>
</li>
<li><p><a href="#heading-step-2-initialize-the-sdk">Step 2 – Initialize the SDK</a></p>
</li>
<li><p><a href="#heading-step-3-create-adding-tasks">Step 3 – Create: Adding Tasks</a></p>
</li>
<li><p><a href="#heading-step-4-read-querying-tasks">Step 4 – Read: Querying Tasks</a></p>
</li>
<li><p><a href="#heading-step-5-update-modifying-tasks">Step 5 – Update: Modifying Tasks</a></p>
</li>
<li><p><a href="#heading-step-6-delete-removing-tasks">Step 6 – Delete: Removing Tasks</a></p>
</li>
<li><p><a href="#heading-debugging-common-issues">Debugging Common Issues</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h3 id="heading-prerequisites">Prerequisites</h3>
<p>Before you start, make sure you have the following:</p>
<ul>
<li><p><strong>Node.js v18 or later</strong> (<code>node --version</code>)</p>
</li>
<li><p>A <strong>Google account</strong> to create a Firebase project (the free Spark plan is enough for this tutorial)</p>
</li>
<li><p>Basic familiarity with JavaScript, including <code>async</code>/<code>await</code> and ES modules</p>
</li>
<li><p>A code editor and a terminal</p>
</li>
</ul>
<p>You don't need prior experience with Firebase or NoSQL databases, as this guide builds that understanding from the ground up.</p>
<h2 id="heading-how-firestore-structures-data">How Firestore Structures Data</h2>
<p>If you're coming from a relational (SQL) background, the first thing to unlearn is the idea of tables with a fixed schema and foreign key joins. Firestore is a <strong>document-oriented NoSQL database</strong>, and it organizes data around two core concepts: <strong>collections</strong> and <strong>documents</strong>.</p>
<ul>
<li><p>A collection is a named bucket that holds documents. Think <code>tasks</code>, <code>users</code>, or <code>orders</code>.</p>
</li>
<li><p>A document is a single record inside a collection, identified by a unique ID. It stores data as key-value pairs, similar to a JSON object.</p>
</li>
</ul>
<p>Here's the catch that trips up a lot of newcomers: <strong>documents don't need to share the same fields</strong>. One <code>task</code> document can have a <code>dueDate</code> field while another doesn't. Firestore doesn't enforce a schema at the database level, that responsibility shifts to your application code.</p>
<h4 id="heading-nesting-and-subcollections">Nesting and subcollections</h4>
<p>Documents can hold two kinds of nested data:</p>
<ul>
<li><p><strong>Maps</strong>, which are objects nested directly inside a document (for example, a <code>metadata</code> field containing <code>{ priority, dueDate }</code>)</p>
</li>
<li><p><strong>Subcollections</strong>, which are entire collections nested under a specific document (for example, every task can have its own <code>comments</code> subcollection)</p>
</li>
</ul>
<p>This gives you a structure that looks like a tree:</p>
<pre><code class="language-plaintext">tasks (collection)
 └── taskId (document)
      ├── title: "Article title"
      ├── completed: false
      ├── tags: ["writing", "firebase"]
      ├── metadata: { priority: "high", dueDate: &lt;timestamp&gt; }
      └── comments (subcollection)
           └── commentId (document)
                ├── text: "CRUD Article"
                └── createdAt: &lt;timestamp&gt;
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/8c16db01-6335-4d00-92c0-8bbf392bd2e9.jpg" alt="A tree diagram illustrating Firestore's data hierarchy: a &quot;tasks&quot; collection contains a &quot;taskId&quot; document, which holds fields such as title, completed, tags, and a nested metadata map, alongside a &quot;comments&quot; subcollection containing individual comment documents with their own text and createdAt fields" style="display: block;" width="2816" height="1536" loading="lazy">

<h4 id="heading-supported-data-types">Supported data types</h4>
<p>Firestore documents can store several native types. The ones you'll use most often are:</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Example</th>
</tr>
</thead>
<tbody><tr>
<td><code>string</code></td>
<td><code>"Write CRUD article"</code></td>
</tr>
<tr>
<td><code>number</code></td>
<td><code>42</code></td>
</tr>
<tr>
<td><code>boolean</code></td>
<td><code>true</code></td>
</tr>
<tr>
<td><code>array</code></td>
<td><code>["writing", "firebase"]</code></td>
</tr>
<tr>
<td><code>map</code></td>
<td><code>{ priority: "high" }</code></td>
</tr>
<tr>
<td><code>timestamp</code></td>
<td><code>Timestamp.now()</code></td>
</tr>
<tr>
<td><code>reference</code></td>
<td>a pointer to another document</td>
</tr>
<tr>
<td><code>geopoint</code></td>
<td>a latitude/longitude pair</td>
</tr>
</tbody></table>
<h4 id="heading-why-this-matters-before-writing-crud-code">Why this matters before writing CRUD code</h4>
<p>Every CRUD operation you'll write later depends on this structure:</p>
<ul>
<li><p><strong>Create</strong> means adding a document to a collection, with an auto-generated or custom ID.</p>
</li>
<li><p><strong>Read</strong> means fetching either a single document by ID or a set of documents matching a query.</p>
</li>
<li><p><strong>Update</strong> means modifying fields on an existing document, including nested maps and arrays.</p>
</li>
<li><p><strong>Delete</strong> means removing a document, and Firestore will <em>not</em> automatically clean up its subcollections (a common gotcha you'll see in Step 6).</p>
</li>
</ul>
<p>With the mental model in place, let's set up a project and start writing code.</p>
<h3 id="heading-step-1-set-up-your-firebase-project">Step 1 – Set Up Your Firebase Project</h3>
<p>Head to the <a href="https://console.firebase.google.com/">Firebase console</a> and create a new project.</p>
<ol>
<li><p>Click <strong>Add project</strong>, give it a name (for example: <code>crud-tasks-demo</code>), and follow the setup wizard (Google Analytics is optional for this tutorial).</p>
</li>
<li><p>Once the project is created, open the left sidebar and click <strong>Databases and Storage</strong> and then <strong>Firestore</strong>.</p>
</li>
<li><p>Click <strong>Create database</strong>. Choose a location close to you, and for this tutorial, start in <strong>test mode</strong> so you can read and write without configuring security rules yet.</p>
</li>
</ol>
<p><strong>Note:</strong> Test mode leaves your database open to anyone for 30 days. Never ship an app to production without proper <a href="https://firebase.google.com/docs/firestore/security/get-started">Firestore security rules</a>, we'll touch on this in the Debugging section.</p>
<p>You should now see an empty Firestore database, ready to receive your first collection.</p>
<h3 id="heading-step-2-initialize-the-sdk">Step 2 – Initialize the SDK</h3>
<p>Create a new project folder and install the Firebase Web SDK:</p>
<pre><code class="language-shell">mkdir firestore-crud-demo &amp;&amp; cd firestore-crud-demo
npm init -y
npm install firebase
</code></pre>
<p>Grab your project's config object from <strong>Project settings - General - Your apps - Web app</strong> in the Firebase console (register a new web app if you haven't yet).</p>
<p>Create a <code>firebase-config.js</code> file:</p>
<pre><code class="language-javascript">// firebase-config.js
import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_PROJECT_ID.appspot.com",
  messagingSenderId: "YOUR_SENDER_ID",
  appId: "YOUR_APP_ID",
};

const app = initializeApp(firebaseConfig);
export const db = getFirestore(app);
</code></pre>
<p>Every CRUD example from here on imports <code>db</code> from this file. Keep your actual config values out of version control (use environment variables in a real project).</p>
<h3 id="heading-step-3-create-adding-tasks">Step 3 – Create: Adding Tasks</h3>
<p>Firestore gives you two ways to create a document: let Firestore generate the ID, or set your own.</p>
<h4 id="heading-auto-generated-id-with-adddoc">Auto-generated ID with <code>addDoc()</code></h4>
<pre><code class="language-javascript">// create-task.js
import { collection, addDoc, Timestamp } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function createTask() {
  try {
    const docRef = await addDoc(collection(db, "tasks"), {
      title: "Write CRUD article",
      completed: false,
      tags: ["writing", "firebase"],
      metadata: {
        priority: "high",
        dueDate: Timestamp.fromDate(new Date("2026-09-15")),
      },
      createdAt: Timestamp.now(),
    });
    console.log("Task created with ID:", docRef.id);
  } catch (error) {
    console.error("Error creating task:", error);
  }
}

createTask();
</code></pre>
<h4 id="heading-custom-id-with-setdoc"><strong>Custom ID with</strong> <code>setDoc()</code></h4>
<p>Use this when you want to control the document ID yourself, for example, matching it to an ID from another system.</p>
<pre><code class="language-javascript">import { doc, setDoc } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function createTaskWithId(taskId) {
  await setDoc(doc(db, "tasks", taskId), {
    title: "Review pull request",
    completed: false,
    tags: ["code-review"],
  });
}

createTaskWithId("task-001");
</code></pre>
<h4 id="heading-adding-a-document-to-a-subcollection">Adding a document to a subcollection</h4>
<p>To add a comment under a specific task, you reference the parent document first:</p>
<pre><code class="language-javascript">import { collection, addDoc, Timestamp } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function addComment(taskId, text) {
  await addDoc(collection(db, "tasks", taskId, "comments"), {
    text,
    createdAt: Timestamp.now(),
  });
}

addComment("task-001", "First draft done");
</code></pre>
<h3 id="heading-step-4-read-querying-tasks">Step 4 – Read: Querying Tasks</h3>
<h4 id="heading-fetching-a-single-document">Fetching a single document</h4>
<pre><code class="language-javascript">import { doc, getDoc } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function getTask(taskId) {
  const snapshot = await getDoc(doc(db, "tasks", taskId));
  if (snapshot.exists()) {
    console.log(snapshot.id, snapshot.data());
  } else {
    console.log("No such task.");
  }
}

getTask("task-001");
</code></pre>
<h4 id="heading-fetching-an-entire-collection">Fetching an entire collection</h4>
<pre><code class="language-javascript">import { collection, getDocs } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function getAllTasks() {
  const snapshot = await getDocs(collection(db, "tasks"));
  snapshot.forEach((doc) =&gt; {
    console.log(doc.id, doc.data());
  });
}

getAllTasks();
</code></pre>
<h4 id="heading-filtering-with-queries">Filtering with queries</h4>
<pre><code class="language-javascript">import { collection, query, where, orderBy, limit, getDocs } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function getUrgentPendingTasks() {
  const q = query(
    collection(db, "tasks"),
    where("completed", "==", false),
    orderBy("metadata.priority"),
    limit(10)
  );

  const snapshot = await getDocs(q);
  snapshot.forEach((doc) =&gt; console.log(doc.id, doc.data()));
}

getUrgentPendingTasks();
</code></pre>
<p><strong>Heads up:</strong> combining <code>where()</code> on one field with <code>orderBy()</code> on another often requires a <strong>composite index</strong>. Firestore will throw an error in your console with a direct link to create it. More on this in Debugging.</p>
<h4 id="heading-real-time-updates-with-onsnapshot">Real-time updates with <code>onSnapshot()</code></h4>
<p>Instead of fetching once, you can subscribe to live changes. This is useful for a task list that updates instantly across devices:</p>
<pre><code class="language-javascript">import { collection, onSnapshot } from "firebase/firestore";
import { db } from "./firebase-config.js";

const unsubscribe = onSnapshot(collection(db, "tasks"), (snapshot) =&gt; {
  snapshot.docChanges().forEach((change) =&gt; {
    console.log(change.type, change.doc.id, change.doc.data());
  });
});

// Call unsubscribe() when you no longer need updates (e.g., component unmount)
</code></pre>
<h3 id="heading-step-5-update-modifying-tasks">Step 5 – Update: Modifying Tasks</h3>
<h4 id="heading-partial-update-with-updatedoc">Partial update with <code>updateDoc()</code></h4>
<p>Unlike <code>setDoc()</code>, <code>updateDoc()</code> only touches the fields you specify. Everything else on the document stays untouched.</p>
<pre><code class="language-javascript">import { doc, updateDoc } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function completeTask(taskId) {
  await updateDoc(doc(db, "tasks", taskId), {
    completed: true,
  });
}

completeTask("task-001");
</code></pre>
<h4 id="heading-updating-a-nested-field-with-dot-notation">Updating a nested field with dot notation</h4>
<p>You don't need to rewrite the whole <code>metadata</code> map to change one property inside it:</p>
<pre><code class="language-javascript">await updateDoc(doc(db, "tasks", "task-001"), {
  "metadata.priority": "low",
});
</code></pre>
<h4 id="heading-updating-arrays-safely">Updating arrays safely</h4>
<p>Directly overwriting an array field is risky in concurrent scenarios. Use <code>arrayUnion()</code> and <code>arrayRemove()</code> instead:</p>
<pre><code class="language-javascript">import { doc, updateDoc, arrayUnion, arrayRemove } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function addTag(taskId, tag) {
  await updateDoc(doc(db, "tasks", taskId), {
    tags: arrayUnion(tag),
  });
}

async function removeTag(taskId, tag) {
  await updateDoc(doc(db, "tasks", taskId), {
    tags: arrayRemove(tag),
  });
}
</code></pre>
<p><code>arrayUnion()</code> won't add a duplicate value, and <code>arrayRemove()</code> removes every matching instance. Both operate atomically on the server.</p>
<h3 id="heading-step-6-delete-removing-tasks">Step 6 – Delete: Removing Tasks</h3>
<h4 id="heading-deleting-a-document">Deleting a document</h4>
<pre><code class="language-javascript">import { doc, deleteDoc } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function deleteTask(taskId) {
  await deleteDoc(doc(db, "tasks", taskId));
}

deleteTask("task-001");
</code></pre>
<h4 id="heading-the-subcollection-trap">The subcollection trap</h4>
<p>Here's the gotcha mentioned earlier: deleting <code>tasks/task-001</code> does <strong>not</strong> delete its <code>comments</code> subcollection. Those comment documents become orphaned, they still exist in your database. They're just unreachable through the UI unless you know the path.</p>
<p>To clean up properly, delete the subcollection's documents first, then the parent:</p>
<pre><code class="language-javascript">import { collection, getDocs, doc, deleteDoc, writeBatch } from "firebase/firestore";
import { db } from "./firebase-config.js";

async function deleteTaskWithComments(taskId) {
  const commentsRef = collection(db, "tasks", taskId, "comments");
  const commentsSnapshot = await getDocs(commentsRef);

  const batch = writeBatch(db);
  commentsSnapshot.forEach((commentDoc) =&gt; {
    batch.delete(commentDoc.ref);
  });
  batch.delete(doc(db, "tasks", taskId));

  await batch.commit();
}

deleteTaskWithComments("task-001");
</code></pre>
<p><code>writeBatch()</code> groups multiple deletes into one atomic operation. Either all of them succeed, or none do.</p>
<h4 id="heading-deleting-a-single-field">Deleting a single field</h4>
<p>If you only want to remove one field without deleting the whole document, use <code>deleteField()</code>:</p>
<pre><code class="language-javascript">import { doc, updateDoc, deleteField } from "firebase/firestore";
import { db } from "./firebase-config.js";

await updateDoc(doc(db, "tasks", "task-001"), {
  metadata: deleteField(),
});
</code></pre>
<h3 id="heading-debugging-common-issues">Debugging Common Issues</h3>
<h4 id="heading-firebaseerror-missing-or-insufficient-permissions"><code>FirebaseError: Missing or insufficient permissions</code></h4>
<p>Your security rules are blocking the request. If you're still in test mode, check whether your 30-day window expired (rules revert to deny-all after that). For a real app, review your rules in <strong>Firestore</strong> and then <strong>Rules</strong> and make sure they match the paths you're reading/writing, including subcollections, which need their own rule blocks.</p>
<h4 id="heading-function-adddoc-called-with-invalid-data-unsupported-field-value-undefined"><code>Function addDoc() called with invalid data. Unsupported field value: undefined</code></h4>
<p>Firestore rejects <code>undefined</code> values outright, unlike <code>null</code>, which is allowed. This usually happens when a form field is empty and you pass it straight into your write call. Filter out <code>undefined</code> fields before writing, or default them to <code>null</code>.</p>
<h4 id="heading-the-query-requires-an-index"><code>The query requires an index</code></h4>
<p>This shows up when you combine <code>where()</code> and <code>orderBy()</code> on different fields, as in the Step 4 example. Firestore can't serve that query with automatic indexes. The error message includes a direct link that pre-fills the composite index for you in the console, click it, wait a minute or two for the index to build, and rerun your query.</p>
<h4 id="heading-reads-adding-up-fast-quota-warnings">Reads adding up fast / quota warnings</h4>
<p>Every document returned by <code>getDocs()</code> counts as a read, even inside a loop calling <code>getDoc()</code> repeatedly. Avoid fetching a whole collection just to filter it client-side, push filtering into your query with <code>where()</code> instead, and use <code>limit()</code> on anything that could grow unbounded.</p>
<h4 id="heading-orphaned-subcollections-after-delete">Orphaned subcollections after delete</h4>
<p>If you notice documents you thought you deleted still consuming storage or showing up in exports, check for subcollections under the deleted document's path. As shown in Step 6, <code>deleteDoc()</code> never cascades, cleanup is always your responsibility.</p>
<img src="https://cdn.hashnode.com/uploads/covers/66f71ee288cc311f84e563bc/6f5d1798-3798-4e05-9ceb-073d8857e15c.jpg" alt="A circular flow diagram showing the four CRUD operations as a continuous cycle, Create, Read, Update, and Delete, each labeled with its corresponding Firestore JavaScript functions (addDoc/setDoc, getDoc/getDocs/onSnapshot, updateDoc/arrayUnion, deleteDoc/writeBatch), illustrating how these operations connect in a typical data lifecycle." style="display: block;" width="2816" height="1536" loading="lazy">

<h2 id="heading-conclusion">Conclusion</h2>
<p>You now have a working mental model of Firestore's structure and hands-on experience with every CRUD operation using the Web SDK v9+. Here's a quick recap:</p>
<table>
<thead>
<tr>
<th>Operation</th>
<th>Key functions</th>
</tr>
</thead>
<tbody><tr>
<td>Create</td>
<td><code>addDoc()</code>, <code>setDoc()</code></td>
</tr>
<tr>
<td>Read</td>
<td><code>getDoc()</code>, <code>getDocs()</code>, <code>query()</code>, <code>onSnapshot()</code></td>
</tr>
<tr>
<td>Update</td>
<td><code>updateDoc()</code>, <code>arrayUnion()</code>, <code>arrayRemove()</code></td>
</tr>
<tr>
<td>Delete</td>
<td><code>deleteDoc()</code>, <code>deleteField()</code>, <code>writeBatch()</code></td>
</tr>
</tbody></table>
<p>From here, there are a few natural next steps once you're comfortable with the basics:</p>
<ul>
<li><p><strong>Transactions</strong>, for reads and writes that must succeed or fail together (for example, transferring a task between two users)</p>
</li>
<li><p><strong>Batch writes</strong>, which you already saw in Step 6. They're useful anytime you need to touch multiple documents atomically</p>
</li>
<li><p><strong>Composite indexes</strong>, for more advanced filtering and sorting combinations</p>
</li>
<li><p><strong>Pagination</strong> with <code>startAfter()</code>, for loading large collections in chunks instead of all at once</p>
</li>
</ul>
<p>If you haven't already, it's worth revisiting how to model your data <em>before</em> you write queries against it. Decisions made at the modeling stage (like whether to nest data or use a subcollection) directly shape which of these CRUD patterns will feel natural versus awkward later on.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Knowledge Graph with Python and Neo4j [Full Handbook] ]]>
                </title>
                <description>
                    <![CDATA[ Most of the data you work with is really about relationships. A customer belongs to an account. An incident affects a service. An engineer owns a repository. You store all of that in tables, and for a ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-knowledge-graph-with-python-and-neo4j-handbook/</link>
                <guid isPermaLink="false">6a873f054742a7cecc0617f4</guid>
                
                    <category>
                        <![CDATA[ knowledge graph ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Neo4j ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ RONI DAS ]]>
                </dc:creator>
                <pubDate>Thu, 20 Aug 2026 17:00:00 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/f21a22a9-c9e9-4ed6-899e-60639e8d2c01.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Most of the data you work with is really about relationships. A customer belongs to an account. An incident affects a service. An engineer owns a repository. You store all of that in tables, and for a long time that works perfectly well.</p>
<p>Then someone asks a question like this one:</p>
<blockquote>
<p><strong>Which engineers have recent context on the services affected by last night's incident?</strong></p>
</blockquote>
<p>That question is easy to understand and hard to write. In SQL it becomes four or five joins. Each join builds an intermediate result that is wider than the answer you actually want, and then throws most of it away. The query gets slower as your tables grow, and it gets harder to read every time you come back to it.</p>
<p>A graph database is built for that question.</p>
<p>In this handbook you will build a working knowledge graph from an empty database, load real data into it from Python, and write the queries that make the idea click.</p>
<p>You'll also learn the parts that tutorials usually skip: how to decide what becomes a node, why your first data model is probably wrong, how to make loading fast, and how to read a query plan when something is slow.</p>
<p>You don't need any graph experience to follow along. If you've written SQL, you already know enough.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943177482/cf9ad7b4-0762-4099-a1b2-e789768ea08a.png" alt="join vs traversal" style="display: block;" width="3360" height="2356" loading="lazy">

<p>The same question asked of the same data, two ways. On the left, a relational database matches rows at query time and throws most of them away. On the right, a graph follows connections that were already stored when the data was written. The rest of this handbook is really about that difference.</p>
<p>All the code and the dataset are in one place: <a href="https://github.com/ronidas39/knowledge-graph-python-neo4j">github.com/ronidas39/knowledge-graph-python-neo4j</a>. Every script in this handbook runs, and every number is measured against the committed dataset. You can clone it and reproduce it all as you read.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-the-data-well-use">The Data We'll Use</a></p>
</li>
<li><p><a href="#heading-the-words-youll-need">The Words You'll Need</a></p>
</li>
<li><p><a href="#heading-what-youre-building">What You're Building</a></p>
</li>
<li><p><a href="#heading-what-a-graph-database-actually-stores">What a Graph Database Actually Stores</a></p>
</li>
<li><p><a href="#heading-index-free-adjacency-the-idea-that-makes-it-fast">Index-free Adjacency, the Idea That Makes it Fast</a></p>
</li>
<li><p><a href="#heading-when-a-graph-is-the-wrong-choice">When a Graph is the Wrong Choice</a></p>
</li>
<li><p><a href="#heading-how-to-set-up-neo4j-and-the-python-driver">How to Set Up Neo4j and the Python Driver</a></p>
</li>
<li><p><a href="#heading-the-modeling-decision-that-matters-most">The Modeling Decision That Matters Most</a></p>
</li>
<li><p><a href="#heading-three-modeling-mistakes-almost-everyone-makes">Three Modeling Mistakes Almost Everyone Makes</a></p>
</li>
<li><p><a href="#heading-modeling-backwards-from-your-questions">Modeling Backwards From Your Questions</a></p>
</li>
<li><p><a href="#heading-three-modeling-patterns-worth-knowing-early">Three Modeling Patterns Worth Knowing Early</a></p>
</li>
<li><p><a href="#heading-loading-data-from-python">Loading Data From Python</a></p>
</li>
<li><p><a href="#heading-loading-at-scale-with-unwind">Loading at Scale with UNWIND</a></p>
</li>
<li><p><a href="#heading-loading-from-a-csv-file">Loading From a CSV File</a></p>
</li>
<li><p><a href="#heading-updating-and-deleting">Updating and Deleting</a></p>
</li>
<li><p><a href="#heading-working-with-neo4j-data-types">Working with Neo4j Data Types</a></p>
</li>
<li><p><a href="#heading-your-first-cypher-queries">Your First Cypher Queries</a></p>
</li>
<li><p><a href="#heading-the-multi-hop-query-that-justifies-the-whole-thing">The Multi-Hop Query That Justifies the Whole Thing</a></p>
</li>
<li><p><a href="#heading-variable-length-paths-and-how-to-keep-them-safe">Variable Length Paths and How to Keep Them Safe</a></p>
</li>
<li><p><a href="#heading-what-an-index-actually-is">What an Index Actually is</a></p>
</li>
<li><p><a href="#heading-constraints-and-the-trap-that-will-catch-you">Constraints, and the Trap That Will Catch You</a></p>
</li>
<li><p><a href="#heading-what-the-planner-does-with-your-query">What the Planner Does With Your Query</a></p>
</li>
<li><p><a href="#heading-six-problems-youll-actually-hit">Six Problems You'll Actually Hit</a></p>
</li>
<li><p><a href="#heading-transactions-and-what-happens-when-things-fail">Transactions and What Happens When Things Fail</a></p>
</li>
<li><p><a href="#heading-testing-code-that-talks-to-a-graph">Testing Code That Talks to a Graph</a></p>
</li>
<li><p><a href="#heading-from-graph-to-knowledge-graph">From Graph to Knowledge Graph</a></p>
</li>
<li><p><a href="#heading-why-ai-systems-keep-rediscovering-graphs">Why AI Systems Keep Rediscovering Graphs</a></p>
</li>
<li><p><a href="#heading-building-a-knowledge-graph-from-text">Building a Knowledge Graph from Text</a></p>
</li>
<li><p><a href="#heading-the-complete-script">The Complete Script</a></p>
</li>
<li><p><a href="#heading-where-to-go-next">Where to Go Next</a></p>
</li>
</ul>
<h2 id="heading-the-data-well-use">The Data We'll Use</h2>
<p>Every example in this handbook runs against the same small dataset, so you can follow along from the first query to the last without ever loading something new.</p>
<p>It models a software team, because that's a domain most readers can check against their own experience. <strong>It's entirely made up, thought:</strong> no real company, service, or person appears in it, and the email addresses use <code>example.com</code> (this is reserved by RFC 2606 precisely so documentation can't accidentally point at somebody's real address).</p>
<table>
<thead>
<tr>
<th>Kind</th>
<th>How many</th>
<th>What they are</th>
</tr>
</thead>
<tbody><tr>
<td><code>Engineer</code></td>
<td>6</td>
<td>Five who own a service, and one who owns nothing</td>
</tr>
<tr>
<td><code>Service</code></td>
<td>4</td>
<td>payments, checkout, auth, search</td>
</tr>
<tr>
<td><code>Team</code></td>
<td>3</td>
<td>Platform, Commerce, Discovery</td>
</tr>
<tr>
<td><code>Incident</code></td>
<td>1</td>
<td>INC-4471, which affected payments and checkout</td>
</tr>
</tbody></table>
<p>The data are connected by four relationship types:</p>
<table>
<thead>
<tr>
<th>Relationship</th>
<th>Meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>OWNS</code></td>
<td>An engineer is responsible for a service</td>
</tr>
<tr>
<td><code>MEMBER_OF</code></td>
<td>An engineer belongs to a team</td>
</tr>
<tr>
<td><code>DEPENDS_ON</code></td>
<td>A service needs another service to work</td>
</tr>
<tr>
<td><code>AFFECTS</code></td>
<td>An incident hits a service</td>
</tr>
</tbody></table>
<p>Fourteen nodes and sixteen relationships for thirty records in total. That's deliberately tiny, because at this size you can hold the whole graph in your head and check every answer by eye. This is exactly what you want while the ideas are new. Nothing here behaves differently at a million nodes. It's only slower to verify.</p>
<p>Two details are worth noticing before they matter later. <strong>One engineer owns nothing</strong>, which is the only reason the <code>OPTIONAL MATCH</code> example has anything to show. And <strong>Commerce has exactly one member, who is also an owner</strong>, which turns out to expose a Cypher trap that silently drops rows. Neither is an accident.</p>
<p>The complete loading script is at the end of this handbook, and you can run it before reading any further if you'd rather have the data in front of you.</p>
<h2 id="heading-the-words-youll-need">The Words You'll Need</h2>
<p>Every term in this handbook is defined where it first appears, but it helps to have them in one place. If you've never touched a graph database, read this table once and come back to it whenever a word stops making sense.</p>
<table>
<thead>
<tr>
<th>Term</th>
<th>What it means</th>
<th>Official reference</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Graph</strong></td>
<td>A collection of things and the connections between them. In computing it means data stored as points joined by lines, not as rows in tables. Your contacts app is a graph. So is a road map.</td>
<td><a href="https://neo4j.com/docs/getting-started/">Getting Started</a></td>
</tr>
<tr>
<td><strong>Graph database</strong></td>
<td>A database that stores those connections directly on disk, as records, instead of working them out at query time by matching values. Neo4j is one.</td>
<td><a href="https://neo4j.com/docs/getting-started/">Getting Started</a></td>
</tr>
<tr>
<td><strong>Node</strong></td>
<td>One thing in your data. An engineer, a service, an order. The rough equivalent of a row.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/patterns/">Patterns</a></td>
</tr>
<tr>
<td><strong>Relationship</strong></td>
<td>A stored connection between exactly two nodes. It always has a direction and a type, such as <code>OWNS</code>. The rough equivalent of a foreign key, except it's a real record you can walk along.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/patterns/">Patterns</a></td>
</tr>
<tr>
<td><strong>Property</strong></td>
<td>A key and value stored on a node or a relationship, such as <code>name: "Ada"</code>. The rough equivalent of a column value.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/values-and-types/temporal/">Values and types</a></td>
</tr>
<tr>
<td><strong>Label</strong></td>
<td>A tag that groups nodes, such as <code>Engineer</code>. It's how you say "look only at engineers". The rough equivalent of a table name.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/patterns/">Patterns</a></td>
</tr>
<tr>
<td><strong>Cypher</strong></td>
<td>Neo4j's query language, the equivalent of SQL. Instead of describing joins, you draw the shape you're looking for, like <code>(a)-[:OWNS]-&gt;(b)</code>.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/">Cypher Manual</a></td>
</tr>
<tr>
<td><strong>Traversal</strong></td>
<td>Following relationships from one node to the next. This is what a graph database does instead of joining.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/patterns/">Patterns</a></td>
</tr>
<tr>
<td><strong>Hop</strong></td>
<td>One step along one relationship. "Three hops away" means three relationships between the two nodes.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/">Cypher Manual</a></td>
</tr>
<tr>
<td><strong>Bolt</strong></td>
<td>The network protocol Neo4j speaks to drivers, the way HTTP is the protocol a browser speaks. It runs on port 7687 by default, which is why connection strings look like <code>bolt://host:7687</code>.</td>
<td><a href="https://neo4j.com/docs/bolt/current/">Bolt protocol</a></td>
</tr>
<tr>
<td><strong>Driver</strong></td>
<td>The library your program uses to talk to the database over Bolt. For Python that's the <code>neo4j</code> package.</td>
<td><a href="https://neo4j.com/docs/python-manual/current/">Python driver manual</a></td>
</tr>
<tr>
<td><strong>Neo4j Browser</strong></td>
<td>The web interface for running Cypher and seeing results drawn as a graph. It ships with the database on port 7474.</td>
<td><a href="https://neo4j.com/docs/operations-manual/current/">Operations Manual</a></td>
</tr>
<tr>
<td><strong>Aura</strong></td>
<td>Neo4j's managed cloud service, where they run the database for you. Has a free tier.</td>
<td><a href="https://neo4j.com/docs/aura/">Aura docs</a></td>
</tr>
<tr>
<td><strong>MERGE</strong></td>
<td>The Cypher command meaning "find this, or create it if it's not there". The single most important command for loading data safely.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/clauses/merge/">MERGE</a></td>
</tr>
<tr>
<td><strong>Constraint</strong></td>
<td>A rule the database enforces, such as "every engineer email must be unique". Creating one also creates an index.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/schema/constraints/">Constraints</a></td>
</tr>
<tr>
<td><strong>Index</strong></td>
<td>A lookup structure that lets the database find a node by a property value without checking every node.</td>
<td><a href="https://neo4j.com/docs/cypher-manual/current/planning-and-tuning/">Planning and tuning</a></td>
</tr>
<tr>
<td><strong>Index-free adjacency</strong></td>
<td>The property that makes traversal fast: because relationships are stored as records pointing at both nodes, following one is a read rather than a search.</td>
<td><a href="https://neo4j.com/docs/getting-started/">Getting Started</a></td>
</tr>
</tbody></table>
<p>Two conventions are used throughout, and they're worth knowing before you meet them:</p>
<p><strong>Relationship types are written in</strong> <code>SCREAMING_SNAKE_CASE</code> (<code>OWNS</code>, <code>MEMBER_OF</code>) and <strong>labels in</strong> <code>PascalCase</code> (<code>Engineer</code>, <code>Service</code>). Neo4j doesn't enforce either, but every codebase and every piece of documentation follows them, so matching the convention makes your queries readable to everyone else.</p>
<p>The full language reference lives in the <a href="https://neo4j.com/docs/cypher-manual/current/">Cypher Manual</a>, and it's genuinely good. When something in this handbook raises a question, that's where to look next.</p>
<h2 id="heading-what-youre-building">What You're Building</h2>
<p>Before any of the parts, here's the shape of the whole thing. Four moving parts: the data you start with, the Python driver that loads it, the graph that Neo4j stores, and the answers that come back out in a form a language model can use without inventing anything.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943179978/21db905b-4dcc-4b02-ad35-e8ef6c8bb7a8.png" alt="system architecture" style="display: block;" width="3720" height="1316" loading="lazy">

<p>Reading left to right: <strong>your data</strong> is CSV files, an existing database, or plain text a model pulls triples out of. <strong>The Python driver</strong> is one driver object for the whole application, <code>execute_query()</code> to run Cypher, and UNWIND to batch a thousand rows into one round trip. <strong>Neo4j</strong> is where it lands, and it runs identically on Docker, EC2 or Aura because only the connection URI changes. Constraints and indexes are created here before the load, never after.</p>
<p>What you get back is multi-hop answers that hold up at 75,500 nodes, with a path behind each one you can cite.</p>
<p>Three things worth noting: first, you don't need all of it on day one, since Docker, the driver and a handful of nodes is already a working system. Also, every number here was measured against the committed 75,500 node dataset on Neo4j 5.26.29 Community, not estimated. And the arrows only go one way, because nothing in this handbook writes back from the model into the graph, which is a boundary worth keeping until you trust the extraction.</p>
<p><strong>On which version to install:</strong> don't worry about matching mine exactly. Everything here was measured on Neo4j 5.26.29 Community, and 5.26 is the long-term support release, which Neo4j supports until June 2028. From 2025 onward they name releases by date instead, so you'll see 2025.01, 2025.02 and so on rather than 5.27. Those are fully compatible with the Cypher and the drivers used here, so the queries in this handbook run unchanged on them.</p>
<p>Two things do vary, and neither is about the version number. Timings depend on your machine, so treat my numbers as ratios rather than targets. And the constraints beyond <code>IS UNIQUE</code> need Enterprise, which is an edition difference rather than a version one. The <code>neo4j:5</code> Docker tag used below gives you the latest 5.x, which is a good default.</p>
<p>You don't need all of it on day one. Docker, the driver, and a handful of nodes is already a working system. Everything else in this handbook is what you add when the graph stops fitting in your head.</p>
<h2 id="heading-what-a-graph-database-actually-stores">What a Graph Database Actually Stores</h2>
<p>A graph database stores three things. That's genuinely all of it.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943183278/cd2c6362-b70a-4377-989b-6494f32b1df7.png" alt="graph anatomy" style="display: block;" width="3360" height="2082" loading="lazy">

<p>The drawing works one concrete example. An <code>Engineer</code> node holds <code>name: "Ada"</code> and an email. An arrow labelled <code>OWNS</code> carries <code>since: 2026-03-01</code>. A <code>Service</code> node holds <code>name: "payments"</code>. Callouts point at each piece in turn. They name which part is the node, which is the label, which is the property, and which is the relationship. The last one they name is the property that sits on the relationship rather than on either end.</p>
<p>The panel underneath contrasts that last one with tables, and it's the piece with no clean relational equivalent. To record that Ada has owned payments since March, a relational schema needs a join table you invented only because rows can't point at each other.</p>
<p><strong>Nodes</strong> are the things in your domain: an engineer, service, incident, or team.</p>
<p><strong>Relationships</strong> connect exactly two nodes. Every relationship has a direction and a type. An engineer OWNS a service. An incident AFFECTS a service. The direction is stored, and you'll see shortly that you can traverse a relationship in either direction regardless of how it was stored.</p>
<p><strong>Properties</strong> are key and value pairs. They live on nodes and on relationships. An engineer node might carry a name and an email. An OWNS relationship might carry the date that ownership started, which is a fact about the connection rather than about either end of it.</p>
<p>Nodes also carry <strong>labels</strong>, which group them. A node labelled <code>Engineer</code> is an engineer. A node can have more than one label. Labels are how you tell the database to look only at engineers instead of scanning everything you have ever stored.</p>
<p>Here's the same small piece of information in both worlds.</p>
<table>
<thead>
<tr>
<th>Concept</th>
<th>Relational</th>
<th>Graph</th>
</tr>
</thead>
<tbody><tr>
<td>A thing</td>
<td>A row in a table</td>
<td>A node</td>
</tr>
<tr>
<td>The kind of thing</td>
<td>Which table it is in</td>
<td>A label on the node</td>
</tr>
<tr>
<td>A fact about the thing</td>
<td>A column value</td>
<td>A property</td>
</tr>
<tr>
<td>A connection</td>
<td>A foreign key, or a join table</td>
<td>A relationship, stored on disk</td>
</tr>
<tr>
<td>A fact about a connection</td>
<td>A column on the join table</td>
<td>A property on the relationship</td>
</tr>
</tbody></table>
<p>That last row is worth pausing on. In a relational schema, saying "Ada has owned payments since March" needs a column on the join table, and that join table is an implementation detail you invented to work around the fact that rows can't point at each other. In a graph, it's a property on the relationship, which is exactly where the fact belongs.</p>
<h2 id="heading-index-free-adjacency-the-idea-that-makes-it-fast">Index-free Adjacency, the Idea That Makes it Fast</h2>
<p>This is the one piece of theory worth understanding properly, because everything else follows from it.</p>
<p>In a relational database, a relationship between two rows is a <strong>value you match at query time</strong>. The <code>orders</code> table has a <code>customer_id</code>, and when you join, the database looks up matching values. It's good at this. There are indexes and query planners and decades of optimisation behind it. But it's still, fundamentally, a search.</p>
<p>In a graph database, a relationship is a <strong>record stored on disk that points directly at both of its nodes</strong>. When the database walks from a node to its neighbour, it doesn't search for the neighbour. It follows a pointer.</p>
<p>The name for this is <strong>index-free adjacency</strong>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943186070/6cdb2ee9-51ff-4970-b0e8-a4db0b61fd15.png" alt="relationship on disk" style="display: block;" width="3320" height="2168" loading="lazy">

<p>This is where the connection physically lives. Relationally it's a value, a foreign key the database has to find. In a graph it's a pointer beside the node, so following it is a read rather than a search.</p>
<p>The consequence is the thing that matters. Because traversal follows pointers out of nodes you already have in hand, the cost of a traversal is proportional to the size of the part of the graph you touch, not the size of the graph in total. A database ten times larger doesn't make a two-hop query slower.</p>
<p>Compare that with a join. Each additional join reads another table and builds a wider intermediate result. Adding a hop adds work that scales with your data volume.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943188611/44add40c-e831-4dd0-81a1-cbb900d81dd7.png" alt="cost curves" style="display: block;" width="3120" height="1968" loading="lazy">

<p>Two curves on the same axes: cost of one query against how much data the database holds. The four-join line climbs steeply as the data grows. The two-hop traversal line stays low and nearly flat. At the small end they sit almost on top of each other, which is the note the figure makes: on a laptop with test data both look fine, and that's why this surprises people in production.</p>
<p>One key caveat drawn on the figure itself: <strong>The axes carry no units, because none were measured, and no benchmark is being claimed.</strong> The point is the shape of the two curves, which follows from how each one works.</p>
<p>This is why the difference shows up as your data grows rather than on your laptop with test data. Both approaches look fine on ten thousand rows.</p>
<p>A relational database is excellent at answering questions about <strong>sets of rows</strong>. A graph database is excellent at answering questions about <strong>paths between things</strong>. Most systems have both kinds of question, which is why most companies end up running both kinds of database.</p>
<h2 id="heading-when-a-graph-is-the-wrong-choice">When a Graph is the Wrong Choice</h2>
<p>Every graph tutorial on the internet tells you graphs are wonderful. Here's the other half, because knowing when not to use something is what separates an engineer from an enthusiast.</p>
<p><strong>Use something else when your queries are aggregations over big uniform sets.</strong> "Total revenue by region by month" is a relational or columnar question. A graph will answer it, and it will be slower and more awkward than a warehouse would be.</p>
<p><strong>Use something else when your data has no meaningful relationships.</strong> A table of log lines is a table of log lines. Modeling each one as a node connected to nothing buys you nothing and costs you storage.</p>
<p><strong>Use something else when you need one thing to be extremely fast and nothing else.</strong> A key-value store answering "give me session 4471" will beat everything, because it does exactly one thing.</p>
<p>A graph is the right choice when the connections are the point. Fraud rings, recommendations, access control, dependency analysis, lineage, org structures, supply chains, and knowledge graphs for AI systems. These share one trait: the interesting questions are about how things connect, and the number of hops isn't fixed in advance.</p>
<p>If your query never goes more than one hop, you probably don't need a graph. If your query goes three hops and the number of hops depends on the data, you almost certainly do.</p>
<h2 id="heading-how-to-set-up-neo4j-and-the-python-driver">How to Set Up Neo4j and the Python Driver</h2>
<p>For this project, you need a database and a driver.</p>
<h3 id="heading-option-a-neo4j-aura-no-installation">Option A: Neo4j Aura, No Installation</h3>
<p>The fastest route is <strong>Neo4j Aura</strong>, Neo4j's managed cloud service. There's nothing to install, and there's a genuinely free tier.</p>
<p>Go to <code>console.neo4j.io</code>, sign in, and choose <strong>Create instance</strong>. You'll be shown several tiers side by side, and this is the screen to read carefully rather than click through:</p>
<table>
<thead>
<tr>
<th>Tier</th>
<th>Cost</th>
<th>What you get</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Free</strong></td>
<td>$0</td>
<td>Up to 200,000 nodes and 400,000 relationships. Limited memory and vCPU. Limited backups. <strong>Auto-deleted after 30 days of inactivity.</strong></td>
</tr>
<tr>
<td>Professional</td>
<td>From $0.09 per GB-hour</td>
<td>Monitoring, predefined roles, 7 day backups, graph algorithms</td>
</tr>
<tr>
<td>Business Critical</td>
<td>From $0.20 per GB-hour</td>
<td>Advanced monitoring, custom roles, IP filtering, SSO, 30 day backups, 99.95% uptime SLA</td>
</tr>
</tbody></table>
<p>Pick Free for this handbook. 200,000 nodes is far more than anything here needs.</p>
<p><strong>Watch the running total at the bottom of that page.</strong> The console shows a live hourly rate and a projected monthly cost, and both update as you change tiers.</p>
<p>A paid tier can read as roughly $0.36 per hour. That is about $259 a month if you leave it running. It's very easy to click past that while concentrating on the instance name. If you only want to learn, the number at the bottom should say $0.</p>
<p>Once you confirm, Aura shows you a credentials dialog exactly once:</p>
<ul>
<li><p>Username, which is always <code>neo4j</code></p>
</li>
<li><p>A long generated password</p>
</li>
<li><p>A warning that reads "Note that the password will not be available after this point"</p>
</li>
</ul>
<p>That warning is literal. Click <strong>Download and continue</strong> to save a <code>.txt</code> file with the connection details, or copy the password somewhere safe first. If you lose it, you can't retrieve it, you can only reset it.</p>
<p>The downloaded file looks like this:</p>
<pre><code class="language-bash">NEO4J_URI=neo4j+s://xxxxxxxx.databases.neo4j.io
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=&lt;your generated password&gt;
NEO4J_DATABASE=neo4j
AURA_INSTANCEID=xxxxxxxx
AURA_INSTANCENAME=demo
</code></pre>
<p>The instance then shows <strong>Creating...</strong> in the console and takes a few minutes. During that window the hostname already resolves in DNS and port 7687 already accepts TCP connections, but the database behind it isn't up yet, so a driver will fail with <code>Unable to retrieve routing information</code>. That error during the first few minutes means "not ready", not "misconfigured". Wait and retry rather than changing your connection string.</p>
<p>The <code>+s</code> in <code>neo4j+s://</code> means the connection is encrypted and the server's certificate is verified. Aura requires encryption, and that verification is the only difference from a local instance that matters for this handbook.</p>
<h3 id="heading-if-aura-refuses-to-connect-and-youre-sure-its-running">If Aura Refuses to Connect and You're Sure it's Running</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943191739/06f47fe4-7bf9-4914-9667-32d8e16f095c.png" alt="tls interception" style="display: block;" width="3360" height="1950" loading="lazy">

<p>Aura is healthy, the browser connects, Python won't. Something on the network, usually a corporate proxy, VPN or antivirus, terminates your TLS connection, reads it, and re-encrypts it with its own certificate. Your browser was told to trust that certificate. The driver wasn't, so it correctly refuses and you get <code>ServiceUnavailable: Unable to retrieve routing information</code> while the database was fine throughout.</p>
<p>There's one failure here that wastes people hours, because the error message points at the wrong thing.</p>
<p>You connect, and the driver says:</p>
<pre><code class="language-text">neo4j.exceptions.ServiceUnavailable: Unable to retrieve routing information
</code></pre>
<p>"Routing" sounds like a cluster problem, so people go and check the instance, recreate it, and try a different region. Often none of that is the cause.</p>
<p>Check the certificate directly:</p>
<pre><code class="language-python">import socket, ssl
ctx = ssl.create_default_context()
with socket.create_connection(("xxxxxxxx.databases.neo4j.io", 7687), timeout=15) as raw:
    with ctx.wrap_socket(raw, server_hostname="xxxxxxxx.databases.neo4j.io") as s:
        print("TLS OK", s.version())
</code></pre>
<p>If that prints something like <code>CERTIFICATE_VERIFY_FAILED: self-signed certificate in certificate chain</code>, the database is fine. <strong>Something on your network is intercepting TLS.</strong> Corporate proxies, some VPNs, and several antivirus products do this: they terminate your encrypted connection, inspect it, and re-encrypt it with their own certificate. Your browser trusts that certificate because the software installed its root into the system store. Python does not, because it ships its own trust store.</p>
<p>You have three options, in order of preference.</p>
<p><strong>1. Add the interceptor's root certificate to Python's trust store</strong>, which is the correct fix and keeps verification on:</p>
<pre><code class="language-bash">export SSL_CERT_FILE=/path/to/corporate-root.pem
</code></pre>
<p><strong>2. Use a network that's not intercepted</strong>, such as a mobile hotspot, which is the quickest way to confirm the diagnosis.</p>
<p><strong>3. Fall back to</strong> <code>neo4j+ssc://</code>, which encrypts but accepts a self-signed certificate:</p>
<pre><code class="language-python">driver = GraphDatabase.driver("neo4j+ssc://xxxxxxxx.databases.neo4j.io", auth=AUTH)
</code></pre>
<p>The <code>ssc</code> stands for self-signed certificate. Your traffic is still encrypted, but the driver no longer checks who's on the other end, so anyone already intercepting can keep doing it undetected. <strong>Use it to unblock yourself while learning, and don't ship it to production.</strong></p>
<p>Every Aura query in this handbook was verified over exactly this route, on a network that turned out to be running TLS inspection.</p>
<h3 id="heading-option-b-docker-one-command">Option B: Docker, One Command</h3>
<p>If you would rather keep everything on your machine, Docker is the shortest path. Everything in this handbook was written and tested against exactly this container.</p>
<pre><code class="language-bash">docker run -d --name neo4j-graphbook \
  -p 7474:7474 -p 7687:7687 \
  -v neo4jdata:/data \
  neo4j:5
</code></pre>
<p>Port 7474 serves Neo4j Browser, the query UI you'll use in a moment. Port 7687 is Bolt, the binary protocol the Python driver speaks.</p>
<p>Set the initial password on the volume <strong>before</strong> the database starts for the first time, because the setting is ignored once a database exists:</p>
<pre><code class="language-bash">docker volume create neo4jdata
docker run --rm -v neo4jdata:/data neo4j:5 \
  neo4j-admin dbms set-initial-password yourpassword
</code></pre>
<p>Then open <code>http://localhost:7474</code> and sign in with <code>neo4j</code> and that password.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943193881/fc6804d9-9b51-40d1-8090-e96668ad8ce8.png" alt="port shadowing" style="display: block;" width="3320" height="2128" loading="lazy">

<p>We have two panels here.</p>
<ol>
<li><p>What you believe: your script dials <code>bolt://localhost:7687</code> and reaches the Docker container running <code>neo4j:5</code> with your data.</p>
</li>
<li><p>What's happening: a native Neo4j, usually Neo4j Desktop, is already listening on <code>127.0.0.1:7687</code>, so it shadows the Docker port mapping and your container is never reached at all. Your script authenticates against that other database, and the driver reports an authentication failure. Nothing in that message mentions ports.</p>
</li>
</ol>
<p>Find out who holds it with <code>lsof -nP -iTCP:7687 -sTCP:LISTEN</code>. If something else owns it, move your container with <code>docker run -p 7475:7474 -p 7688:7687 neo4j:5</code> and connect on 7688 instead.</p>
<p><strong>A trap worth knowing about:</strong> if you already run Neo4j Desktop, or any other Neo4j, it's probably already listening on 7687. A native process holding that port takes precedence over a Docker port mapping, and the symptom is confusing: the container starts fine, Browser loads, and your driver reports an authentication failure, because it's quietly talking to the <em>other</em> database.</p>
<p>If that happens, map the container somewhere else with <code>-p 7475:7474 -p 7688:7687</code> and point your driver at <code>bolt://localhost:7688</code>. Check what holds the port with <code>lsof -nP -iTCP:7687 -sTCP:LISTEN</code>.</p>
<h3 id="heading-option-c-a-cloud-server-you-control">Option C: a Cloud Server You Control</h3>
<p>There is a third option worth walking through, because it's closer to how you would actually run this for a team, and because it teaches you what the other two hide. You put Neo4j on a small Linux server in the cloud.</p>
<p>Everything below is exactly what I ran to produce the screenshots in this handbook. It uses AWS, but the shape is identical on any provider.</p>
<h4 id="heading-step-1-find-out-which-account-youre-about-to-spend-money-in">Step 1. Find out which account you're about to spend money in.</h4>
<p>This sounds obvious and it's the step people skip.</p>
<pre><code class="language-bash">aws sts get-caller-identity
aws configure get region
</code></pre>
<p>The first prints the account number and the user. The second prints the region. If either isn't what you expected, stop and fix your profile before creating anything.</p>
<h4 id="heading-step-2-find-the-current-linux-image">Step 2. Find the current Linux image.</h4>
<p>Instead of hardcoding an image ID from a blog post, ask AWS for the latest one:</p>
<pre><code class="language-bash">aws ssm get-parameters \
  --names /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64 \
  --query 'Parameters[0].Value' --output text
</code></pre>
<p>An AMI is a machine image, the template your server boots from. Image IDs differ per region and change over time, which is why you look it up rather than copy it.</p>
<h4 id="heading-step-3-create-a-firewall-that-only-lets-you-in">Step 3. Create a firewall that only lets you in.</h4>
<p>This is the step that matters most, and it's the one that gets people breached.</p>
<pre><code class="language-bash">MYIP=$(curl -s https://checkip.amazonaws.com)/32

SG=$(aws ec2 create-security-group \
  --group-name neo4j-demo-sg \
  --description "Neo4j demo, locked to my IP" \
  --vpc-id &lt;your-default-vpc-id&gt; \
  --query GroupId --output text)

for port in 22 7474 7687; do
  aws ec2 authorize-security-group-ingress \
    --group-id $SG --protocol tcp --port $port --cidr $MYIP
done
</code></pre>
<p>A security group is a firewall attached to the server. Port 22 is SSH, 7474 is Neo4j Browser, 7687 is Bolt. The <code>--cidr $MYIP</code> part restricts every one of them to your own address.</p>
<p><strong>Don't replace that with</strong> <code>0.0.0.0/0</code><strong>.</strong> That means "the entire internet". Databases left open on default ports are found by automated scanners within hours, not weeks, and an open Neo4j is a full read and write handle on your data.</p>
<h4 id="heading-step-4-boot-the-server-and-install-neo4j-automatically">Step 4. Boot the server and install Neo4j automatically.</h4>
<p>A user-data script is a shell script the server runs once, on first boot, as root.</p>
<pre><code class="language-bash">#!/bin/bash
dnf install -y docker
systemctl enable --now docker

# ask the instance what its own public address is
TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 300")
PUBIP=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/public-ipv4)

docker run -d --name neo4j --restart unless-stopped \
  -p 7474:7474 -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/ChangeThisPassword \
  -e NEO4J_server_default__listen__address=0.0.0.0 \
  -e NEO4J_server_bolt_advertised__address=$PUBIP:7687 \
  -e NEO4J_server_http_advertised__address=$PUBIP:7474 \
  neo4j:5
</code></pre>
<p>Three details in there are the whole reason this section exists.</p>
<p><code>169.254.169.254</code> is the instance metadata service, a special address every AWS server can reach to ask questions about itself. Here it is asking for its own public IP.</p>
<p><code>NEO4J_server_default__listen__address=0.0.0.0</code> tells Neo4j to accept connections from outside the machine. By default it listens only on localhost, and without this your server would be running perfectly and refusing every connection.</p>
<p>The <strong>advertised address</strong> settings are the subtle one. Neo4j Browser is a web page served by the server, and when it opens a Bolt connection it uses the address the server advertises. If the server advertises <code>localhost</code>, the Browser running in <em>your</em> laptop's browser will try to connect to <em>your</em> laptop. Setting the advertised address to the public IP is what makes a remote Browser work at all.</p>
<p>Note the double underscores. In Neo4j's environment variables, a dot in a config key becomes an underscore and a real underscore becomes a double underscore, so <code>server.default_listen_address</code> becomes <code>NEO4J_server_default__listen__address</code>.</p>
<h4 id="heading-step-5-launch-it">Step 5. Launch it.</h4>
<pre><code class="language-bash">aws ec2 run-instances \
  --image-id &lt;ami-from-step-2&gt; \
  --instance-type t3.medium \
  --key-name &lt;your-key-pair&gt; \
  --security-group-ids $SG \
  --associate-public-ip-address \
  --user-data file://userdata.sh \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=neo4j-demo}]'
</code></pre>
<p><code>t3.medium</code> gives 2 CPUs and 4GB of memory, which is comfortable for learning. Neo4j will start on 1GB but you'll fight it.</p>
<p>Boot, package install, and image pull took about 90 seconds. Poll until the Browser answers rather than guessing:</p>
<pre><code class="language-bash">until curl -s -o /dev/null -w "%{http_code}" http://&lt;public-ip&gt;:7474 | grep -q 200; do
  sleep 10
done
</code></pre>
<h4 id="heading-step-6-delete-it-when-youre-finished">Step 6. Delete it when you're finished.</h4>
<p>A server you forgot about bills every hour, forever.</p>
<pre><code class="language-bash">aws ec2 terminate-instances --instance-ids &lt;instance-id&gt;
aws ec2 delete-security-group --group-id $SG
</code></pre>
<p>I can't stress this enough for anyone learning on their own account: set a billing alarm, and terminate the moment you're done. The instance used for this handbook existed for under an hour and cost a few cents, but only because I deleted it after.</p>
<h3 id="heading-the-driver">The Driver</h3>
<pre><code class="language-bash">pip install neo4j
</code></pre>
<p>That installs the official driver. At the time of writing it's version 6.x and supports Python 3.10 and above.</p>
<h3 id="heading-connecting">Connecting</h3>
<p>The driver object is expensive to create and cheap to reuse. Create one when your program starts, and keep it. Creating a driver per request is a common and costly mistake, because each one builds its own connection pool.</p>
<pre><code class="language-python">from neo4j import GraphDatabase

URI = "neo4j+s://xxxxxxxx.databases.neo4j.io"
AUTH = ("neo4j", "your-password")

with GraphDatabase.driver(URI, auth=AUTH) as driver:
    driver.verify_connectivity()
    print("Connected")
</code></pre>
<p>There are two things worth doing every time:</p>
<p><code>verify_connectivity()</code> fails immediately with a clear error if the URI or the password is wrong. Without it, your first failure happens inside a query, where the error is less obvious and harder to attribute.</p>
<p>Using the driver as a context manager, with <code>with</code>, closes it cleanly when the block exits. In a long-running service you would instead create the driver at startup and close it during shutdown.</p>
<p>Never put credentials in your source. Read them from the environment:</p>
<pre><code class="language-python">import os
from neo4j import GraphDatabase

driver = GraphDatabase.driver(
    os.environ["NEO4J_URI"],
    auth=(os.environ["NEO4J_USER"], os.environ["NEO4J_PASSWORD"]),
)
</code></pre>
<h2 id="heading-the-modeling-decision-that-matters-most">The Modeling Decision That Matters Most</h2>
<p>Before you write a single row of data you have to decide what becomes a node, what becomes a property, and what becomes a relationship.</p>
<p>This is the part that decides whether your graph is a pleasure or a problem six months from now. It's also the part that no query optimiser can fix for you later.</p>
<p>Here are the rules:</p>
<p><strong>Make it a node if you'll ever ask a question about it.</strong> If you want to know which engineers work on the payments service, then the payments service is a node. If you want to count incidents by severity, severity is a candidate for a node.</p>
<p><strong>Make it a property if it only ever describes something else.</strong> The timestamp on an incident is a property. Nobody asks a database to find all the things that happened at 14:32 and then traverse outwards from that moment.</p>
<p><strong>Make it a relationship if it connects two nodes and you want to walk it.</strong> Ownership connects an engineer to a service, and the entire point is walking from one to the other, so it's a relationship.</p>
<p>A useful test: <strong>can you imagine drawing an arrow to it?</strong> If yes, it's probably a node. Nobody draws an arrow to a timestamp.</p>
<p>Another useful test: <strong>would you ever want to attach something else to it?</strong> Teams have managers, budgets, and charters. That's three arrows waiting to happen, which means a team is a node, not a string.</p>
<h3 id="heading-relationship-direction">Relationship Direction</h3>
<p>Every relationship in Neo4j has a direction. You store <code>(:Engineer)-[:OWNS]-&gt;(:Service)</code> because an engineer owns a service and not the other way round.</p>
<p>Direction matters when you write the data. It matters much less when you query, because you can traverse against the stored direction, and you can ignore direction entirely.</p>
<pre><code class="language-cypher">// follow the stored direction
MATCH (e:Engineer)-[:OWNS]-&gt;(s:Service) RETURN e, s

// traverse against it: start from the service
MATCH (s:Service)&lt;-[:OWNS]-(e:Engineer) RETURN s, e

// ignore direction entirely
MATCH (e:Engineer)-[:OWNS]-(s:Service) RETURN e, s
</code></pre>
<p>Those three return the same pairs. Store the direction that reads naturally as an English sentence, and stop worrying about it.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943196770/6bf41f5e-07b8-45f6-845a-ba847f9f4a49.png" alt="relationship direction" style="display: block;" width="3360" height="1372" loading="lazy">

<p>Three patterns matching identical data: walking the stored direction, walking against it, and dropping the arrowhead to ignore direction. All three return Ada and payments.</p>
<p>That third one is the debugging move. If a query returns nothing and you expected rows, drop the arrowheads. If rows appear, direction was the cause. If not, you've ruled out the likeliest suspect in ten seconds. Direction does matter when you write: <code>MERGE (a)-[:OWNS]-&gt;(b)</code> and the reverse create two different facts, and only one is true.</p>
<h3 id="heading-properties-on-relationships">Properties on Relationships</h3>
<p>This is the feature people forget exists, and it's often the cleanest answer.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943199122/bc69217f-bb75-40ac-a1af-ac4eac54118d.png" alt="relationship properties" style="display: block;" width="3580" height="2008" loading="lazy">

<p>One fact, stored two ways. In tables, <code>since</code> lives on an <code>ownership</code> join table that isn't part of your domain and exists only because rows can't point at each other. In a graph it sits on the connection, and you can query it directly: <code>MATCH (e:Engineer)-[r:OWNS]-&gt;(s:Service) WHERE r.since &lt; date() - duration('P1Y')</code> gives you everyone who has owned something for more than a year.</p>
<pre><code class="language-cypher">MERGE (e:Engineer {email: 'ada@example.com'})-[r:OWNS]-&gt;(s:Service {name: 'payments'})
  SET r.since = date('2026-03-01'), r.primary = true
</code></pre>
<p>Now you can ask who has owned a service for longer than a year, without inventing a join table to hold the fact.</p>
<h2 id="heading-three-modeling-mistakes-almost-everyone-makes">Three Modeling Mistakes Almost Everyone Makes</h2>
<p>I've watched these three mistakes happen more times than any others, and each one is easy to avoid once you've seen it.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943202043/5509c707-9bf2-4739-9ed1-6ada4388190a.png" alt="modelling mistake" style="display: block;" width="3200" height="1968" loading="lazy">

<p>Almost every first graph model makes this one: storing a connection as a property because it looks simpler. It can't be traversed, can't carry facts of its own, and turns into string matching.</p>
<h3 id="heading-mistake-1-storing-a-connection-as-a-property">Mistake #1: Storing a Connection as a Property</h3>
<p>You give each engineer a <code>team</code> property holding the string <code>"platform"</code>.</p>
<p>This works right up until you want to know what else the platform team owns. Now you're matching strings scattered across thousands of nodes. Worse, the moment someone writes <code>"Platform"</code> with a capital P, you've silently created a second team, and no error was raised.</p>
<p>The fix is to make the team a node and connect engineers to it. Both problems disappear at once, and you gain somewhere to hang the team's manager and budget later.</p>
<p>The general form of this mistake: <strong>anything you want to traverse must be a relationship</strong>. A property holding a list of identifiers is a graph database pretending to be a spreadsheet.</p>
<h3 id="heading-mistake-2-one-generic-relationship-type-for-everything">Mistake #2: One Generic Relationship Type for Everything</h3>
<p>You create a <code>RELATED_TO</code> relationship and put a <code>type</code> property on it to say what kind of relation it is.</p>
<p>This looks flexible. It's the opposite. Neo4j narrows the search by relationship type before it walks anything, so <code>-[:OWNS]-&gt;</code> is fast. Filtering on a property means walking every <code>RELATED_TO</code> relationship first, then discarding most of them, which is exactly the row-scanning behaviour you moved to a graph to avoid.</p>
<p>Name your relationships for what they mean: <code>OWNS</code>, <code>AFFECTS</code>, <code>MEMBER_OF</code>, or <code>DEPENDS_ON</code>. Specific types are both faster and self documenting.</p>
<h3 id="heading-mistake-3-making-everything-a-node">Mistake #3: Making Everything a Node</h3>
<p>This is the overcorrection, and it's its own problem.</p>
<p>If a value only ever describes one node, and you never search for it independently, it's a property. Creating a node for every timestamp gives you a much larger graph, slower traversals, and nothing whatsoever in return.</p>
<p>The test remains the same. Will you ask a question about it, or attach something to it? If not, it's a property.</p>
<h2 id="heading-modeling-backwards-from-your-questions">Modeling Backwards From Your Questions</h2>
<p>Here's a technique that will save you a rewrite.</p>
<p>Don't start by modeling your domain. Start by writing down the questions the graph has to answer, in plain English, before you draw anything.</p>
<p>For our example:</p>
<ol>
<li><p>Which services did this incident affect?</p>
</li>
<li><p>Who owns those services?</p>
</li>
<li><p>Which teams do those owners belong to?</p>
</li>
<li><p>Which services depend on the one that broke?</p>
</li>
<li><p>Who has been on call for this service in the last month?</p>
</li>
</ol>
<p>Now check your model against the list. Every question should be a path you can trace with your finger. If a question requires a join across two properties, or a scan of every node of some label, the model is wrong for that question.</p>
<p>Question five is a good example of why this matters. "On call in the last month" is a fact about a period of time connecting a person and a service. That's a relationship with properties on it, and if you had modeled on-call as a boolean property on the engineer, you would've discovered the problem after loading your data instead of before.</p>
<p>Relational modeling teaches you to normalise first and query later. Graph modeling works better in the other direction.</p>
<h2 id="heading-three-modeling-patterns-worth-knowing-early">Three Modeling Patterns Worth Knowing Early</h2>
<p>Once the basics land, three patterns cover most of what you'll hit in real data.</p>
<h3 id="heading-when-a-relationship-needs-more-than-two-ends">When a Relationship Needs More Than Two Ends</h3>
<p>A relationship connects exactly two nodes. Sometimes a fact connects three or more.</p>
<p>"Ada was on call for payments during March" involves a person, a service, and a time window. You can't hang that off a single relationship without losing something.</p>
<p>The pattern is to promote the fact itself to a node:</p>
<pre><code class="language-cypher">MERGE (e:Engineer {email: 'ada@example.com'})
MERGE (s:Service {name: 'payments'})
CREATE (r:OnCallRotation {start: date('2026-03-01'), end: date('2026-03-31')})
MERGE (e)-[:SERVED]-&gt;(r)
MERGE (r)-[:FOR_SERVICE]-&gt;(s)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943204974/7c021397-05a5-4cc5-a585-ece032246029.png" alt="nary intermediate node" style="display: block;" width="3360" height="2128" loading="lazy">

<p>"Ada was on call for payments during March" has three participants and a relationship has two ends. Forced onto one <code>ON_CALL</code>, it breaks in April, because a second rotation needs a second relationship between the same nodes and nothing can hang off either. Promote the fact to a node and it gets three relationships, so anything can attach. The signal is wanting to put a property on a relationship that describes something other than that exact pair.</p>
<p><code>OnCallRotation</code> is sometimes called an intermediate node, a reified relationship, or a hyper-edge. The name doesn't matter. What matters is that a fact with three participants becomes a node with three relationships, and now you can attach more to it later, such as who swapped in halfway through.</p>
<p>The signal that you need this: you find yourself wanting to put a property on a relationship that describes something other than that exact pair of nodes.</p>
<h3 id="heading-versioning-when-facts-change-over-time">Versioning, When Facts Change Over Time</h3>
<p>Graphs are easy to update in place, which makes it tempting to overwrite. If history matters, don't.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943207486/ebcf3146-22f0-4c8b-8d17-a9f23e56e5f7.png" alt="temporal versioning" style="display: block;" width="3360" height="1420" loading="lazy">

<p>Ownership changes hands, and pointing the relationship at the new person erases that anyone else ever held it. The alternative closes the old relationship with an end date and opens a new one, so history survives. Overwriting is what happens if you don't decide.</p>
<p>The usual pattern is to keep the relationship and mark it closed rather than deleting it:</p>
<pre><code class="language-cypher">// close the old ownership rather than deleting it
MATCH (e:Engineer {email: $old})-[r:OWNS]-&gt;(s:Service {name: $service})
WHERE r.until IS NULL
SET r.until = date()

// open a new one
MATCH (e:Engineer {email: $new}), (s:Service {name: $service})
MERGE (e)-[r2:OWNS]-&gt;(s)
  ON CREATE SET r2.since = date()
</code></pre>
<p>Current ownership is then <code>WHERE r.until IS NULL</code>, and history is still there when someone asks who owned this last year. The cost is that every query about "now" needs that filter, so decide deliberately rather than by accident.</p>
<h3 id="heading-hierarchies-which-graphs-are-unusually-good-at">Hierarchies, Which Graphs Are Unusually Good At</h3>
<p>Trees are painful in SQL and trivial here. An organisation, a category tree, a folder structure, and a dependency chain are all the same shape.</p>
<pre><code class="language-cypher">// everyone under a given manager, at any depth
MATCH path = (m:Engineer {email: $email})&lt;-[:REPORTS_TO*1..10]-(report:Engineer)
RETURN report.name AS name, length(path) AS depth
ORDER BY depth, name
</code></pre>
<p>Naming the path with <code>path =</code> is what lets you call <code>length()</code> on it, which returns the number of relationships traversed and therefore how far down the tree each person sits.</p>
<p>This is the query that makes people switch. In SQL it's a recursive common table expression that most engineers have to look up every time. Here it's one line, and changing the depth is changing a number.</p>
<h2 id="heading-loading-data-from-python">Loading Data From Python</h2>
<p>The modern driver gives you one method for running a query: <code>execute_query</code>. It manages sessions and retries for you, and it's the right default.</p>
<p>Start with a single engineer and a single service.</p>
<pre><code class="language-python">driver.execute_query(
    """
    MERGE (e:Engineer {email: $email})
      SET e.name = $name
    MERGE (s:Service {name: $service})
    MERGE (e)-[:OWNS]-&gt;(s)
    """,
    email="ada@example.com",
    name="Ada",
    service="payments",
    database_="neo4j",
)
</code></pre>
<p>Three things in that snippet deserve attention.</p>
<h3 id="heading-merge-rather-than-create">MERGE Rather Than CREATE</h3>
<p><code>CREATE</code> always makes a new node. Run your loading script twice and you have two identical engineers, two identical services, and a mess.</p>
<p><code>MERGE</code> looks for a node matching the pattern and creates one only if nothing matches. That makes the script safe to run again, which you'll want the very first time it fails halfway through a load.</p>
<p>The rule of thumb: <code>CREATE</code> when you know the thing is new, <code>MERGE</code> when you're loading from a source that might contain something you already have.</p>
<h3 id="heading-merge-on-identity-then-set-everything-else">Merge on Identity, Then Set Everything Else</h3>
<p>Look carefully at where the properties are.</p>
<pre><code class="language-python">MERGE (e:Engineer {email: $email})
  SET e.name = $name
</code></pre>
<p>The <code>MERGE</code> is on <code>email</code> alone, and the name is applied afterwards with <code>SET</code>.</p>
<p>If you had merged on both email and name, then the day someone changes their name you would create a second node rather than updating the first. You would end up with two Adas, connected to different things, and no error to tell you.</p>
<p><strong>Merge on the property that identifies the node. Set the rest.</strong></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943210745/4bef13c4-8f7b-4c11-981c-bf264a9c61ab.png" alt="merge key" style="display: block;" width="3360" height="1576" loading="lazy">

<p>Two scripts that both run without error and both report success. The left merges on email and name together. The right merges on email alone and sets the name afterwards.</p>
<p>Load them once and they look identical. Then Ada marries and changes her name to Ada Okonjo, same email. On the left the pattern no longer matches, because the name differs, so MERGE creates a second node. Her ownerships are now split across both, and every query about her returns part of the truth.</p>
<p>On the right the email still matched, so MERGE found the existing node and SET overwrote the name, and her relationships stay attached to the node they were always on.</p>
<p>The rule: merge on the property that identifies the node and nothing else, and set everything that merely describes it. If a value can change while the thing stays the same thing, it doesn't belong in the key. You can catch this whole class of bug by loading your data twice and asserting the node count is identical, which costs three lines.</p>
<p>There's a matching variant when you want different behaviour on first insert versus update:</p>
<pre><code class="language-cypher">MERGE (e:Engineer {email: $email})
  ON CREATE SET e.name = $name, e.created = datetime()
  ON MATCH  SET e.name = $name, e.last_seen = datetime()
</code></pre>
<h3 id="heading-parameters-never-string-formatting">Parameters, Never String Formatting</h3>
<p>The values are passed separately as <code>$email</code> and <code>$name</code>. Never build a query by concatenating strings.</p>
<p>This protects you from injection, which is the obvious reason. There's a second reason that matters for performance: Neo4j caches query plans keyed on the query text. Parameterised queries have identical text every time, so the plan is compiled once and reused. String-formatted queries produce a new plan for every distinct value, which fills the plan cache with garbage and recompiles constantly.</p>
<h2 id="heading-loading-at-scale-with-unwind">Loading at Scale with UNWIND</h2>
<p>One node at a time means one network round trip per node. Loading ten thousand records that way is slow, and almost all of the time is spent waiting rather than working.</p>
<p>Send a list instead and let Cypher loop inside the database.</p>
<pre><code class="language-python">rows = [
    {"email": "ada@example.com",   "name": "Ada",   "service": "payments"},
    {"email": "linus@example.com", "name": "Linus", "service": "checkout"},
    {"email": "grace@example.com", "name": "Grace", "service": "payments"},
]

driver.execute_query(
    """
    UNWIND $rows AS row
    MERGE (e:Engineer {email: row.email})
      SET e.name = row.name
    MERGE (s:Service {name: row.service})
    MERGE (e)-[:OWNS]-&gt;(s)
    """,
    rows=rows,
    database_="neo4j",
)
</code></pre>
<p><code>UNWIND</code> takes a list and turns it into rows, so everything after it runs once per element, all inside a single transaction and a single round trip.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943213878/e0c6c996-c416-44ae-8751-315a28083a64.png" alt="unwind round trips" style="display: block;" width="3240" height="2128" loading="lazy">

<p>What makes a bulk load slow isn't the writing, it's the waiting between writes. One statement per row is a network round trip per row. One UNWIND sends the batch in a single trip and lets the database loop internally.</p>
<p>This is not a small optimisation. Writing 1,000 rows to the 75,500 node dataset, one statement per row against a single <code>UNWIND</code>:</p>
<table>
<thead>
<tr>
<th>Approach</th>
<th>Round trips</th>
<th>Time</th>
</tr>
</thead>
<tbody><tr>
<td>One statement per row</td>
<td>1,000</td>
<td>2,758 ms</td>
</tr>
<tr>
<td>One <code>UNWIND</code></td>
<td>1</td>
<td>64 ms</td>
</tr>
</tbody></table>
<p>Forty-three times faster, on a database running on the same machine as the client, where a round trip costs almost nothing. Run it yourself and you'll get a different multiple, somewhere in the same region: a clean checkout on this machine measured sixty-six.</p>
<p><strong>The gap grows with distance.</strong> I ran the same comparison against a managed instance in another city and measured 91,722 ms against 150 ms, which is 613 times. Nothing about the work changed. What changed is that each of the 1,000 round trips now pays for a journey across the country and back. A minute and a half became a seventh of a second.</p>
<p>That's the real lesson: the cost of chattiness isn't fixed. It is however far away your database happens to be, multiplied by how many times you talk to it.</p>
<p>For a real load, batch it. One enormous transaction holds every change in memory until it commits, and a transaction containing a million updates is a good way to exhaust the heap.</p>
<pre><code class="language-python">def load_in_batches(driver, rows, batch_size=5000):
    query = """
    UNWIND $rows AS row
    MERGE (e:Engineer {email: row.email})
      SET e.name = row.name
    MERGE (s:Service {name: row.service})
    MERGE (e)-[:OWNS]-&gt;(s)
    """
    for start in range(0, len(rows), batch_size):
        batch = rows[start:start + batch_size]
        driver.execute_query(query, rows=batch, database_="neo4j")
        print(f"loaded {start + len(batch)} of {len(rows)}")
</code></pre>
<p>A few thousand rows per batch is a reasonable starting point. Tune it by watching memory rather than by guessing.</p>
<h2 id="heading-loading-from-a-csv-file">Loading From a CSV File</h2>
<p>Most real data starts life in a spreadsheet or an export. There are two ways to get it in, and picking the wrong one is a common source of frustration.</p>
<h3 id="heading-option-1-read-it-in-python-send-it-with-unwind">Option #1: Read it in Python, Send it with UNWIND</h3>
<p>This is the one to reach for by default. You already know how it works, it runs anywhere, and you can clean the data on the way through.</p>
<pre><code class="language-python">import csv

def load_csv(driver, path, batch_size=5000):
    with open(path, newline="", encoding="utf-8") as f:
        rows = list(csv.DictReader(f))

    query = """
    UNWIND $rows AS row
    MERGE (e:Engineer {email: row.email})
      SET e.name = row.name
    MERGE (s:Service {name: row.service})
    MERGE (e)-[:OWNS]-&gt;(s)
    """
    for start in range(0, len(rows), batch_size):
        driver.execute_query(query, rows=rows[start:start + batch_size], database_="neo4j")
</code></pre>
<p><code>csv.DictReader</code> gives you a dictionary per row keyed by the header names, which is exactly the shape <code>UNWIND</code> wants.</p>
<p>One warning that catches everyone: <strong>every value from a CSV is a string.</strong> A column of numbers arrives as <code>"42"</code>, not <code>42</code>, and a column of dates arrives as <code>"2026-03-01"</code>. If you store them raw you'll later write comparisons that silently do the wrong thing, because <code>"9" &gt; "10"</code> is true when both are strings. Convert as you read:</p>
<pre><code class="language-python">for row in rows:
    row["headcount"] = int(row["headcount"]) if row["headcount"] else None
</code></pre>
<h3 id="heading-option-3-load-csv-which-runs-inside-the-database">Option #3: LOAD CSV, Which Runs Inside the Database</h3>
<p>Cypher can read a file itself. This is faster for very large files because the data never travels through your Python process.</p>
<pre><code class="language-cypher">LOAD CSV WITH HEADERS FROM 'file:///engineers.csv' AS row
CALL {
  WITH row
  MERGE (e:Engineer {email: row.email})
    SET e.name = row.name
  MERGE (s:Service {name: row.service})
  MERGE (e)-[:OWNS]-&gt;(s)
} IN TRANSACTIONS OF 1000 ROWS
</code></pre>
<p><code>CALL { ... } IN TRANSACTIONS OF 1000 ROWS</code> is the important part. Without it the whole file is one transaction, which is how people run a large import and watch it exhaust memory.</p>
<p>There are two constraints on <code>LOAD CSV</code> that surprise people:</p>
<p>First, the file has to be somewhere the database can reach, not somewhere you can reach. <code>file:///</code> means the import directory <em>on the server</em>. On Docker that means mounting a folder into the container with <code>-v $(pwd)/data:/var/lib/neo4j/import</code>. On Aura you can't use local files at all, so the URL must be a publicly reachable <code>https://</code> address.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943216000/82b43543-57b9-48c0-8581-c03881d3cc2f.png" alt="csv strings" style="display: block;" width="3280" height="2088" loading="lazy">

<p>Every CSV value arrives as a string, including numbers. Nothing errors and no warning appears, so <code>"9" &gt; "10"</code> is true and your filter quietly returns the wrong rows. Cast on the way in.</p>
<p>Second, everything is still a string. Cypher has conversion functions for this:</p>
<pre><code class="language-cypher">LOAD CSV WITH HEADERS FROM 'https://example.com/services.csv' AS row
MERGE (s:Service {name: row.name})
  SET s.headcount = toInteger(row.headcount),
      s.launched  = date(row.launched)
</code></pre>
<p><code>toInteger</code>, <code>toFloat</code>, <code>date</code> and <code>datetime</code> are the ones you'll use constantly. <code>toInteger</code> returns <code>null</code> rather than throwing on a value it can't parse, which is convenient and also means a column full of typos will quietly become a column full of nulls. Check your data after loading:</p>
<pre><code class="language-cypher">MATCH (s:Service) WHERE s.headcount IS NULL RETURN count(*) AS unparsed
</code></pre>
<h2 id="heading-updating-and-deleting">Updating and Deleting</h2>
<p>Loading is only half of it. Data changes, and the commands that change it have sharp edges.</p>
<h3 id="heading-changing-properties">Changing Properties</h3>
<p><code>SET</code> adds or overwrites a property. <code>REMOVE</code> takes one away entirely, which is different from setting it to null.</p>
<pre><code class="language-cypher">MATCH (e:Engineer {email: $email})
SET e.name = $name, e.updated = datetime()
REMOVE e.legacy_id
</code></pre>
<p>There's a shorthand that overwrites several properties at once from a map:</p>
<pre><code class="language-cypher">MATCH (e:Engineer {email: $email})
SET e += $props
</code></pre>
<p><code>+=</code> merges the map into the node, leaving properties you didn't mention alone. Plain <code>=</code> <strong>replaces the entire property set</strong>, silently deleting anything not in your map. That difference has cost people real data, so it's worth reading twice.</p>
<h3 id="heading-deleting">Deleting</h3>
<p>You can't delete a node that still has relationships. Neo4j refuses, because leaving a dangling relationship would corrupt the graph.</p>
<pre><code class="language-cypher">// fails if the engineer owns anything
MATCH (e:Engineer {email: $email}) DELETE e
</code></pre>
<p><code>DETACH DELETE</code> removes the relationships and then the node:</p>
<pre><code class="language-cypher">MATCH (e:Engineer {email: $email}) DETACH DELETE e
</code></pre>
<p>It's handy, and dangerous for exactly the same reason. Run the <code>MATCH</code> on its own with <code>RETURN</code> first and look at what comes back, every time.</p>
<p>To wipe a whole database while experimenting:</p>
<pre><code class="language-cypher">MATCH (n) DETACH DELETE n
</code></pre>
<p>That's fine on a few thousand nodes and a bad idea on millions, because it builds one enormous transaction. For a large reset, drop the database or delete in batches with <code>CALL { ... } IN TRANSACTIONS</code>.</p>
<h2 id="heading-working-with-neo4j-data-types">Working with Neo4j Data Types</h2>
<p>Neo4j stores more than strings and numbers, and using the right type saves you from parsing dates out of text later.</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Example</th>
<th>Notes</th>
</tr>
</thead>
<tbody><tr>
<td>String, Integer, Float, Boolean</td>
<td><code>'payments'</code>, <code>42</code>, <code>1.5</code>, <code>true</code></td>
<td>As expected</td>
</tr>
<tr>
<td>List</td>
<td><code>['a','b','c']</code></td>
<td>Homogeneous lists of primitives</td>
</tr>
<tr>
<td>Date, DateTime, Time</td>
<td><code>date('2026-03-01')</code>, <code>datetime()</code></td>
<td>Real temporal types, comparable and sortable</td>
</tr>
<tr>
<td>Duration</td>
<td><code>duration('P30D')</code></td>
<td>Periods, which you can add to a date</td>
</tr>
<tr>
<td>Point</td>
<td><code>point({latitude: 51.5, longitude: -0.12})</code></td>
<td>Spatial, with a distance function</td>
</tr>
</tbody></table>
<p>A property can't hold a map or a node. If you find yourself wanting nested structure inside a property, that nested thing is usually asking to be a node.</p>
<p>Temporal types are the ones that earn their keep immediately:</p>
<pre><code class="language-cypher">MATCH (e:Engineer)-[r:OWNS]-&gt;(s:Service)
WHERE r.since &lt; date() - duration('P1Y')
RETURN e.name, s.name, duration.between(r.since, date()).years AS years
</code></pre>
<p>Comparing dates as dates, rather than as strings you hope sort correctly, removes a whole category of bug.</p>
<p>On the Python side the driver converts these for you. <code>date</code> and <code>datetime</code> come back as <code>neo4j.time</code> objects, which have <code>.to_native()</code> if you want Python's own <code>datetime</code>:</p>
<pre><code class="language-python">records, _, _ = driver.execute_query(
    "MATCH (e:Engineer)-[r:OWNS]-&gt;(s:Service) WHERE r.since IS NOT NULL RETURN r.since AS since",
    database_="neo4j",
)
for r in records:
    print(r["since"], "-&gt;", r["since"].to_native())
</code></pre>
<h2 id="heading-your-first-cypher-queries">Your First Cypher Queries</h2>
<p>Cypher looks a little like SQL in places, but its central idea is different. You draw the shape you're looking for, and the database finds every part of the graph matching that shape.</p>
<p>Patterns use parentheses for nodes and arrows for relationships:</p>
<pre><code class="language-cypher">(e:Engineer)-[:OWNS]-&gt;(s:Service)
</code></pre>
<p>Read it aloud: an engineer node, an OWNS relationship pointing out of it, and a service node at the other end. The pattern is the query.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943218402/fade2e09-2b03-4b21-ba0e-90d79ebc2691.png" alt="cypher pattern anatomy" style="display: block;" width="3360" height="1944" loading="lazy">

<p>Five conventions on <code>(e:Engineer)-[:OWNS]-&gt;(s:Service)</code>. Round brackets are a node. <code>e</code> is an optional variable, named only if you want it back. <code>:Engineer</code> is a label, narrowing to that kind first. Square brackets and an arrow are a relationship and its stored direction. <code>:OWNS</code> is the type, and Neo4j narrows by type first, which is why specific types are fast.</p>
<p>Said aloud: "an engineer, who owns a service." The SQL equivalent says how to reconstruct the connection. The Cypher says what the connection is.</p>
<h3 id="heading-finding-things">Finding Things</h3>
<pre><code class="language-python">records, summary, keys = driver.execute_query(
    """
    MATCH (e:Engineer)-[:OWNS]-&gt;(s:Service {name: $service})
    RETURN e.name AS name, e.email AS email
    ORDER BY name
    """,
    service="payments",
    database_="neo4j",
)

for record in records:
    print(record["name"], record["email"])
</code></pre>
<p><code>execute_query</code> returns three things: the records, a summary, and the keys that were returned.</p>
<p>Most of the time you want the records, which is why you'll often see the other two discarded with underscores.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943220876/8c9c25e9-4844-4420-b46e-14331427abd8.png" alt="multihop table" style="display: block;" width="4400" height="788" loading="lazy">

<p>Neo4j Browser running the multi-hop query, with the results as a table. It's the same query you wrote above, with the parameter filled in by hand. That's what you do when you're exploring in the browser rather than calling from Python.</p>
<p>The query starts at incident <code>INC-4471</code>, follows <code>AFFECTS</code> out to the services it touched, then follows <code>OWNS</code> backwards to the engineers who own them. The rows that come back are those engineers' names and email addresses, sorted by name.</p>
<p>The same query, just run in Neo4j Browser. Two columns come back, <code>name</code> and <code>email</code>, one row per engineer.</p>
<h3 id="heading-filtering">Filtering</h3>
<p><code>WHERE</code> works much as you would expect.</p>
<pre><code class="language-cypher">MATCH (e:Engineer)-[r:OWNS]-&gt;(s:Service)
WHERE r.since &lt; date('2026-01-01') AND s.tier = 'critical'
RETURN e.name, s.name, r.since
</code></pre>
<p>Note that you can filter on a property of the relationship, <code>r.since</code>, as easily as on a property of a node. That's the payoff for modeling the fact where it belongs.</p>
<h3 id="heading-counting-and-grouping">Counting and Grouping</h3>
<p>Cypher has no <code>GROUP BY</code>. Aggregation is implicit: anything you return that's not an aggregate becomes the grouping key.</p>
<pre><code class="language-cypher">MATCH (t:Team)&lt;-[:MEMBER_OF]-(e:Engineer)-[:OWNS]-&gt;(s:Service)
RETURN t.name AS team, count(DISTINCT s) AS services
ORDER BY services DESC
</code></pre>
<p>That returns one row per team, because <code>t.name</code> is the only non-aggregate in the <code>RETURN</code>.</p>
<h3 id="heading-when-something-might-not-be-there">When Something Might Not Be There</h3>
<p><code>MATCH</code> drops rows that don't match the whole pattern. If you want engineers whether or not they own anything, use <code>OPTIONAL MATCH</code>, which is the closest equivalent to a left outer join.</p>
<pre><code class="language-cypher">MATCH (e:Engineer)
OPTIONAL MATCH (e)-[:OWNS]-&gt;(s:Service)
RETURN e.name AS name, collect(s.name) AS services
</code></pre>
<p>Engineers who own nothing come back with an empty list rather than vanishing from the result.</p>
<h2 id="heading-the-multi-hop-query-that-justifies-the-whole-thing">The Multi-Hop Query That Justifies the Whole Thing</h2>
<p>Now let's return to the question from the very beginning.</p>
<p>An incident affected some services. Who has context on those services?</p>
<pre><code class="language-python">records, _, _ = driver.execute_query(
    """
    MATCH (i:Incident {ref: $ref})-[:AFFECTS]-&gt;(:Service)&lt;-[:OWNS]-(e:Engineer)
    RETURN DISTINCT e.name AS name, e.email AS email
    """,
    ref="INC-4471",
    database_="neo4j",
)
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943223173/418b0723-595f-4013-ba13-7641ea9db3b3.png" alt="traversal iso" style="display: block;" width="3200" height="1588" loading="lazy">

<p>One incident, two hops, and six nodes read. The work is the small pile standing on each step, not anything proportional to how much data the database holds.</p>
<p>Read the pattern from left to right and it's close to the English sentence.</p>
<p>Here's that query run against a live Neo4j Aura instance from the terminal:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943225485/04c4f3e9-1572-47e5-a7b2-11ff258c91c9.png" alt="terminal multihop" style="display: block;" width="3000" height="984" loading="lazy">

<p>Same query again, this time from <code>cypher-shell</code> against Aura instead of the browser, returning the identical three names: <code>"Ada Okonjo"</code>, <code>"Grace Lin"</code> and <code>"Linus Berg"</code>.</p>
<p>Start at the incident, follow AFFECTS to the services it hit, then follow OWNS backwards to the engineers who own them.</p>
<p>The arrow pointing left, <code>&lt;-[:OWNS]-</code>, is doing real work. Ownership was stored from engineer to service, so reaching the engineers from the services means traversing against the stored direction.</p>
<p>Getting this backwards is the single most common reason a beginner's query returns nothing at all. If a query returns an empty result and you expected rows, check your arrow directions first.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943228032/5fc8a639-07d2-473f-b001-bfc698490c76.png" alt="graph result" style="display: block;" width="4400" height="1360" loading="lazy">

<p>This is the same result drawn as a graph instead of a table, in Neo4j Browser. The incident sits at one end, the services it affected in the middle, and the engineers who own those services at the other end. The path the query walked is visible as a shape rather than as rows.</p>
<p>Now widen it. Which whole teams are behind the affected services?</p>
<p>Here's the query most people write first. <strong>It's wrong, and it fails silently</strong>, which is why it's worth showing.</p>
<pre><code class="language-cypher">// WRONG: silently drops teams. Explanation below.
MATCH (i:Incident {ref: $ref})-[:AFFECTS]-&gt;(:Service)&lt;-[:OWNS]-(:Engineer)
      -[:MEMBER_OF]-&gt;(t:Team)&lt;-[:MEMBER_OF]-(e:Engineer)
RETURN DISTINCT t.name AS team, e.name AS name
ORDER BY team, name
</code></pre>
<p>Run that against the dataset in this handbook and it returns three rows, all from the Platform team. The Commerce team is missing, even though Linus owns <code>checkout</code> and <code>checkout</code> was affected.</p>
<h3 id="heading-relationship-uniqueness-the-trap-that-hides-answers">Relationship Uniqueness, the Trap That Hides Answers</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943230817/46852bb9-0a7f-4765-b57c-527f96dd128d.png" alt="relationship uniqueness" style="display: block;" width="3400" height="2248" loading="lazy">

<p>We have two versions side by side here. The single pattern looks correct and <strong>returns three rows</strong>. Split into two patterns joined by <code>WITH</code>, the same question <strong>returns four</strong>. The drawing traces why: the pattern has to walk out along a <code>MEMBER_OF</code> relationship and back along the same one, and Cypher discards that match rather than reusing the relationship.</p>
<p>Splitting the pattern lifts the restriction because the rule applies within one pattern, not across the query, and <code>WITH DISTINCT</code> keeps the extra rows from duplicating.</p>
<p>Cypher guarantees that <strong>a single pattern won't traverse the same relationship twice</strong>. This is called relationship isomorphism, and it exists to stop patterns looping back on themselves forever.</p>
<p>Look at what that means for Commerce. Its only member is Linus, and Linus is also the owner. To match, the pattern has to walk out of Linus along his <code>MEMBER_OF</code> relationship to reach the team, and then walk back down the very same relationship to reach a member. That's the same relationship twice, so Cypher discards the row.</p>
<p>There's no error or warning, just a quieter answer than the truth.</p>
<p>The fix is to break the single pattern into two, so the rule no longer spans both halves:</p>
<pre><code class="language-cypher">MATCH (i:Incident {ref: $ref})-[:AFFECTS]-&gt;(:Service)&lt;-[:OWNS]-(:Engineer)-[:MEMBER_OF]-&gt;(t:Team)
WITH DISTINCT t
MATCH (t)&lt;-[:MEMBER_OF]-(e:Engineer)
RETURN t.name AS team, e.name AS name
ORDER BY team, name
</code></pre>
<p><code>WITH</code> ends one pattern and begins another. The second <code>MATCH</code> starts fresh, so the owner's own membership is available again.</p>
<p>That version returns four rows, including Commerce and Linus.</p>
<h3 id="heading-does-it-still-hold-at-scale">Does it Still Hold at Scale?</h3>
<p>A fair objection to everything above is that fourteen nodes proves nothing. So here is the same multi-hop query, unchanged, against the 75,500 node dataset:</p>
<pre><code class="language-text">33 engineers returned, 150 database accesses, 4.6 ms
</code></pre>
<p>The graph is roughly five thousand times larger. The query is identical, and it still touches around a hundred and fifty things.</p>
<p>That's index-free adjacency doing exactly what was promised at the top of this article. The work is proportional to the neighbourhood you walk, not to the size of the database you walk it in. A join across three tables of that size would have to consider vastly more rows to answer the same question.</p>
<p>You can reproduce this yourself. The dataset is committed to the <a href="https://github.com/ronidas39/knowledge-graph-python-neo4j">companion repository</a>, and <code>benchmark.py</code> runs this measurement along with the others in this article.</p>
<p><strong>The general lesson:</strong> whenever a pattern leaves a node and comes back to the same kind of node, ask whether the two halves could ever be the same relationship. If they could, split the query with <code>WITH</code>. This is the most common source of silently incomplete results in Cypher, and it's very hard to spot by reading, because the query looks correct and returns plausible data.</p>
<p>Four hops, still readable as a sentence. Writing the equivalent in SQL means several joins plus a distinct, and changing "two steps" to "three steps" means rewriting it.</p>
<h2 id="heading-variable-length-paths-and-how-to-keep-them-safe">Variable Length Paths and How to Keep Them Safe</h2>
<p>Sometimes you don't know how many hops you need. Service dependencies are the classic case: payments depends on auth, auth depends on the user store, and you want everything downstream of a failure.</p>
<pre><code class="language-cypher">MATCH (s:Service {name: $name})&lt;-[:DEPENDS_ON*1..4]-(affected:Service)
RETURN DISTINCT affected.name
</code></pre>
<p>The <code>*1..4</code> means follow between one and four <code>DEPENDS_ON</code> relationships.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943234236/4dd2cc87-1c11-4ef7-9d59-d67a917e8123.png" alt="variable length paths" style="display: block;" width="3320" height="2088" loading="lazy">

<p>Always bound a variable length path. Each hop multiplies what the last one reached, so <code>[:DEPENDS_ON*]</code> has nothing to stop it while <code>[:DEPENDS_ON*1..4]</code> does. On a connected graph the unbounded version doesn't return slowly, it stops being a query you can wait for.</p>
<p><strong>Always put an upper bound on it.</strong> An unbounded <code>*</code> on a well-connected graph can walk an enormous portion of the database, and the query that was instant on your test data will hang on production data. This is the single most common way people make a graph database look slow.</p>
<p>Here's what each extra pair of hops costs, starting from the most depended-upon service in the 75,500 node dataset, which has 10,039 <code>DEPENDS_ON</code> relationships between services:</p>
<table>
<thead>
<tr>
<th>Bound</th>
<th>Services reached</th>
<th>Database accesses</th>
</tr>
</thead>
<tbody><tr>
<td><code>*1..2</code></td>
<td>30</td>
<td>290</td>
</tr>
<tr>
<td><code>*1..4</code></td>
<td>133</td>
<td>1,620</td>
</tr>
<tr>
<td><code>*1..6</code></td>
<td>481</td>
<td>6,388</td>
</tr>
</tbody></table>
<p>Look at what happens between two hops and six. The reach grows more than fifteen fold, and the work grows twenty two fold. Nothing about the query changed except two characters.</p>
<p>That's the shape to keep in your head. Reach grows geometrically, and work grows with it. On a denser graph than this one the multiplier is larger, which is why an unbounded <code>*</code> on a social graph or a dependency graph can go from fast to hopeless with no warning at all, and why the failure arrives in production rather than on your laptop: your test data was not connected enough to hurt you.</p>
<p>I have deliberately not given you timings for these three. At this size they all complete in two to four milliseconds and the differences between them are measurement noise, not signal. The database access counts are the honest comparison, and unlike the timings, they'll be identical on your machine.</p>
<p>You can also ask for the shortest connection between two nodes, which is a genuinely hard query in SQL and a one liner here:</p>
<pre><code class="language-cypher">MATCH p = shortestPath(
  (a:Engineer {email: $from})-[:MEMBER_OF|OWNS*..6]-(b:Engineer {email: $to})
)
RETURN [n IN nodes(p) | coalesce(n.name, n.email)] AS hops
</code></pre>
<p>That returns the chain of things connecting two people. Recommendation engines, fraud detection, and access analysis are all variations on this one query.</p>
<h2 id="heading-what-an-index-actually-is">What an Index Actually is</h2>
<p>Before we use one, it's worth being clear about what an index is, because almost every performance problem in this article traces back to this one idea.</p>
<p>Think about a textbook of nine hundred pages. You want the part about photosynthesis. You have two options: you can start at page one and read forward until you find it, or you can turn to the index at the back, find "photosynthesis, 412", and go straight to page 412.</p>
<p>Both find the same page. One reads up to nine hundred pages, the other reads two.</p>
<p>A database index is that back-of-the-book index. It's a second, separate structure that the database maintains alongside your data, which maps a property value to the nodes that have it. You don't query the index directly and you don't have to tell Cypher to use it. You create it once, and from then on the planner uses it when it helps.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943236492/9aecbf0a-5e0e-4993-aece-fa1b6d68adea.png" alt="index book analogy" style="display: block;" width="3280" height="1768" loading="lazy">

<p>On the left, <code>AllNodesScan</code>: sixty pages read, one of them useful, and the other fifty-nine still read. On the right, <code>NodeUniqueIndexSeek</code>: two reads, the index entry and then the page.</p>
<p>The figure also carries the number this handbook measures later, on the 75,500 node dataset: <strong>151,002 database accesses became 3.</strong> And the part worth remembering is that you never tell Cypher to use an index. You create it once, and from then on the planner reaches for it when it helps.</p>
<p>Here's the same lookup done three ways, against the 75,500 node dataset. All three find exactly one engineer, and all three return the same answer. What changes is how much work the database does to get there.</p>
<p><strong>One: no label, no index.</strong></p>
<pre><code class="language-cypher">PROFILE MATCH (n) WHERE n.email = 'eng25000@example.com' RETURN n.name
</code></pre>
<pre><code class="language-text">operator            details                       est     rows   dbHits
ProduceResults      `n.name`                     3775        1        0
  Projection        n.name AS `n.name`           3775        1        1
    Filter          n.email = $autostring_0      3775        1    75500
      AllNodesScan  n                           75500    75500    75501
</code></pre>
<p><code>AllNodesScan</code> is the database reading every node it has. All 75,500 of them, including every service, team, and incident, none of which could possibly have an email. Then <code>Filter</code> checks the email property on every one. <strong>Total: 151,002 database accesses to find one node.</strong></p>
<p><strong>Two: with a label, still no index.</strong></p>
<pre><code class="language-cypher">PROFILE MATCH (e:Engineer) WHERE e.email = 'eng25000@example.com' RETURN e.name
</code></pre>
<pre><code class="language-text">operator               details                    est     rows   dbHits
ProduceResults         `e.name`                  2500        1        0
  Projection           e.name AS `e.name`        2500        1        1
    Filter             e.email = $autostring_0   2500        1    50000
      NodeByLabelScan  e:Engineer               50000    50000    50001
</code></pre>
<p><code>NodeByLabelScan</code> is better. It reads only the 50,000 engineers instead of all 75,500 nodes. But it still reads every single one. <strong>Total: 100,002 accesses.</strong> The label narrowed the haystack. It didn't stop us searching it straw by straw.</p>
<p><strong>Three: with an index.</strong></p>
<pre><code class="language-cypher">CREATE CONSTRAINT engineer_email IF NOT EXISTS
FOR (e:Engineer) REQUIRE e.email IS UNIQUE
</code></pre>
<pre><code class="language-cypher">PROFILE MATCH (e:Engineer) WHERE e.email = 'eng25000@example.com' RETURN e.name
</code></pre>
<pre><code class="language-text">operator                 details                                        est   rows   dbHits
ProduceResults           `e.name`                                         1      1        0
  Projection             e.name AS `e.name`                               1      1        1
    NodeUniqueIndexSeek  UNIQUE e:Engineer(email) WHERE email = $auto      1      1        2
</code></pre>
<p>The scan and the filter are both gone, replaced by a single <code>NodeUniqueIndexSeek</code>. <strong>Total: 3 database accesses.</strong></p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943240334/ee25f5a1-c736-404e-90bf-79a5ac0ecf20.png" alt="scan vs seek ladder" style="display: block;" width="3360" height="1194" loading="lazy">

<p>Here we have one lookup done three ways, finding one engineer among 50,000 in a graph of 75,500 nodes, measured with PROFILE on Neo4j 5.26.29 Community. All three return the identical answer. What changes is the work: reading every node of the label, a scan narrowed by property, or an index seek straight to it.</p>
<p>Three, against a hundred and fifty-one thousand. That's the entire argument for indexes in one table:</p>
<table>
<thead>
<tr>
<th>How</th>
<th>Operator</th>
<th>Database accesses</th>
</tr>
</thead>
<tbody><tr>
<td>No label, no index</td>
<td><code>AllNodesScan</code></td>
<td>151,002</td>
</tr>
<tr>
<td>Label, no index</td>
<td><code>NodeByLabelScan</code></td>
<td>100,002</td>
</tr>
<tr>
<td>Index</td>
<td><code>NodeUniqueIndexSeek</code></td>
<td>3</td>
</tr>
</tbody></table>
<p>On my machine, that was 35.4 ms without the index and 4.0 ms with it, so about nine times faster.</p>
<p><strong>But</strong> <strong>be careful how you quote numbers like these.</strong> The database did 33,334 times less work, but it didn't run 33,334 times faster, because a single query also pays for connection handling, planning and returning the result, none of which the index changes. The work ratio is the durable claim. The speed ratio depends on your hardware, your cache, and what else the server is doing.</p>
<p><strong>You won't get nine.</strong> When I ran this same benchmark again from a clean checkout, the same query on the same data measured seventeen times faster rather than nine. The database access counts were identical to the digit: 100,002 and 3, both times.</p>
<p>That contrast is the entire point. Database accesses are a property of your data and your query, so they reproduce exactly. Milliseconds are a property of the machine you happened to run on, so they do not. When you're comparing two ways of writing a query, compare the accesses.</p>
<h3 id="heading-the-index-types-neo4j-gives-you">The Index Types Neo4j Gives You</h3>
<p>Most tutorials show you one kind of index and stop. Neo4j 5 has six, and picking the wrong one is the same as having none, because the planner will quietly ignore an index that can' t answer your predicate.</p>
<table>
<thead>
<tr>
<th>Type</th>
<th>Use it for</th>
<th>Created with</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Range</strong></td>
<td>Exact matches, ranges, <code>STARTS WITH</code>, sorting. The default.</td>
<td><code>CREATE INDEX ... FOR (n:Label) ON (n.prop)</code></td>
</tr>
<tr>
<td><strong>Text</strong></td>
<td><code>CONTAINS</code> and <code>ENDS WITH</code> on string properties</td>
<td><code>CREATE TEXT INDEX ...</code></td>
</tr>
<tr>
<td><strong>Point</strong></td>
<td>Distance and bounding box queries on geographic points</td>
<td><code>CREATE POINT INDEX ...</code></td>
</tr>
<tr>
<td><strong>Token lookup</strong></td>
<td>Finding nodes by label or relationships by type</td>
<td>Exists by default, two of them</td>
</tr>
<tr>
<td><strong>Full-text</strong></td>
<td>Searching <em>inside</em> text, ranked by relevance. Powered by Lucene.</td>
<td><code>CREATE FULLTEXT INDEX ...</code></td>
</tr>
<tr>
<td><strong>Vector</strong></td>
<td>Nearest-neighbour search over embeddings</td>
<td><code>CREATE VECTOR INDEX ...</code></td>
</tr>
</tbody></table>
<p>The one that catches people is the difference between range and text. A range index handles <code>STARTS WITH</code> perfectly well, because names sharing a prefix sit next to each other in sorted order, the same way "photosynthesis" and "photosphere" are neighbours in a book index. It cannot help with <code>CONTAINS</code> or <code>ENDS WITH</code>, because the thing you are searching for could be anywhere inside the value, and a sorted structure gives you no way to narrow that down. That's what a text index is for.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943242850/9746cba9-9e22-4e6c-a2bf-668e9f67e9a2.png" alt="index type decision" style="display: block;" width="3360" height="992" loading="lazy">

<p>We have six index types and the question each answers. The wrong type is the same as no index, because the planner quietly ignores an index that can't answer your predicate and nothing tells you it happened.</p>
<p>If you write no type at all, you get a range index, which is the right default for the overwhelming majority of cases:</p>
<pre><code class="language-cypher">CREATE INDEX service_tier IF NOT EXISTS FOR (s:Service) ON (s.tier)
</code></pre>
<p>You can also index more than one property at once, which is called a composite index:</p>
<pre><code class="language-cypher">CREATE INDEX service_tier_name IF NOT EXISTS FOR (s:Service) ON (s.tier, s.name)
</code></pre>
<p>A composite index isn't the same as two separate indexes. It's one structure sorted by tier first and then by name inside each tier, like a phone book ordered by city and then surname. It's excellent when you filter on both, and useless if you filter only on the second one, because you can't look up a surname in a phone book that is grouped by city without going through every city.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943245742/7d5c7c45-ee00-478b-8eed-05cbf3c04cd1.png" alt="composite index" style="display: block;" width="3280" height="1808" loading="lazy">

<p>A composite index covers a combination of properties, and their order decides which queries it serves. Filtering on the first property alone can use it. Filtering only on the second can't.</p>
<p>Relationships can be indexed too, using the same syntax with a relationship pattern:</p>
<pre><code class="language-cypher">CREATE INDEX owns_since IF NOT EXISTS FOR ()-[r:OWNS]-() ON (r.since)
</code></pre>
<p>To see what you have, ask:</p>
<pre><code class="language-cypher">SHOW INDEXES
</code></pre>
<h3 id="heading-why-your-index-isnt-being-used">Why Your Index Isn't Being Used</h3>
<p>An index that exists but is never used is the most frustrating case, because everything looks correct. There are four usual reasons, and a <code>PROFILE</code> tells you which one you have.</p>
<ol>
<li><p><strong>You indexed a different property from the one you filter on.</strong> An index on <code>email</code> does nothing for a query filtering on <code>name</code>.</p>
</li>
<li><p><strong>Your predicate can't use that index type.</strong> <code>CONTAINS</code> against a range index is the classic. The index exists, the planner looks at it, and correctly concludes it can't help.</p>
</li>
<li><p><strong>You wrapped the property in a function.</strong> <code>WHERE toLower(e.email) = 'x'</code> can't use an index on <code>e.email</code>, because the index stores the original values, not the lowercased ones. Store a normalised copy of the property and index that instead.</p>
</li>
<li><p><strong>You didn't give the node a label.</strong> Indexes are defined on a label. <code>MATCH (n) WHERE n.email = ...</code> has no label to work with, which is exactly why the first example above scanned every node in the database.</p>
</li>
</ol>
<h2 id="heading-constraints-and-the-trap-that-will-catch-you">Constraints, and the Trap That Will Catch You</h2>
<p>An index makes lookups fast. A <strong>constraint</strong> makes a rule impossible to break. They're different jobs, and the reason they get discussed together is that in Neo4j one of them quietly does the other.</p>
<p>Every <code>MERGE</code> has to check whether a matching node already exists. Without an index, that check scans every node carrying the label.</p>
<p>On a thousand nodes you won't notice. At a hundred thousand your import will crawl, and the reason won't be obvious because nothing is broken. It's simply doing an enormous amount of unnecessary work.</p>
<p>Create a uniqueness constraint on the property you merge on. It enforces correctness and creates the supporting index at the same time.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943247840/6b9f5808-3267-4998-aaab-f59c65c3e0ef.png" alt="constraint effect" style="display: block;" width="3360" height="1314" loading="lazy">

<p>Here we have two runs of the same existence check, before and after a constraint. Without one, answering "does this engineer already exist" means reading every Engineer node and comparing the email, keeping one match and discarding the rest, then doing it all again for the next row. The plan shows <code>NodeByLabelScan</code>. With a uniqueness constraint the database creates a supporting index, so it goes straight to the node or straight to nothing and never looks at the others. The plan shows <code>NodeUniqueIndexSeek</code>.</p>
<p>At a thousand nodes you won't notice. At a hundred thousand the import crawls and nothing in the output explains why. The cost is the same either way, so there is no reason to skip it.</p>
<p>To check what yours is doing, put PROFILE in front of the query and look at the bottom operator. <code>NodeByLabelScan</code> on a starting node almost always means a missing index, and it's the single most common finding in a slow Cypher query.</p>
<p>You can prove the second half of that sentence rather than take my word for it:</p>
<pre><code class="language-cypher">SHOW INDEXES YIELD name, type, owningConstraint
WHERE owningConstraint IS NOT NULL
RETURN name, type, owningConstraint
</code></pre>
<pre><code class="language-text">name             type     owningConstraint
engineer_email   RANGE    engineer_email
incident_ref     RANGE    incident_ref
service_name     RANGE    service_name
team_name        RANGE    team_name
</code></pre>
<p>Four constraints, four range indexes created automatically, each owned by its constraint. This is why the loading script in this article never creates those indexes separately: doing so would be redundant, and Neo4j would reject it as a conflict.</p>
<p>Neo4j offers four kinds of constraint:</p>
<table>
<thead>
<tr>
<th>Constraint</th>
<th>Enforces</th>
</tr>
</thead>
<tbody><tr>
<td><code>IS UNIQUE</code></td>
<td>No two nodes with this label share this property value</td>
</tr>
<tr>
<td><code>IS NOT NULL</code></td>
<td>The property must be present</td>
</tr>
<tr>
<td><code>IS NODE KEY</code></td>
<td>Both of the above, over one or more properties together</td>
</tr>
<tr>
<td><code>IS :: TYPE</code></td>
<td>The property must be of a given type, such as <code>STRING</code></td>
</tr>
</tbody></table>
<p><strong>Here's the trap:</strong> only the first one works on Neo4j Community Edition, which is what you get from the Docker image in this article. The other three are Enterprise features. Aura runs Enterprise, so they work there.</p>
<p>That means the same script can succeed against Aura and fail against your local Docker container, which is a genuinely confusing thing to hit when you are learning. This is what it looks like:</p>
<pre><code class="language-text">Neo.DatabaseError.Schema.ConstraintCreationFailed
Unable to create Constraint( type='NODE PROPERTY EXISTENCE', schema=(:Engineer {name}) ):
Property existence constraint requires Neo4j Enterprise Edition
</code></pre>
<p>That's not your mistake. It's an edition limit, and the message says so if you read to the end of the line.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943250520/20bcc7a4-5d0d-46a5-9e2d-1bc1840fa8a3.png" alt="constraint editions" style="display: block;" width="3360" height="1174" loading="lazy">

<p><code>IS UNIQUE</code> works on Community Edition, which is what the Docker image in this handbook gives you, and it also creates the backing index. The figure lists three others that Community refuses: <code>IS NOT NULL</code> for property existence, <code>IS NODE KEY</code> for unique-and-present across one or more properties, and a property type constraint such as requiring a STRING. All three need Enterprise.</p>
<p>Aura runs Enterprise, so the same script can succeed there and fail on your laptop. That isn't your mistake, and the refusal says so if you read to the end: <code>Neo.DatabaseError.Schema.ConstraintCreationFailed</code>, followed by the words Enterprise Edition.</p>
<p>Everything in this handbook uses only <code>IS UNIQUE</code>, so all of it runs on Community.</p>
<pre><code class="language-cypher">CREATE CONSTRAINT engineer_email IF NOT EXISTS
FOR (e:Engineer) REQUIRE e.email IS UNIQUE
</code></pre>
<p>Do this <strong>before</strong> you load, not after.</p>
<p>For properties you filter on frequently but which aren't unique, create a plain index:</p>
<pre><code class="language-cypher">CREATE INDEX service_tier IF NOT EXISTS
FOR (s:Service) ON (s.tier)
</code></pre>
<p>A sensible starting set for our model:</p>
<pre><code class="language-cypher">CREATE CONSTRAINT engineer_email IF NOT EXISTS FOR (e:Engineer) REQUIRE e.email IS UNIQUE;
CREATE CONSTRAINT service_name  IF NOT EXISTS FOR (s:Service)  REQUIRE s.name  IS UNIQUE;
CREATE CONSTRAINT incident_ref  IF NOT EXISTS FOR (i:Incident) REQUIRE i.ref   IS UNIQUE;
CREATE CONSTRAINT team_name     IF NOT EXISTS FOR (t:Team)     REQUIRE t.name  IS UNIQUE;
</code></pre>
<p>Run these from Python once at setup time:</p>
<pre><code class="language-python">CONSTRAINTS = [
    "CREATE CONSTRAINT engineer_email IF NOT EXISTS FOR (e:Engineer) REQUIRE e.email IS UNIQUE",
    "CREATE CONSTRAINT service_name  IF NOT EXISTS FOR (s:Service)  REQUIRE s.name  IS UNIQUE",
    "CREATE CONSTRAINT incident_ref  IF NOT EXISTS FOR (i:Incident) REQUIRE i.ref   IS UNIQUE",
    "CREATE CONSTRAINT team_name     IF NOT EXISTS FOR (t:Team)     REQUIRE t.name  IS UNIQUE",
]

for statement in CONSTRAINTS:
    driver.execute_query(statement, database_="neo4j")
</code></pre>
<p><code>IF NOT EXISTS</code> makes that block safe to run on every startup.</p>
<h2 id="heading-what-the-planner-does-with-your-query">What the Planner Does With Your Query</h2>
<p>Cypher is a declarative language. You describe the shape of the answer you want, and you never say how to find it. That's a real convenience, and it has one consequence worth understanding: something has to decide how.</p>
<p>That something is the <strong>query planner</strong>.</p>
<p>When you send a query, Neo4j parses it, then considers the different ways it could be executed. For our multi-hop query it could start from the incident and walk out to the engineers, or start from all the engineers and walk in towards the incident. Both produce identical results. One touches a handful of nodes and the other touches fifty thousand.</p>
<p>The planner picks between them using <strong>statistics</strong> it keeps about your data: how many nodes carry each label, how many relationships of each type exist, and how many distinct values a given indexed property has. From those it estimates how many rows each possible step would produce, and chooses the plan with the lowest estimated cost. This is why it is called a cost-based planner, and why the header of every plan says <code>Planner COST</code>.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943253280/c1482f32-db21-4249-80f2-f3234d4415e9.png" alt="planner pipeline" style="display: block;" width="3560" height="824" loading="lazy">

<p>Cypher is declarative, so you never say how to find anything. Something still chooses, and that choice is where fast and slow are decided. A query plan is that decision, written down.</p>
<p>The important consequence for you: <strong>the planner is guessing.</strong> Educated guessing, from real statistics, but guessing. When its guess is badly wrong, you get a slow query, and the plan is where you can see that happening.</p>
<h3 id="heading-explain-and-profile">EXPLAIN and PROFILE</h3>
<p>Two keywords let you see the plan, and the difference between them matters.</p>
<p><code>EXPLAIN</code> <strong>plans the query without running it.</strong> You get the operators the planner chose and its row estimates. Nothing is executed, nothing is read, and no data is changed. It costs essentially nothing, so you can use it on a query you suspect might run for an hour.</p>
<p><code>PROFILE</code> <strong>plans the query and then runs it.</strong> You get everything <code>EXPLAIN</code> gives you plus what actually happened: real row counts and real database hits per operator.</p>
<p>Here's the same query both ways.</p>
<pre><code class="language-cypher">EXPLAIN MATCH (e:Engineer)-[:OWNS]-&gt;(s:Service {tier:'critical'}) RETURN count(e) AS c
</code></pre>
<pre><code class="language-text">operator               details                       est   rows   dbHits
ProduceResults         c                               1      ?        ?
  EagerAggregation     count(e) AS c                   1      ?        ?
    Filter             e:Engineer                   2401      ?        ?
      Expand(All)      (s)&lt;-[anon_0:OWNS]-(e)       2401      ?        ?
        Filter         s.tier = $autostring_0        250      ?        ?
          NodeByLabelScan  s:Service                5000      ?        ?
</code></pre>
<p>Every <code>rows</code> and <code>dbHits</code> value is a question mark, because nothing ran. Now with <code>PROFILE</code>:</p>
<pre><code class="language-text">operator               details                       est   rows   dbHits
ProduceResults         c                               1      1        0
  EagerAggregation     count(e) AS c                   1      1        0
    Filter             e:Engineer                   2401   7573     7573
      Expand(All)      (s)&lt;-[anon_0:OWNS]-(e)       2401   7573    17871
        Filter         s.tier = $autostring_0        250    786     5000
          NodeByLabelScan  s:Service                5000   5000     5001
</code></pre>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943255710/52a4ec28-a29e-4b43-ab9a-67e73da2898a.png" alt="explain vs profile" style="display: block;" width="3360" height="1068" loading="lazy">

<p>EXPLAIN plans it, PROFILE runs it. Operators and estimates are identical because the planner decided the same either way. What EXPLAIN can't give you is what actually happened, which is the number you need when the estimate was wrong.</p>
<p>Use <code>EXPLAIN</code> when you want to know what the database intends to do, or when running the query would be expensive or destructive. Use <code>PROFILE</code> when you want to know what it actually did.</p>
<p><code>EXPLAIN</code> has a second use that's worth more than it sounds: it parses and plans without touching data, so it is the fastest possible check that a query is even valid. You can run every Cypher string in your codebase through <code>EXPLAIN</code> as a test, and catch typos and renamed properties before they reach production.</p>
<p>That's exactly what the <code>check_cypher.py</code> script in the <a href="https://github.com/ronidas39/knowledge-graph-python-neo4j">companion repository</a> does: it pulls every Cypher block out of this article, 39 of them, runs each through <code>EXPLAIN</code>, and fails if a single one is invalid.</p>
<h3 id="heading-reading-a-plan-start-at-the-bottom">Reading a Plan: Start at the Bottom</h3>
<p>This is the single thing that makes plans readable, and it's the opposite of what most people assume.</p>
<p><strong>A query plan is read from the bottom up.</strong> The bottom row is the leaf operator, where data enters. Each row above it receives rows from the row below, does something to them, and passes the result upward. The top row, always <code>ProduceResults</code>, is where the answer leaves the database.</p>
<p>So in the plan above, reading it the right way round:</p>
<ol>
<li><p><code>NodeByLabelScan</code> reads all 5,000 services. This is the leaf: it's where rows come from.</p>
</li>
<li><p><code>Filter</code> keeps only the critical ones, 786 of the 5,000.</p>
</li>
<li><p><code>Expand(All)</code> follows <code>OWNS</code> backwards from each of those to the engineers, producing 7,573 rows.</p>
</li>
<li><p><code>Filter</code> checks that each is really an <code>Engineer</code>.</p>
</li>
<li><p><code>EagerAggregation</code> counts them.</p>
</li>
<li><p><code>ProduceResults</code> hands back the single number.</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943257995/b90fdb81-ca67-4328-8eff-122d080087ea.png" alt="plan read bottom up" style="display: block;" width="3000" height="2048" loading="lazy">

<p>A plan is read from the bottom up. The bottom row is where rows enter, and each row above receives them, changes them and passes them on, up to <code>ProduceResults</code>. Reading it top down is why plans look like noise at first.</p>
<p>Indentation shows the parent and child relationship. An operator's children sit one level deeper than it does. Most operators have exactly one child. A few, like joins, have two, and their right-hand input is shown first and indented deeper.</p>
<h3 id="heading-what-the-columns-mean">What the Columns Mean</h3>
<table>
<thead>
<tr>
<th>Column</th>
<th>What it tells you</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Operator</strong></td>
<td>The kind of work being done: a scan, a seek, an expand, a filter</td>
</tr>
<tr>
<td><strong>Id</strong></td>
<td>A stable number for cross-referencing within this plan</td>
</tr>
<tr>
<td><strong>Details</strong></td>
<td>The specific thing: which label, which pattern, which predicate</td>
</tr>
<tr>
<td><strong>Estimated Rows</strong></td>
<td>How many rows the planner <em>thought</em> this step would produce</td>
</tr>
<tr>
<td><strong>Rows</strong></td>
<td>How many it <em>actually</em> produced. <code>PROFILE</code> only</td>
</tr>
<tr>
<td><strong>DB Hits</strong></td>
<td>How much work the storage engine did. <code>PROFILE</code> only</td>
</tr>
<tr>
<td><strong>Memory (Bytes)</strong></td>
<td>Peak memory for this operator. <code>PROFILE</code> only</td>
</tr>
<tr>
<td><strong>Page Cache Hits/Misses</strong></td>
<td>How often data was found in memory instead of on disk</td>
</tr>
</tbody></table>
<p>Two of these are misread often enough to be worth spelling out.</p>
<p><strong>DB hits aren't rows.</strong> A database hit counts low-level accesses in the storage engine: reading a node, reading a property, or reading an index entry. A single returned row can cost many hits. Look again at the <code>Expand(All)</code> line above: 7,573 rows, 17,871 hits. The row count is your result size, the hit count is the price you paid for it.</p>
<p><strong>Page cache hits and misses show whether the data was in memory.</strong> A miss means the database had to go to disk. On a first run against cold data you'll see mostly misses, and on a second run mostly hits, which is why comparing timings between a cold and a warm run tells you nothing useful. This column is an Enterprise Edition feature, so on the Community Docker image in this article it reads <code>0/0</code> throughout. That's not a bug and it doesn't mean your cache is empty.</p>
<h3 id="heading-the-most-useful-thing-in-the-whole-plan">The Most Useful Thing in the Whole Plan</h3>
<p>Compare <strong>Estimated Rows</strong> against <strong>Rows</strong>.</p>
<p>The estimate is what the planner believed when it chose this plan. The row count is the truth. When they're close, the planner made its decision with a good picture of your data. When they diverge badly, it chose a plan for a dataset that doesn't exist, and that's very often the real reason a query is slow.</p>
<p>Look at the numbers from the profile above:</p>
<table>
<thead>
<tr>
<th>Operator</th>
<th>Estimated</th>
<th>Actual</th>
<th>Off by</th>
</tr>
</thead>
<tbody><tr>
<td><code>NodeByLabelScan</code></td>
<td>5,000</td>
<td>5,000</td>
<td>correct</td>
</tr>
<tr>
<td><code>Filter</code> on <code>tier</code></td>
<td>250</td>
<td>786</td>
<td>3.1x under</td>
</tr>
<tr>
<td><code>Expand(All)</code></td>
<td>2,401</td>
<td>7,573</td>
<td>3.2x under</td>
</tr>
</tbody></table>
<p>The planner guessed that filtering services down to the critical ones would leave 250 of 5,000. In our data it leaves 786, because roughly 15% of services are critical rather than the 5% its default assumption implies. That error then flows upward: because it expected 250 services it expected about 2,401 engineers, and got 7,573.</p>
<p>Here the consequence is harmless. On a bigger query, a three-fold underestimate at the bottom of a plan is exactly how the planner talks itself into a strategy that falls apart, because it believed it was joining a small thing to a big thing when it was really joining two big things.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943260911/c197f4b6-3fba-40f4-aabe-c8e1ed9fcae3.png" alt="estimated vs actual" style="display: block;" width="3360" height="1098" loading="lazy">

<p>Estimated Rows is what the planner believed when it chose this plan. Rows is what happened. Where they diverge is usually where a slow query is explained, because the planner optimised for a shape the data didn't have.</p>
<p>If estimates are consistently wrong across your queries, the statistics behind them may be stale.</p>
<p><strong>So the habit worth building is:</strong> run <code>PROFILE</code>, read from the bottom, and check the estimate against the truth at every step. You aren't looking for a big number. You're looking for the first place the planner was surprised.</p>
<h3 id="heading-three-tells-worth-recognising">Three Tells Worth Recognising</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943263531/1567ddd5-ad42-48d4-9f41-242d1a0b0ff9.png" alt="profile plan" style="display: block;" width="4400" height="1360" loading="lazy">

<p>This is PROFILE output in Neo4j Browser, showing the operator chain with estimated and actual row counts beside each step. This is the real output the <code>NodeUniqueIndexSeek</code> explanation refers to.</p>
<p>Beyond the estimate check, three specific things in a plan should catch your eye.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943266391/78671cc3-1f92-4d7b-9bd0-f4570a71069c.png" alt="plan tells" style="display: block;" width="3400" height="2128" loading="lazy">

<p>What specific operators tell you when you see them. <code>NodeByLabelScan</code> on a starting node means no index is being used. Each entry pairs the symptom with the cause and the fix.</p>
<p><code>NodeByLabelScan</code> means the database read every node with that label. On a starting node this almost always means a missing index. It's the single most common finding.</p>
<p><strong>A row count that explodes and then collapses:</strong> if one step produces two hundred thousand rows and the next reduces it to forty, you're generating work and throwing it away. Usually the pattern can be reordered so the selective part happens first.</p>
<p><code>CartesianProduct</code> means two parts of your pattern aren't connected, so the database is combining every row on the left with every row on the right. It's nearly always an accident, and it's nearly always the reason a query went from milliseconds to minutes.</p>
<p>All three have the same shape as a fix: give the planner a cheaper way in. An index turns a scan into a seek, a reordered pattern makes the selective step happen first, and a missing relationship in the pattern removes the cartesian product.</p>
<h2 id="heading-six-problems-youll-actually-hit">Six Problems You'll Actually Hit</h2>
<p>These are the ones that cost people an afternoon. None of them produce an obvious error message, which is exactly why they're worth listing.</p>
<h3 id="heading-the-query-returns-nothing-and-you-expected-rows">The Query Returns Nothing and You Expected Rows</h3>
<p>Check your arrow directions first. <code>(a)-[:OWNS]-&gt;(b)</code> and <code>(a)&lt;-[:OWNS]-(b)</code> are different questions, and the second one is what you want when you're starting from the thing that's owned. If you're unsure, drop the arrowheads entirely and use <code>-[:OWNS]-</code>, which matches either direction. If rows appear, direction was the problem.</p>
<h3 id="heading-the-query-returns-fewer-rows-than-the-truth">The Query Returns Fewer Rows Than the Truth</h3>
<p>This is the relationship uniqueness trap from earlier in this handbook. If a pattern leaves a node and comes back to the same kind of node, and both halves could be the same relationship, Cypher discards those matches without a word. Split the pattern with <code>WITH</code>.</p>
<h3 id="heading-a-query-that-was-instant-is-suddenly-slow">A Query That Was Instant is Suddenly Slow</h3>
<p>Look for <code>CartesianProduct</code> in <code>PROFILE</code>. It means two parts of your pattern aren't connected to each other, so every row on the left is being combined with every row on the right. Usually a variable was forgotten, or two <code>MATCH</code> clauses were written where one pattern was meant.</p>
<h3 id="heading-merge-created-a-duplicate">MERGE Created a Duplicate</h3>
<p>You merged on more than the identifying property. <code>MERGE (e:Engineer {email: $email, name: $name})</code> treats a changed name as a different node. Merge on identity, then <code>SET</code> the rest.</p>
<h3 id="heading-merge-is-unbearably-slow">MERGE is Unbearably Slow</h3>
<p>You have no index on the property you merge on, so every merge scans every node with that label. Create the constraint before loading, not after.</p>
<h3 id="heading-the-whole-import-ran-out-of-memory">The Whole Import Ran Out of Memory</h3>
<p>You put everything in one transaction. Batch it. A few thousand rows per transaction is a sane default, and <code>CALL { ... } IN TRANSACTIONS</code> lets Cypher do the batching for you inside a single query.</p>
<p>Here's a short checklist worth keeping next to you:</p>
<table>
<thead>
<tr>
<th>Symptom</th>
<th>First thing to check</th>
</tr>
</thead>
<tbody><tr>
<td>No rows</td>
<td>Arrow direction</td>
</tr>
<tr>
<td>Too few rows</td>
<td>Relationship uniqueness, split with <code>WITH</code></td>
</tr>
<tr>
<td>Sudden slowness</td>
<td><code>PROFILE</code> for <code>CartesianProduct</code></td>
</tr>
<tr>
<td>Duplicate nodes</td>
<td>Merging on more than the identity</td>
</tr>
<tr>
<td>Slow <code>MERGE</code></td>
<td>Missing constraint or index</td>
</tr>
<tr>
<td>Out of memory</td>
<td>One giant transaction</td>
</tr>
</tbody></table>
<h2 id="heading-transactions-and-what-happens-when-things-fail">Transactions and What Happens When Things Fail</h2>
<p><code>execute_query</code> wraps each call in its own transaction and retries it automatically if it hits a transient error such as a leader election in a cluster. For the majority of work, that's exactly what you want and you don't need to think about it.</p>
<p>Here's what actually happens across the driver, the session and the database, including the case everyone worries about: a write that fails halfway.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943270013/1636e385-6717-4a1c-a397-a1eb77ec6c24.png" alt="transaction lifecycle" style="display: block;" width="3000" height="1902" loading="lazy">

<p>From your code through the driver and session to Neo4j. One driver per application with <code>GraphDatabase.driver(uri, auth)</code>, then a session per unit of work. The session is cheap and short-lived, the driver expensive and long-lived, and swapping those round is a common cause of slow applications.</p>
<p>The important part is the middle. Once a transaction begins, nothing it has written is visible or durable until it commits. A failure at step nine doesn't leave you with half a graph, it leaves you with the graph you started with.</p>
<p>When you need several statements to succeed or fail together, manage the transaction yourself:</p>
<pre><code class="language-python">def reassign_service(tx, service, from_email, to_email):
    tx.run(
        """
        MATCH (:Engineer {email: $from_email})-[r:OWNS]-&gt;(s:Service {name: $service})
        DELETE r
        """,
        from_email=from_email, service=service,
    )
    tx.run(
        """
        MATCH (e:Engineer {email: $to_email}), (s:Service {name: $service})
        MERGE (e)-[:OWNS {since: date()}]-&gt;(s)
        """,
        to_email=to_email, service=service,
    )

with driver.session(database="neo4j") as session:
    session.execute_write(reassign_service, "payments", "ada@example.com", "grace@example.com")
</code></pre>
<p><code>execute_write</code> runs your function inside one transaction. If any statement raises, the whole thing rolls back and the graph is left as it was. It also retries the function on transient failures, which is why the work goes in a function rather than inline: it may be executed more than once, so it must be safe to repeat.</p>
<p>That last point is worth saying plainly: <strong>any function you hand to</strong> <code>execute_write</code> <strong>must be idempotent</strong>, which means running it twice has the same effect as running it once. A retry starts your function again from the top, so anything that increments a counter or appends to a list will do it twice. This is another reason to reach for <code>MERGE</code> rather than <code>CREATE</code> inside one.</p>
<h2 id="heading-testing-code-that-talks-to-a-graph">Testing Code That Talks to a Graph</h2>
<p>Graph code is easy to write and easy to get subtly wrong, as the relationship uniqueness trap earlier in this handbook showed. Tests are how you find that class of bug once rather than repeatedly.</p>
<h3 id="heading-dont-mock-the-database">Don't Mock the Database</h3>
<p>The temptation is to mock the driver and assert that your function called it with a particular string. Resist it. That test passes when your Cypher is wrong, which is precisely the failure you need to catch. The bugs in graph code are almost never in the Python around the query. They're in the query.</p>
<p>Run tests against a real Neo4j. It starts in seconds in Docker, and the whole point is to exercise the query engine.</p>
<h3 id="heading-give-each-test-a-clean-graph">Give Each Test a Clean Graph</h3>
<pre><code class="language-python">import os
import pytest
from neo4j import GraphDatabase

@pytest.fixture(scope="session")
def driver():
    d = GraphDatabase.driver(
        os.environ.get("NEO4J_TEST_URI", "bolt://localhost:7687"),
        auth=("neo4j", os.environ["NEO4J_TEST_PASSWORD"]),
    )
    d.verify_connectivity()
    yield d
    d.close()

@pytest.fixture(autouse=True)
def clean(driver):
    """Wipe before every test so tests cannot leak into each other."""
    driver.execute_query("MATCH (n) DETACH DELETE n", database_="neo4j")
</code></pre>
<p>The driver is created once for the whole session, because it's expensive. The wipe runs before every test, because a test that depends on another test's leftovers will pass alone and fail in a suite.</p>
<h3 id="heading-test-the-thing-that-actually-broke">Test the Thing That Actually Broke</h3>
<p>A useful test is one that would have caught a real bug. Here's the one for the trap from earlier:</p>
<pre><code class="language-python">def test_teams_includes_a_team_whose_only_member_is_the_owner(driver):
    driver.execute_query(
        """
        MERGE (e:Engineer {email: 'linus@example.com'}) SET e.name = 'Linus'
        MERGE (s:Service {name: 'checkout'})
        MERGE (t:Team {name: 'Commerce'})
        MERGE (i:Incident {ref: 'INC-1'})
        MERGE (e)-[:OWNS]-&gt;(s)
        MERGE (e)-[:MEMBER_OF]-&gt;(t)
        MERGE (i)-[:AFFECTS]-&gt;(s)
        """,
        database_="neo4j",
    )

    teams = teams_involved(driver, "INC-1")

    # The single-pattern version returns [] here, with no error at all.
    assert [t["team"] for t in teams] == ["Commerce"]
</code></pre>
<p>That test is worth more than a dozen tests of your Python. It encodes a specific, silent, hard-to-spot failure, and it will fail loudly if anyone ever "simplifies" the query back into one pattern.</p>
<h3 id="heading-assert-on-counts-as-well-as-contents">Assert on Counts as Well as Contents</h3>
<p>Silent under-fetching is the characteristic graph bug, so assert how many rows you got, not only that the ones you got look right:</p>
<pre><code class="language-python">def test_load_is_idempotent(driver):
    load(driver)
    _, summary, _ = driver.execute_query(
        "MATCH (e:Engineer) RETURN count(e) AS c", database_="neo4j"
    )
    first = driver.execute_query("MATCH (e:Engineer) RETURN count(e) AS c", database_="neo4j")[0][0]["c"]

    load(driver)   # run it again
    second = driver.execute_query("MATCH (e:Engineer) RETURN count(e) AS c", database_="neo4j")[0][0]["c"]

    assert first == second, "loading twice created duplicates, so a MERGE key is wrong"
</code></pre>
<p>That single assertion catches the most expensive loading mistake there is, which is merging on more than the identifying property.</p>
<p>The same graph, seen as a data model in Neo4j Browser against the live Aura instance:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943272688/17cd6c67-22bb-4049-9095-2ef5916a558f.png" alt="data model" style="display: block;" width="4400" height="1360" loading="lazy">

<p><code>CALL db.schema.visualization()</code> running in the Aura console, which draws the shape of whatever is currently in the database. It shows four node labels, <code>Engineer</code>, <code>Incident</code>, <code>Service</code> and <code>Team</code>, joined by four relationship types: an incident <code>AFFECTS</code> a service, a service <code>DEPENDS_ON</code> another service, an engineer <code>OWNS</code> a service, and an engineer is a <code>MEMBER_OF</code> a team. The property keys in use are <code>email</code>, <code>name</code>, <code>ref</code> and <code>summary</code>.</p>
<p>This is the same model you built locally, running on the managed service, and it's a quick way to check that a load did what you expected.</p>
<h2 id="heading-from-graph-to-knowledge-graph">From Graph to Knowledge Graph</h2>
<p>Everything so far has been a graph database. A <strong>knowledge graph</strong> is what you get when the nodes represent real entities from your domain and the relationships represent meaningful facts about them, so that the graph itself is a model of what you know.</p>
<p>The step up from one to the other is mostly about where the data comes from. Instead of loading rows from a table, you extract entities and relationships from documents, tickets, wikis, code, or conversations.</p>
<p>The mechanics you've already learned don't change:</p>
<pre><code class="language-python">def add_fact(driver, subject, predicate_service, source_doc):
    driver.execute_query(
        """
        MERGE (e:Engineer {email: $subject})
        MERGE (s:Service {name: $service})
        MERGE (e)-[r:OWNS]-&gt;(s)
          ON CREATE SET r.source = $source, r.extracted = datetime()
        """,
        subject=subject, service=predicate_service, source=source_doc,
        database_="neo4j",
    )
</code></pre>
<p>Notice <code>r.source</code>. When facts are extracted rather than entered, <strong>recording where each fact came from isn't optional</strong>. You'll need it the first time somebody asks why the graph believes something, and you'll need it when a source document is corrected and you have to find everything derived from it.</p>
<p>Two habits make extracted graphs survivable:</p>
<ul>
<li><p><strong>Store provenance on the relationship.</strong> Which document, which version, when.</p>
</li>
<li><p><strong>Keep extraction idempotent.</strong> Re-running over the same document must not duplicate facts, which is exactly what <code>MERGE</code> on an identifying property gives you.</p>
</li>
</ul>
<h2 id="heading-why-ai-systems-keep-rediscovering-graphs">Why AI Systems Keep Rediscovering Graphs</h2>
<p>This is the part that makes graphs suddenly relevant to people who have never touched one.</p>
<p>The standard way to give a language model access to your data is to embed your documents as vectors and retrieve the chunks most similar to the question. This works well, and it fails in a specific and predictable way.</p>
<p>Similarity retrieval can tell you that two things are related. It can't tell you how.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943275441/b6277b72-99cc-4f1c-b55e-6a1037e21ae6.png" alt="vector vs graph" style="display: block;" width="3360" height="1618" loading="lazy">

<p>This is why neither retrieval method is enough alone, and what order to combine them in.</p>
<p>Vector search alone finds four documents that are each related to the question and none of which contain the answer. The chain from incident to service to owner to team spans all four, so no single chunk holds it and nothing scores highly enough to be retrieved together.</p>
<p>Graph traversal alone is exact once it starts: hop one goes from the incident to payments and checkout, hop two to Ada and Grace, hop three to the Platform team. The problem is starting, because "last night's payments incident" is a phrase, not a node, and the graph has never seen that wording.</p>
<p>Used together, in order: embed the question and find which entities it's about, which handles wording the graph has never seen. Traverse out from those entities, where relationships are stored so the chain is read rather than inferred. Hand back a small, precise set of facts with their provenance instead of five paragraphs of loosely related prose.</p>
<p>Similarity search can tell you that two things are related. It can't tell you how, which is why these answers degrade into confident guesses exactly when the reasoning gets interesting.</p>
<p>Ask "who should I talk to about last night's payments incident" and a vector store returns the chunks that look most like that sentence. It has no representation of the fact that the incident affected a service, that the service is owned by an engineer, and that the engineer is on a team. Each of those facts might live in a different document, and no single chunk contains the chain.</p>
<p>A graph stores the chain explicitly. Multi-hop questions become traversals, and the answer is derived rather than guessed.</p>
<p>The two aren't rivals, and treating them as rivals is a mistake. The pattern that works in practice is to use both:</p>
<table>
<thead>
<tr>
<th>Job</th>
<th>Best tool</th>
<th>Why</th>
</tr>
</thead>
<tbody><tr>
<td>Find the entry point from fuzzy language</td>
<td>Vector search</td>
<td>Handles wording the graph has never seen</td>
</tr>
<tr>
<td>Traverse from that entry point to related facts</td>
<td>Graph</td>
<td>Relationships are stored, not inferred</td>
</tr>
<tr>
<td>Answer "what is connected to what, and how"</td>
<td>Graph</td>
<td>Paths are the query</td>
</tr>
<tr>
<td>Answer "what does this passage say"</td>
<td>Vector search</td>
<td>The text is the answer</td>
</tr>
</tbody></table>
<p>In practice the pattern is: embed the text, use similarity to work out <strong>which entities</strong> the question is about, then traverse the graph from those entities to assemble the context you hand to the model.</p>
<p>Neo4j can hold the vectors too, which keeps both halves in one place. You create a vector index over a property holding the embedding:</p>
<pre><code class="language-cypher">CREATE VECTOR INDEX service_notes IF NOT EXISTS
FOR (s:Service) ON (s.embedding)
OPTIONS {indexConfig: {
  `vector.dimensions`: 1536,
  `vector.similarity_function`: 'cosine'
}}
</code></pre>
<p>Then the hybrid query becomes one round trip: similarity finds the entry points, and the traversal does the rest.</p>
<pre><code class="language-python">def context_for_question(driver, question_embedding, k=3):
    records, _, _ = driver.execute_query(
        """
        // 1. vector search finds the services the question is about
        CALL db.index.vector.queryNodes('service_notes', $k, $embedding)
        YIELD node AS s, score

        // 2. the graph supplies what similarity cannot: how things connect
        OPTIONAL MATCH (s)&lt;-[:OWNS]-(owner:Engineer)-[:MEMBER_OF]-&gt;(t:Team)
        OPTIONAL MATCH (s)&lt;-[:AFFECTS]-(i:Incident)
        RETURN s.name AS service, score,
               collect(DISTINCT owner.name) AS owners,
               collect(DISTINCT t.name)     AS teams,
               collect(DISTINCT i.ref)      AS incidents
        ORDER BY score DESC
        """,
        embedding=question_embedding, k=k, database_="neo4j",
    )
    return [dict(r) for r in records]
</code></pre>
<p>Read what each half contributes. The vector index answers "which services does this question seem to be about", which a graph alone can't do because the user's wording won't match your node names.</p>
<p>The traversal then answers "who owns them, which teams, what broke recently", which similarity alone can't do because those facts live in different documents and no single chunk contains the chain.</p>
<p>The result you hand the model is a small, precise set of connected facts rather than five paragraphs of loosely related prose. That's usually the difference between an answer and a plausible guess.</p>
<p><strong>A note on honesty in the output:</strong> because every fact came out of the graph, you can cite it. Passing the relationship provenance along with the facts lets the model say where each claim came from, and lets you check it when it gets one wrong.</p>
<p>The same argument explains why durable memory for AI agents keeps ending up shaped like a graph.</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943278708/fca8d9a1-5c17-46a7-91de-4cd7866ce6cb.png" alt="agent memory graph" style="display: block;" width="3360" height="1530" loading="lazy">

<p>The example is three notes. <code>note-03</code> says "We decided to use Mongo for payments", <code>note-09</code> says "Mira moved payments onto Postgres", <code>note-14</code> says "Payments storage reviewed, no action". Ask "what database does payments use" and, as loose text, all three look equally relevant, so the agent picks one.</p>
<p>Drawn as a graph, the newer Decision node <code>use Postgres</code> has a <code>SUPERSEDES</code> edge pointing at the Mongo decision and an <code>APPLIES_TO</code> edge pointing at the payments Service. The ordering that was invisible in prose is now a stored fact the agent can follow.</p>
<p>An agent that remembers needs to know that a decision was made, who made it, what it superseded, and what depends on it. Those are relationships with direction and properties. Storing them as loose text and hoping similarity search reconstructs them is how agents end up confidently contradicting themselves.</p>
<p>None of this requires new skills. It's the same modeling discipline from earlier in this handbook, applied to facts extracted from text instead of rows from a table. Which is why the modeling section is the one worth re-reading.</p>
<h2 id="heading-building-a-knowledge-graph-from-text">Building a Knowledge Graph from Text</h2>
<p>So far every fact arrived as a tidy Python dictionary. Real knowledge graphs are usually built from prose: incident write-ups, wiki pages, tickets, commit messages, and support threads.</p>
<p>The extraction step is where people either build something durable or build a mess. Three rules keep it durable, and here's where each of them sits in the pipeline:</p>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943281113/e7348bd7-2004-47ce-b4aa-a68f96791604.png" alt="ingestion pipeline" style="display: block;" width="3360" height="550" loading="lazy">

<p>Raw text goes to an extractor, which produces candidate entities and relationships, which are merged into the graph. The stages are separable, which matters because the extractor is the part you'll swap and re-run.</p>
<p>Notice where the gate is. The schema check happens <strong>before</strong> anything is written, not after. Once an invented relationship type is in the graph it's indistinguishable from a real one, and you'll be cleaning it up by hand.</p>
<h3 id="heading-rule-1-extract-into-a-fixed-schema-not-a-free-for-all">Rule #1: Extract into a Fixed Schema, Not a Free-for-All</h3>
<p>If you let an extractor invent relationship types, you'll end up with <code>OWNS</code>, <code>owns</code>, <code>IS_OWNER_OF</code> and <code>RESPONSIBLE_FOR</code> all meaning the same thing, and no query will ever find all four.</p>
<p>Decide your vocabulary first, and make the extractor choose from it:</p>
<pre><code class="language-python">NODE_LABELS = ["Engineer", "Service", "Incident", "Team"]
REL_TYPES = ["OWNS", "AFFECTS", "MEMBER_OF", "DEPENDS_ON"]
</code></pre>
<p>Whatever does the extraction (a language model, a regex, or a human), its job is to emit triples that use only those names. Anything else gets rejected rather than written.</p>
<h3 id="heading-rule-2-every-extracted-fact-carries-its-source">Rule #2: Every Extracted Fact Carries its Source</h3>
<img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1786943283291/d5254326-f4eb-45d5-a6a1-148d42f7c0f9.png" alt="extraction provenance" style="display: block;" width="3360" height="2128" loading="lazy">

<p>Three stages, left to right: documents go in, extraction emits triples using a fixed vocabulary, and the merge records where each fact came from.</p>
<p>The detail the drawing turns on is the split between <code>ON CREATE</code> and <code>ON MATCH</code>. The source is written once, when the fact is first created, while the freshness timestamp updates every time the same fact is seen again. That way re-running over the same document doesn't overwrite the original provenance.</p>
<p>It pays off when a document turns out to be wrong, because matching on the source property lets you retract every fact that came from it in one query. The step people skip is the confidence score: store it, then actually use it downstream, because a guess at 0.4 must not read as a confirmed fact.</p>
<p>When a human types data in, you can ask them. When a machine extracts it, you can't, and someone will eventually ask "why does the graph think Ada owns checkout?"</p>
<pre><code class="language-python">def write_triple(driver, subject_email, rel_type, object_name, source_doc, confidence):
    if rel_type not in REL_TYPES:
        raise ValueError(f"refusing unknown relationship type: {rel_type}")

    driver.execute_query(
        f"""
        MERGE (e:Engineer {{email: $subject}})
        MERGE (s:Service {{name: $object}})
        MERGE (e)-[r:{rel_type}]-&gt;(s)
          ON CREATE SET r.source = $source,
                        r.confidence = $confidence,
                        r.extracted_at = datetime()
          ON MATCH  SET r.last_seen = datetime()
        """,
        subject=subject_email, object=object_name,
        source=source_doc, confidence=confidence,
        database_="neo4j",
    )
</code></pre>
<p>Two things about that snippet deserve a warning.</p>
<p>The relationship type is the <strong>one</strong> thing in Cypher you can't pass as a parameter. <code>-[r:$type]-&gt;</code> isn't valid, which is why it's interpolated into the string.</p>
<p>That's exactly the pattern that causes injection bugs, so the <code>if rel_type not in REL_TYPES</code> check above it is not decoration. It's the only thing making the interpolation safe. Never build that string from raw model output without checking it against a fixed list first.</p>
<p><code>ON CREATE</code> and <code>ON MATCH</code> let you record provenance once and freshness every time, which means re-running extraction over the same document does not overwrite the original source.</p>
<h3 id="heading-rule-3-make-re-extraction-safe">Rule #3: make Re-extraction Safe</h3>
<p>You will re-run extraction. Documents get corrected, your prompt improves, or a bug gets fixed. If a second run duplicates everything, the graph is worthless.</p>
<p>Because every write above is a <code>MERGE</code> on an identifying property, re-running is safe by construction. That's the same idempotency property from the loading section, and it matters far more here.</p>
<p>To retract facts from a document that has changed:</p>
<pre><code class="language-cypher">MATCH ()-[r]-&gt;()
WHERE r.source = $source_doc
DELETE r
</code></pre>
<p>Then re-extract. Deleting by source is only possible because you stored the source, which is the whole argument for rule two.</p>
<h3 id="heading-a-caution-on-confidence">A Caution on Confidence</h3>
<p>If your extractor emits a confidence score, store it, and then <strong>actually use it</strong>. A graph that mixes facts a human confirmed with facts a model guessed at 0.4 confidence, and treats them identically at query time, will produce confident wrong answers.</p>
<pre><code class="language-cypher">MATCH (e:Engineer)-[r:OWNS]-&gt;(s:Service)
WHERE r.confidence IS NULL OR r.confidence &gt; 0.8
RETURN e.name, s.name
</code></pre>
<p><code>r.confidence IS NULL</code> keeps the hand-entered facts, which have no score because nobody guessed them.</p>
<h2 id="heading-the-complete-script">The Complete Script</h2>
<p>Here's everything from this handbook as one runnable file. It creates the constraints, loads the data, and answers the question from the introduction. If you've followed along, this is the whole thing in one place.</p>
<pre><code class="language-python">"""A minimal knowledge graph, end to end."""

import os
from neo4j import GraphDatabase

URI = os.environ.get("NEO4J_URI", "bolt://localhost:7687")
AUTH = (
    os.environ.get("NEO4J_USER", "neo4j"),
    os.environ["NEO4J_PASSWORD"],
)

CONSTRAINTS = [
    "CREATE CONSTRAINT engineer_email IF NOT EXISTS FOR (e:Engineer) REQUIRE e.email IS UNIQUE",
    "CREATE CONSTRAINT service_name  IF NOT EXISTS FOR (s:Service)  REQUIRE s.name  IS UNIQUE",
    "CREATE CONSTRAINT incident_ref  IF NOT EXISTS FOR (i:Incident) REQUIRE i.ref   IS UNIQUE",
    "CREATE CONSTRAINT team_name     IF NOT EXISTS FOR (t:Team)     REQUIRE t.name  IS UNIQUE",
]

PEOPLE = [
    {"email": "ada@example.com",   "name": "Ada Okonjo",   "service": "payments", "team": "Platform"},
    {"email": "grace@example.com", "name": "Grace Lin",    "service": "payments", "team": "Platform"},
    {"email": "linus@example.com", "name": "Linus Berg",   "service": "checkout", "team": "Commerce"},
    {"email": "mira@example.com",  "name": "Mira Haddad",  "service": "auth",     "team": "Platform"},
    {"email": "tom@example.com",   "name": "Tom Ferreira", "service": "search",   "team": "Discovery"},
]

# One engineer who owns nothing, so the OPTIONAL MATCH example has something to
# show. Without her, that query looks identical to a plain MATCH.
UNASSIGNED = {"email": "nadia@example.com", "name": "Nadia Rossi"}

# Service dependencies, which the variable length path example walks.
DEPENDENCIES = [
    {"upstream": "auth",     "downstream": "payments"},
    {"upstream": "auth",     "downstream": "checkout"},
    {"upstream": "payments", "downstream": "checkout"},
    {"upstream": "search",   "downstream": "checkout"},
]

INCIDENT = {"ref": "INC-4471", "summary": "Elevated 5xx on card capture",
            "services": ["payments", "checkout"]}


def setup(driver):
    """Constraints first. They enforce correctness and create the indexes
    that stop MERGE from scanning every node."""
    for statement in CONSTRAINTS:
        driver.execute_query(statement, database_="neo4j")


def load(driver):
    """People and teams, then the unassigned engineer, then dependencies,
    then the incident. Four round trips for the whole dataset."""
    driver.execute_query(
        """
        UNWIND $rows AS row
        MERGE (e:Engineer {email: row.email})
          SET e.name = row.name
        MERGE (s:Service {name: row.service})
        MERGE (t:Team {name: row.team})
        MERGE (e)-[:OWNS]-&gt;(s)
        MERGE (e)-[:MEMBER_OF]-&gt;(t)
        """,
        rows=PEOPLE, database_="neo4j",
    )
    driver.execute_query(
        "MERGE (e:Engineer {email: $email}) SET e.name = $name",
        **UNASSIGNED, database_="neo4j",
    )
    driver.execute_query(
        """
        UNWIND $rows AS row
        MATCH (u:Service {name: row.upstream}), (d:Service {name: row.downstream})
        MERGE (d)-[:DEPENDS_ON]-&gt;(u)
        """,
        rows=DEPENDENCIES, database_="neo4j",
    )
    driver.execute_query(
        """
        MERGE (i:Incident {ref: $ref}) SET i.summary = $summary
        WITH i
        UNWIND $services AS svc
        MATCH (s:Service {name: svc})
        MERGE (i)-[:AFFECTS]-&gt;(s)
        """,
        **INCIDENT, database_="neo4j",
    )


def who_has_context(driver, ref):
    """The question from the introduction, in one pattern."""
    records, _, _ = driver.execute_query(
        """
        MATCH (i:Incident {ref: $ref})-[:AFFECTS]-&gt;(:Service)&lt;-[:OWNS]-(e:Engineer)
        RETURN DISTINCT e.name AS name, e.email AS email
        ORDER BY name
        """,
        ref=ref, database_="neo4j",
    )
    return [dict(r) for r in records]


def teams_involved(driver, ref):
    """Split into two patterns on purpose. A single pattern would hit the
    relationship uniqueness rule and silently drop any team whose only
    member is also the owner."""
    records, _, _ = driver.execute_query(
        """
        MATCH (i:Incident {ref: $ref})-[:AFFECTS]-&gt;(:Service)&lt;-[:OWNS]-(:Engineer)-[:MEMBER_OF]-&gt;(t:Team)
        WITH DISTINCT t
        MATCH (t)&lt;-[:MEMBER_OF]-(e:Engineer)
        RETURN t.name AS team, collect(e.name) AS members
        ORDER BY team
        """,
        ref=ref, database_="neo4j",
    )
    return [dict(r) for r in records]


def main():
    with GraphDatabase.driver(URI, auth=AUTH) as driver:
        driver.verify_connectivity()
        setup(driver)
        load(driver)

        print("Engineers with context on INC-4471:")
        for row in who_has_context(driver, "INC-4471"):
            print(f"  {row['name']:&lt;14} {row['email']}")

        print("\nTeams involved:")
        for row in teams_involved(driver, "INC-4471"):
            print(f"  {row['team']:&lt;10} {', '.join(row['members'])}")


if __name__ == "__main__":
    main()
</code></pre>
<p>Run it with your password in the environment rather than in the file:</p>
<pre><code class="language-bash">export NEO4J_PASSWORD='your-password'
python3 knowledge_graph.py
</code></pre>
<p>Note <code>os.environ["NEO4J_PASSWORD"]</code> with square brackets rather than <code>.get()</code>. That's deliberate. It fails loudly at startup if the variable is missing, instead of quietly trying to connect with <code>None</code> and giving you a confusing authentication error.</p>
<h2 id="heading-where-to-go-next">Where to Go Next</h2>
<p>You now have the pieces that matter: a data model you can defend, a loading script that's safe to re-run, queries that traverse instead of joining, indexes that keep them fast, and a way to find out why something is slow.</p>
<p>Here are three suggestions for what to do with that:</p>
<p><strong>Start with a domain you already understand.</strong> Modeling is the hard part, and it's far easier to judge whether a model is right when you already know what questions the data should answer. Your own codebase, your team's services, or your reading list are all better first projects than a dataset you downloaded.</p>
<p><strong>Write the questions before the model.</strong> It takes ten minutes and it will save you a rewrite. This remains the single highest-leverage habit in this whole handbook.</p>
<p><strong>Then point something at it that's not a person.</strong> Once your data is modeled properly, wiring a language model to traverse it is a much smaller step than it sounds, because the hard part was never the model. It was knowing what the things are and how they connect.</p>
<p><strong>The companion repository is</strong> <a href="https://github.com/ronidas39/knowledge-graph-python-neo4j"><strong>github.com/ronidas39/knowledge-graph-python-neo4j</strong></a><strong>.</strong> It has the complete script, the 75,500 node dataset as committed CSVs, the benchmark behind every number in this article, and a checker that runs all 39 Cypher blocks through <code>EXPLAIN</code>. Clone it, run <code>verify_dataset.py</code>, and you'll know your data matches mine before you trust a single measurement.</p>
<p>If you want to go deeper, I write about system design at <a href="https://systemdesign.academy">systemdesign.academy</a> and publish longer engineering tutorials on <a href="https://www.youtube.com/@totaltechnologyzonne">my YouTube channel</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use PostgreSQL as a Cache, Queue, and Search Engine ]]>
                </title>
                <description>
                    <![CDATA[ "Just use Postgres" has been circulating as advice for years, but most articles arguing for it are opinion pieces. I wanted hard numbers. So I built a benchmark suite that pits vanilla PostgreSQL agai ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-use-postgresql-as-a-cache-queue-and-search-engine/</link>
                <guid isPermaLink="false">69e7accfe43672781470ff97</guid>
                
                    <category>
                        <![CDATA[ PostgreSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ backend ]]>
                    </category>
                
                    <category>
                        <![CDATA[ performance ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Databases ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Aaron Yong ]]>
                </dc:creator>
                <pubDate>Tue, 21 Apr 2026 16:58:55 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/6fcdd3c0-eead-42a7-b2f0-cf4c6a3d06dc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>"Just use Postgres" has been circulating as advice for years, but most articles arguing for it are opinion pieces. I wanted hard numbers.</p>
<p>So I built a benchmark suite that pits vanilla PostgreSQL against a feature-optimized PostgreSQL instance — measuring caching, message queues, full-text search, and pub/sub under controlled conditions.</p>
<p>In this article, you'll learn how to use PostgreSQL's built-in features for caching, job queues, full-text search, and pub/sub. You'll see actual benchmark results (latency percentiles, throughput, and error rates) comparing naive PostgreSQL patterns against optimized ones, and understand where PostgreSQL's limits are so you can decide whether you really need that extra service in your stack.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-the-setup">The Setup</a></p>
</li>
<li><p><a href="#heading-benchmark-1-caching-with-unlogged-tables">Benchmark 1: Caching with UNLOGGED Tables</a></p>
</li>
<li><p><a href="#heading-benchmark-2-job-queues-with-skip-locked">Benchmark 2: Job Queues with SKIP LOCKED</a></p>
</li>
<li><p><a href="#heading-benchmark-3-full-text-search-with-tsvector">Benchmark 3: Full-Text Search with tsvector</a></p>
</li>
<li><p><a href="#heading-benchmark-4-pubsub-with-listennotify">Benchmark 4: Pub/Sub with LISTEN/NOTIFY</a></p>
</li>
<li><p><a href="#heading-the-combined-workload-the-honest-test">The Combined Workload: The Honest Test</a></p>
</li>
<li><p><a href="#heading-what-i-learned">What I Learned</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along or reproduce the benchmarks, you'll need:</p>
<ul>
<li><p>Docker and Docker Compose</p>
</li>
<li><p>Node.js 20+ (for the Express TypeScript API layer)</p>
</li>
<li><p><a href="https://k6.io/">k6</a> for load testing</p>
</li>
<li><p>Basic familiarity with SQL and PostgreSQL</p>
</li>
</ul>
<p>The full benchmark project is <a href="https://github.com/aaronhsyong2/pg-stack-benchmark">open source on GitHub</a> — you can clone it and run every test yourself.</p>
<h2 id="heading-the-setup">The Setup</h2>
<p>The benchmark uses two identical PostgreSQL 17 instances running in Docker containers, each with fixed resource constraints (2 CPUs, 2 GB RAM). Both share the same Express TypeScript API layer — the only difference is which PostgreSQL features are enabled.</p>
<pre><code class="language-plaintext">┌─────────┐     ┌──────────────────┐     ┌─────────────────┐
│   k6    │────&gt;│  Express API     │────&gt;│  PG Baseline    │
│  (load  │     │  (TypeScript)    │     │  (vanilla PG17) │
│  test)  │────&gt;│  Port 3001/3002  │────&gt;│  PG Modded      │
└─────────┘     └──────────────────┘     │  (features on)  │
                                         └─────────────────┘
</code></pre>
<p>The baseline instance uses naïve approaches (regular tables, <code>ILIKE</code> search, polling). The modded instance uses PostgreSQL's built-in features (UNLOGGED tables, <code>tsvector</code> with GIN indexes, <code>LISTEN/NOTIFY</code>, partial indexes). Same hardware, same API code, same data. Only the database features differ.</p>
<p>Both instances share this tuned <code>postgresql.conf</code>:</p>
<pre><code class="language-ini"># Memory allocation
shared_buffers = 512MB           # 25% of available RAM
effective_cache_size = 1536MB    # 75% of RAM — helps the query planner
work_mem = 16MB                  # per-sort/hash operation memory

# SSD-optimized planner settings
random_page_cost = 1.1           # default 4.0 assumes spinning disks
effective_io_concurrency = 200   # allow parallel I/O on SSDs
</code></pre>
<p>These settings matter. The defaults assume spinning disks from the early 2000s. Setting <code>random_page_cost = 1.1</code> tells the query planner that random reads are nearly as fast as sequential reads on SSDs, which encourages index usage over sequential scans.</p>
<h2 id="heading-benchmark-1-caching-with-unlogged-tables">Benchmark 1: Caching with UNLOGGED Tables</h2>
<p><strong>The idea:</strong> Use an UNLOGGED table as an in-database cache. UNLOGGED tables skip PostgreSQL's Write-Ahead Log (WAL) — the mechanism that guarantees durability. Since cache data is ephemeral by nature, losing it on a crash is acceptable, and skipping WAL removes the biggest write bottleneck.</p>
<pre><code class="language-sql">-- Modded: UNLOGGED table for cache entries
CREATE UNLOGGED TABLE cache_entries (
    key TEXT PRIMARY KEY,
    value JSONB NOT NULL,
    expires_at TIMESTAMPTZ
);

-- Baseline: same schema, but a regular (logged) table
CREATE TABLE cache_entries (
    key TEXT PRIMARY KEY,
    value JSONB NOT NULL,
    expires_at TIMESTAMPTZ
);
</code></pre>
<h3 id="heading-results-200-virtual-users">Results (200 Virtual Users)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (regular table)</td>
<td>1.87ms</td>
<td>6.00ms</td>
<td>2.50ms</td>
<td>1,754/s</td>
</tr>
<tr>
<td>Modded (UNLOGGED table)</td>
<td>1.71ms</td>
<td>5.24ms</td>
<td>2.17ms</td>
<td>1,760/s</td>
</tr>
</tbody></table>
<p>A consistent 13% improvement across all percentiles. Not dramatic, but free — you change one keyword in your <code>CREATE TABLE</code> statement.</p>
<h3 id="heading-under-stress-1000-virtual-users-no-sleep">Under Stress (1,000 Virtual Users, No Sleep)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>req/s</th>
<th>Total Requests</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline</td>
<td>83.38ms</td>
<td>143.23ms</td>
<td>7,663/s</td>
<td>728,021</td>
</tr>
<tr>
<td>Modded</td>
<td>77.69ms</td>
<td>126.39ms</td>
<td>8,062/s</td>
<td>765,934</td>
</tr>
</tbody></table>
<p>The relative improvement stays locked at 12-13% regardless of load level. The UNLOGGED advantage is a per-write optimization — it saves the same amount of I/O whether you are doing 100 or 10,000 writes per second. The modded instance served 37,000 more requests in the same time window.</p>
<h3 id="heading-the-verdict">The Verdict</h3>
<p>UNLOGGED tables won't match Redis for sub-millisecond hot-path caching (real-time bidding, gaming leaderboards). But for web applications where the difference between 2ms and 5ms is invisible to users, they eliminate an entire infrastructure dependency for zero additional complexity.</p>
<p>You do give up Redis data structures (sorted sets, HyperLogLog, streams). If you need those, a dedicated cache is still the right call.</p>
<h2 id="heading-benchmark-2-job-queues-with-skip-locked">Benchmark 2: Job Queues with SKIP LOCKED</h2>
<p><strong>The idea:</strong> Use PostgreSQL as a job queue with <code>SELECT ... FOR UPDATE SKIP LOCKED</code>. Multiple workers poll the same table, and <code>SKIP LOCKED</code> ensures each worker gets a different row — no duplicates, no contention.</p>
<pre><code class="language-sql">-- Queue table with a partial index on pending jobs only
CREATE TABLE job_queue (
    id SERIAL PRIMARY KEY,
    payload JSONB NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Partial index: only indexes pending jobs
-- As jobs complete, they leave the index — it stays small forever
CREATE INDEX idx_pending_jobs ON job_queue (created_at)
    WHERE status = 'pending';
</code></pre>
<p>The dequeue pattern:</p>
<pre><code class="language-sql">-- Atomic dequeue: select + update in one statement
UPDATE job_queue SET status = 'processing'
WHERE id = (
    SELECT id FROM job_queue
    WHERE status = 'pending'
    ORDER BY created_at
    LIMIT 1
    FOR UPDATE SKIP LOCKED  -- skip rows locked by other workers
) RETURNING *;
</code></pre>
<p>How <code>SKIP LOCKED</code> works: Worker A locks row 1. Worker B tries row 1, sees the lock, skips it, and takes row 2 instead. No blocking, no duplicates. If a worker crashes, the transaction rolls back and the row becomes available again.</p>
<h3 id="heading-results-100-producers-50-consumers">Results (100 Producers + 50 Consumers)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (full index)</td>
<td>1.90ms</td>
<td>5.01ms</td>
<td>2.30ms</td>
<td>1,053/s</td>
</tr>
<tr>
<td>Modded (partial index)</td>
<td>1.81ms</td>
<td>5.28ms</td>
<td>2.29ms</td>
<td>1,052/s</td>
</tr>
</tbody></table>
<p>They're virtually identical. The partial index doesn't show its value in a 60-second benchmark because the table doesn't accumulate enough completed rows for the index size difference to matter. In a production system with millions of completed jobs, the partial index keeps the index at kilobytes while a full index grows to gigabytes.</p>
<h3 id="heading-the-verdict">The Verdict</h3>
<p><code>SKIP LOCKED</code> is production-ready for job queues. Libraries like <a href="https://github.com/timgit/pg-boss">pg-boss</a> (Node.js) and <a href="https://github.com/riverqueue/river">river</a> (Go) build on this exact pattern.</p>
<p>You do give up exchange/routing patterns (fan-out, topic-based routing) and consumer groups with message replay. If you need those, a dedicated message broker is still the right tool. For simple "process this job once" workloads, PostgreSQL handles it.</p>
<h2 id="heading-benchmark-3-full-text-search-with-tsvector">Benchmark 3: Full-Text Search with tsvector</h2>
<p><strong>The idea:</strong> Use PostgreSQL's built-in full-text search instead of a separate search service. A <code>tsvector</code> column stores pre-processed search tokens, and a GIN (Generalized Inverted Index) enables fast lookups using the same inverted index concept that powers Elasticsearch.</p>
<pre><code class="language-sql">-- Search-optimized article table
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    body TEXT NOT NULL,
    search_vector tsvector  -- pre-computed search tokens
);

-- GIN index for full-text search
CREATE INDEX idx_search ON articles USING GIN (search_vector);

-- Auto-update search_vector on insert/update
CREATE OR REPLACE FUNCTION update_search_vector() RETURNS trigger AS $$
BEGIN
    NEW.search_vector := to_tsvector('english',
        COALESCE(NEW.title, '') || ' ' || COALESCE(NEW.body, ''));
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_search
    BEFORE INSERT OR UPDATE ON articles
    FOR EACH ROW EXECUTE FUNCTION update_search_vector();
</code></pre>
<p>The baseline uses <code>ILIKE</code> with a leading wildcard — the approach most developers reach for first:</p>
<pre><code class="language-sql">-- Baseline: sequential scan on every query
SELECT * FROM articles
WHERE title ILIKE '%postgresql%' OR body ILIKE '%postgresql%';

-- Modded: GIN index lookup with relevance ranking
SELECT id, title,
    ts_rank(search_vector, plainto_tsquery('english', 'postgresql')) AS rank
FROM articles
WHERE search_vector @@ plainto_tsquery('english', 'postgresql')
ORDER BY rank DESC LIMIT 20;
</code></pre>
<h3 id="heading-results-500-virtual-users">Results (500 Virtual Users)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (ILIKE)</td>
<td>1.96ms</td>
<td>101.83ms</td>
<td>25.22ms</td>
<td>561/s</td>
</tr>
<tr>
<td>Modded (tsvector + GIN)</td>
<td>2.76ms</td>
<td>10.39ms</td>
<td>3.76ms</td>
<td>675/s</td>
</tr>
</tbody></table>
<p>This is the standout result. The baseline's p95 of 101ms versus the modded's 10ms is a 10x improvement.</p>
<p>Why the baseline's p50 (1.96ms) is slightly better than the modded's (2.76ms): simple <code>ILIKE</code> queries on small result sets can be fast when the data fits in <code>shared_buffers</code>. But as load increases and the buffer cache is contested, sequential scans degrade dramatically. The GIN index stays stable.</p>
<h3 id="heading-under-stress-500-virtual-users-no-sleep">Under Stress (500 Virtual Users, No Sleep)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>req/s</th>
<th>Total Requests</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (ILIKE)</td>
<td>599ms</td>
<td>1,000ms</td>
<td>558/s</td>
<td>50,212</td>
</tr>
<tr>
<td>Modded (tsvector)</td>
<td>209ms</td>
<td>396ms</td>
<td>1,441/s</td>
<td>129,679</td>
</tr>
</tbody></table>
<p>ILIKE collapses to 1-second p95 latencies. Each query forces a sequential scan of all 10,000 articles, blocking shared buffers and starving concurrent queries. The tsvector approach serves 2.6x more requests in the same time window because the GIN index lookup is O(log n) regardless of concurrency.</p>
<h3 id="heading-the-verdict">The Verdict</h3>
<p>This is the strongest argument in the entire benchmark. The fix requires zero extensions — <code>to_tsvector()</code>, <code>plainto_tsquery()</code>, and <code>CREATE INDEX USING GIN</code> are all built into core PostgreSQL. If you're doing <code>WHERE column ILIKE '%term%'</code> on any table with more than a few thousand rows, you're leaving massive performance on the table.</p>
<p>You do give up distributed search across shards, complex analyzers for CJK languages, and aggregation/faceted search pipelines. For a product search bar, blog search, or internal tool — PostgreSQL is enough.</p>
<h2 id="heading-benchmark-4-pubsub-with-listennotify">Benchmark 4: Pub/Sub with LISTEN/NOTIFY</h2>
<p><strong>The idea:</strong> Use PostgreSQL's native <code>LISTEN/NOTIFY</code> for pub/sub messaging, triggered automatically on INSERT via a database trigger.</p>
<pre><code class="language-sql">-- Trigger that fires pg_notify on every new message
CREATE OR REPLACE FUNCTION notify_message() RETURNS trigger AS $$
BEGIN
    PERFORM pg_notify(NEW.channel, NEW.payload::text);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER trg_notify
    AFTER INSERT ON messages
    FOR EACH ROW EXECUTE FUNCTION notify_message();
</code></pre>
<h3 id="heading-results-200-virtual-users">Results (200 Virtual Users)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline (poll-based)</td>
<td>1.99ms</td>
<td>6.04ms</td>
<td>2.84ms</td>
<td>1,116/s</td>
</tr>
<tr>
<td>Modded (LISTEN/NOTIFY)</td>
<td>1.65ms</td>
<td>4.80ms</td>
<td>2.13ms</td>
<td>1,131/s</td>
</tr>
</tbody></table>
<p>Here we have a 20% improvement at p95. The trigger-based approach does more work per INSERT (INSERT + NOTIFY), but the reduced round trips and better connection reuse patterns offset the overhead.</p>
<h3 id="heading-the-verdict">The Verdict</h3>
<p><code>LISTEN/NOTIFY</code> works for real-time features where you would otherwise reach for Redis pub/sub. The main limitation is payload size (8,000 bytes maximum) and the requirement for dedicated connections (incompatible with PgBouncer in transaction mode).</p>
<h2 id="heading-the-combined-workload-the-honest-test">The Combined Workload: The Honest Test</h2>
<p>Individual benchmarks are flattering. The real question: can one PostgreSQL instance handle caching, queues, search, and pub/sub simultaneously without degrading?</p>
<h3 id="heading-results-all-four-workloads-running-together">Results (All Four Workloads Running Together)</h3>
<table>
<thead>
<tr>
<th>Mode</th>
<th>p50</th>
<th>p95</th>
<th>avg</th>
<th>req/s</th>
</tr>
</thead>
<tbody><tr>
<td>Baseline</td>
<td>1.65ms</td>
<td>5.24ms</td>
<td>2.17ms</td>
<td>1,424/s</td>
</tr>
<tr>
<td>Modded</td>
<td>1.86ms</td>
<td>6.05ms</td>
<td>2.47ms</td>
<td>1,417/s</td>
</tr>
</tbody></table>
<p>Under combined load, the baseline marginally outperforms the modded setup. The modded PostgreSQL does more work per operation — maintaining GIN indexes, firing triggers, running <code>pg_cron</code> in the background. When all these features are active simultaneously, the overhead is measurable: about 15% higher p95 latency.</p>
<p>But both setups stay comfortably under 10ms at p95. For most web applications, that's more than good enough.</p>
<h2 id="heading-what-i-learned">What I Learned</h2>
<p>After running all these benchmarks, here's what I would tell a team evaluating whether to "just use Postgres":</p>
<ol>
<li><p><strong>Do it for full-text search:</strong> Switching from <code>ILIKE</code> to <code>tsvector</code> with a GIN index is a 10x improvement that requires zero extensions. This is the single highest-ROI change in the entire PostgreSQL ecosystem, and most developers don't know it exists.</p>
</li>
<li><p><strong>Do it for job queues:</strong> <code>SKIP LOCKED</code> is production-ready and eliminates RabbitMQ for simple "process this job" workloads. Use a library like pg-boss or river rather than rolling your own.</p>
</li>
<li><p><strong>Consider it for caching:</strong> UNLOGGED tables give a steady 13% improvement over regular tables. If sub-millisecond latency is not a hard requirement (and for most web apps, it is not), you can drop Redis entirely.</p>
</li>
<li><p><strong>Be honest about the overhead:</strong> Running all four roles simultaneously adds about 15% latency compared to running any single role. Whether that matters depends on your latency budget.</p>
</li>
<li><p><strong>Know where to stop:</strong> PostgreSQL won't match Redis for sub-millisecond caching, Kafka for millions of messages per second, or Elasticsearch for distributed multi-node search with complex analyzers. The line is at extreme throughput or extreme specialization.</p>
</li>
</ol>
<p>The honest conclusion is not "PostgreSQL does everything." It is: for most applications, a single well-configured PostgreSQL instance handles 80% of what you would otherwise need three to five additional services for. That is less infrastructure to deploy, monitor, and maintain — and fewer things to break at 3 AM.</p>
<p>Enterprise-scale applications processing millions of messages per second, serving sub-millisecond cache hits to millions of concurrent users, or running distributed search across terabytes of documents will still need specialized tools. Those tools exist for a reason, and at that scale the operational cost of running them is justified by the performance you get back.</p>
<p>But most of us aren't building at that scale — and may never need to. Starting with PostgreSQL for these roles means you ship faster with fewer moving parts. If and when you outgrow what PostgreSQL can handle, your benchmarks will tell you exactly which role needs to be extracted into a dedicated service. That is a much better position than starting with five services on day one because you assumed you would need them.</p>
<p>The <a href="https://github.com/aaronhsyong2/pg-stack-benchmark">benchmark project</a> is open source if you want to reproduce these results or adapt the tests for your own workload.</p>
<p>You can find more of my writing at <a href="https://site.aaronhsyong.com">site.aaronhsyong.com</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Store Data Locally with Isar in Flutter ]]>
                </title>
                <description>
                    <![CDATA[ When building Flutter applications, managing local data efficiently is critical. You want a database that is lightweight, fast, and easy to integrate, especially if your app will work offline. Isar is one such database. It is a high-performance, easy... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/store-data-locally-with-isar-in-flutter/</link>
                <guid isPermaLink="false">68cd561cebc0d959d789d679</guid>
                
                    <category>
                        <![CDATA[ Flutter ]]>
                    </category>
                
                    <category>
                        <![CDATA[ NoSQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Dart ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Atuoha Anthony ]]>
                </dc:creator>
                <pubDate>Fri, 19 Sep 2025 13:09:48 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758287132737/7886bedc-374f-401d-b59c-04c59590e81f.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>When building Flutter applications, managing local data efficiently is critical. You want a database that is lightweight, fast, and easy to integrate, especially if your app will work offline. Isar is one such database. It is a high-performance, easy-to-use NoSQL embedded database tailored for Flutter. With features like reactive queries, indexes, relationships, migrations, and transactions, Isar makes local data persistence both powerful and developer-friendly.</p>
<p>In this article, you’lll learn how to integrate Isar into a Flutter project, set up a data model, and perform the full range of CRUD (Create, Read, Update, Delete) operations. To make this practical, you’ll build a simple to-do app that allows users to create, view, update, and delete tasks.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-we-are-building">What We Are Building</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-isar-in-a-flutter-project">How to Set Up Isar in a Flutter Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-the-task-model">How to Create the Task Model</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-repository-for-crud-operations">How to Build the Repository for CRUD Operations</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-crud-into-the-flutter-ui">How to Integrate CRUD into the Flutter UI</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-beyond-crud-advanced-features-of-isar">Beyond CRUD: Advanced Features of Isar</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, ensure you have the following:</p>
<ol>
<li><p><strong>Flutter SDK</strong> installed (version 3.0 or above recommended).<br> Check your version with:</p>
<pre><code class="lang-bash"> flutter --version
</code></pre>
</li>
<li><p><strong>Dart knowledge</strong>: Familiarity with Dart syntax, classes, and async programming.</p>
</li>
<li><p><strong>Flutter basics</strong>: You should know how to set up a Flutter project, build widgets, and use <code>FutureBuilder</code> or <code>setState</code> for state management.</p>
</li>
<li><p><strong>Code editor</strong>: VS Code or Android Studio is recommended.</p>
</li>
</ol>
<p>If these are in place, we are ready to begin.</p>
<h2 id="heading-what-we-are-building">What We Are Building</h2>
<p>We will create a Task Manager App that lets users:</p>
<ul>
<li><p>Add new tasks.</p>
</li>
<li><p>View all tasks in a list.</p>
</li>
<li><p>Update existing tasks.</p>
</li>
<li><p>Delete tasks.</p>
</li>
</ul>
<p>By the end, you will have a fully functioning CRUD app built with Flutter and Isar.</p>
<h2 id="heading-how-to-set-up-isar-in-a-flutter-project">How to Set Up Isar in a Flutter Project</h2>
<h3 id="heading-step-1-add-dependencies">Step 1: Add dependencies</h3>
<p>Open your <code>pubspec.yaml</code> file and add the following:</p>
<pre><code class="lang-yaml"><span class="hljs-attr">dependencies:</span>
  <span class="hljs-attr">flutter:</span>
    <span class="hljs-attr">sdk:</span> <span class="hljs-string">flutter</span>
  <span class="hljs-attr">isar:</span> <span class="hljs-string">^3.1.0</span>
  <span class="hljs-attr">isar_flutter_libs:</span> <span class="hljs-string">^3.1.0</span>

<span class="hljs-attr">dev_dependencies:</span>
  <span class="hljs-attr">isar_generator:</span> <span class="hljs-string">^3.1.0</span>
  <span class="hljs-attr">build_runner:</span> <span class="hljs-string">any</span>
</code></pre>
<ul>
<li><p><code>isar</code>: The core Isar package.</p>
</li>
<li><p><code>isar_flutter_libs</code>: Required for Flutter integration.</p>
</li>
<li><p><code>isar_generator</code>: Used to generate code for your models.</p>
</li>
<li><p><code>build_runner</code>: Runs the code generator.</p>
</li>
</ul>
<p>Run:</p>
<pre><code class="lang-bash">flutter pub get
</code></pre>
<h3 id="heading-step-2-create-and-initialize-isar">Step 2: Create and initialize Isar</h3>
<p>Create a file named <code>isar_setup.dart</code>. This will handle the opening of the Isar database.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:isar/isar.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'package:path_provider/path_provider.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'task.dart'</span>; <span class="hljs-comment">// we will create this model soon</span>

<span class="hljs-keyword">late</span> <span class="hljs-keyword">final</span> Isar isar;

Future&lt;<span class="hljs-keyword">void</span>&gt; initializeIsar() <span class="hljs-keyword">async</span> {
  <span class="hljs-keyword">final</span> dir = <span class="hljs-keyword">await</span> getApplicationDocumentsDirectory();
  isar = <span class="hljs-keyword">await</span> Isar.open(
    [TaskSchema],
    directory: dir.path,
  );
}
</code></pre>
<p><strong>Explanation</strong>:</p>
<ul>
<li><p><code>getApplicationDocumentsDirectory()</code> provides a storage location for the database file.</p>
</li>
<li><p><code>Isar.open()</code> initializes the database and registers our <code>Task</code> schema.</p>
</li>
<li><p><code>late final Isar isar;</code> ensures we can access the database instance globally after initialization.</p>
</li>
</ul>
<h2 id="heading-how-to-create-the-task-model">How to Create the Task Model</h2>
<p>Now let’s define our data model for tasks. Create a file named <code>task.dart</code>.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:isar/isar.dart'</span>;

<span class="hljs-keyword">part</span> <span class="hljs-string">'task.g.dart'</span>;

<span class="hljs-meta">@Collection</span>()
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Task</span> </span>{
  Id id = Isar.autoIncrement; <span class="hljs-comment">// auto-incrementing primary key</span>

  <span class="hljs-keyword">late</span> <span class="hljs-built_in">String</span> name;

  <span class="hljs-keyword">late</span> <span class="hljs-built_in">DateTime</span> createdAt;

  Task(<span class="hljs-keyword">this</span>.name) : createdAt = <span class="hljs-built_in">DateTime</span>.now();
}
</code></pre>
<p><strong>Explanation</strong>:</p>
<ul>
<li><p><code>@Collection()</code> tells Isar this class represents a database collection.</p>
</li>
<li><p><code>Id id = Isar.autoIncrement;</code> creates a unique identifier automatically.</p>
</li>
<li><p><code>late String name;</code> stores the task name.</p>
</li>
<li><p><code>late DateTime createdAt;</code> stores the creation timestamp.</p>
</li>
<li><p><code>part 'task.g.dart';</code> links to the generated code, which will be created after running the code generator.</p>
</li>
</ul>
<p>Generate the code with:</p>
<pre><code class="lang-bash">flutter pub run build_runner build
</code></pre>
<p>This generates <code>task.g.dart</code>, which contains the necessary schema code.</p>
<h2 id="heading-how-to-build-the-repository-for-crud-operations">How to Build the Repository for CRUD Operations</h2>
<p>Create a new file called <code>task_repository.dart</code>. This will house the methods for interacting with the database.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:isar/isar.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'task.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'isar_setup.dart'</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TaskRepository</span> </span>{
  Future&lt;<span class="hljs-keyword">void</span>&gt; addTask(<span class="hljs-built_in">String</span> name) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">final</span> task = Task(name);
    <span class="hljs-keyword">await</span> isar.writeTxn(() <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">await</span> isar.tasks.put(task);
    });
  }

  Future&lt;<span class="hljs-built_in">List</span>&lt;Task&gt;&gt; getAllTasks() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> isar.tasks.where().findAll();
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; updateTask(Task task) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> isar.writeTxn(() <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">await</span> isar.tasks.put(task);
    });
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; deleteTask(Task task) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> isar.writeTxn(() <span class="hljs-keyword">async</span> {
      <span class="hljs-keyword">await</span> isar.tasks.delete(task.id);
    });
  }
}
</code></pre>
<p><strong>Explanation</strong>:</p>
<ul>
<li><p><code>addTask</code>: Creates a new task and saves it.</p>
</li>
<li><p><code>getAllTasks</code>: Reads all tasks from the database.</p>
</li>
<li><p><code>updateTask</code>: Updates an existing task by calling <code>.put()</code> again.</p>
</li>
<li><p><code>deleteTask</code>: Removes a task by its <code>id</code>.</p>
</li>
<li><p><code>isar.writeTxn</code>: Ensures operations run inside a transaction for safety and consistency.</p>
</li>
</ul>
<h2 id="heading-how-to-integrate-crud-into-the-flutter-ui">How to Integrate CRUD into the Flutter UI</h2>
<p>Now, let’s connect everything inside <code>main.dart</code>.</p>
<pre><code class="lang-dart"><span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'isar_setup.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'task_repository.dart'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">'task.dart'</span>;

<span class="hljs-keyword">void</span> main() <span class="hljs-keyword">async</span> {
  WidgetsFlutterBinding.ensureInitialized();
  <span class="hljs-keyword">await</span> initializeIsar(); <span class="hljs-comment">// initialize Isar before runApp</span>
  runApp(MyApp());
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> MaterialApp(
      home: TaskListScreen(),
    );
  }
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TaskListScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatefulWidget</span> </span>{
  <span class="hljs-meta">@override</span>
  _TaskListScreenState createState() =&gt; _TaskListScreenState();
}

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">_TaskListScreenState</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">State</span>&lt;<span class="hljs-title">TaskListScreen</span>&gt; </span>{
  <span class="hljs-keyword">final</span> TaskRepository _taskRepository = TaskRepository();
  <span class="hljs-keyword">late</span> Future&lt;<span class="hljs-built_in">List</span>&lt;Task&gt;&gt; _tasksFuture;

  <span class="hljs-meta">@override</span>
  <span class="hljs-keyword">void</span> initState() {
    <span class="hljs-keyword">super</span>.initState();
    _tasksFuture = _taskRepository.getAllTasks();
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; _addTask() <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _taskRepository.addTask(<span class="hljs-string">'New Task'</span>);
    setState(() {
      _tasksFuture = _taskRepository.getAllTasks();
    });
  }

  Future&lt;<span class="hljs-keyword">void</span>&gt; _deleteTask(Task task) <span class="hljs-keyword">async</span> {
    <span class="hljs-keyword">await</span> _taskRepository.deleteTask(task);
    setState(() {
      _tasksFuture = _taskRepository.getAllTasks();
    });
  }

  <span class="hljs-meta">@override</span>
  Widget build(BuildContext context) {
    <span class="hljs-keyword">return</span> Scaffold(
      appBar: AppBar(title: Text(<span class="hljs-string">'Isar CRUD Example'</span>)),
      body: FutureBuilder&lt;<span class="hljs-built_in">List</span>&lt;Task&gt;&gt;(
        future: _tasksFuture,
        builder: (context, snapshot) {
          <span class="hljs-keyword">if</span> (snapshot.connectionState == ConnectionState.waiting) {
            <span class="hljs-keyword">return</span> Center(child: CircularProgressIndicator());
          } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (snapshot.hasError) {
            <span class="hljs-keyword">return</span> Center(child: Text(<span class="hljs-string">'Error: <span class="hljs-subst">${snapshot.error}</span>'</span>));
          } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">final</span> tasks = snapshot.data ?? [];
            <span class="hljs-keyword">if</span> (tasks.isEmpty) {
              <span class="hljs-keyword">return</span> Center(child: Text(<span class="hljs-string">'No tasks yet.'</span>));
            }
            <span class="hljs-keyword">return</span> ListView.builder(
              itemCount: tasks.length,
              itemBuilder: (context, index) {
                <span class="hljs-keyword">final</span> task = tasks[index];
                <span class="hljs-keyword">return</span> ListTile(
                  title: Text(task.name),
                  subtitle: Text(<span class="hljs-string">'Created at: <span class="hljs-subst">${task.createdAt}</span>'</span>),
                  trailing: IconButton(
                    icon: Icon(Icons.delete),
                    onPressed: () =&gt; _deleteTask(task),
                  ),
                );
              },
            );
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addTask,
        child: Icon(Icons.add),
      ),
    );
  }
}
</code></pre>
<p><strong>Explanation</strong>:</p>
<ul>
<li><p><code>initializeIsar()</code>: Ensures the database is ready before the app runs.</p>
</li>
<li><p><code>_tasksFuture</code>: Holds a future of the list of tasks.</p>
</li>
<li><p><code>_addTask</code>: Adds a new task and refreshes the list.</p>
</li>
<li><p><code>_deleteTask</code>: Deletes a task and refreshes the list.</p>
</li>
<li><p><code>FutureBuilder</code>: Automatically rebuilds the UI when the future completes.</p>
</li>
<li><p><code>ListView.builder</code>: Displays all tasks dynamically.</p>
</li>
</ul>
<p>This gives you a simple yet complete CRUD app using Isar.</p>
<h2 id="heading-beyond-crud-advanced-features-of-isar">Beyond CRUD: Advanced Features of Isar</h2>
<p>Once you are comfortable with CRUD, Isar provides advanced tools to optimize and extend your application:</p>
<ol>
<li><p><strong>Reactive Queries</strong>:<br> Instead of using <code>FutureBuilder</code>, you can listen for changes directly.</p>
<pre><code class="lang-dart"> <span class="hljs-keyword">final</span> stream = isar.tasks.where().watch(fireImmediately: <span class="hljs-keyword">true</span>);
</code></pre>
</li>
<li><p><strong>Indexes</strong>:<br> Improve query performance by indexing fields.</p>
<pre><code class="lang-dart"> <span class="hljs-meta">@Collection</span>()
 <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Task</span> </span>{
   Id id = Isar.autoIncrement;

   <span class="hljs-meta">@Index</span>()
   <span class="hljs-keyword">late</span> <span class="hljs-built_in">String</span> name;
 }
</code></pre>
</li>
<li><p><strong>Relations</strong>:<br> Link one collection to another (for example, <code>Project</code> with many <code>Tasks</code>).</p>
</li>
<li><p><strong>Custom Queries</strong>:<br> Perform complex filtering, sorting, and pagination.</p>
</li>
<li><p><strong>Migrations</strong>:<br> Safely evolve your schema as the app grows.</p>
</li>
<li><p><strong>Batch Operations</strong>:<br> Insert or update many records in one transaction.</p>
</li>
</ol>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We built a simple Flutter to-do app with Isar that supports creating, reading, updating, and deleting tasks. Along the way, we learned how to:</p>
<ol>
<li><p>Add Isar dependencies.</p>
</li>
<li><p>Define a model with annotations.</p>
</li>
<li><p>Generate schema code.</p>
</li>
<li><p>Implement CRUD operations in a repository.</p>
</li>
<li><p>Connect Isar to the Flutter UI.</p>
</li>
</ol>
<p>With its performance, developer-friendly API, and advanced features, Isar is an excellent choice for local persistence in Flutter applications.</p>
<p>For further learning, consult the official docs:</p>
<ol>
<li><p><a target="_blank" href="https://pub.dev/packages/isar">Isar on pub.dev</a></p>
</li>
<li><p><a target="_blank" href="https://isar.dev/">Isar documentation</a></p>
</li>
</ol>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Handle MongoDB Migrations with ts-migrate-mongoose ]]>
                </title>
                <description>
                    <![CDATA[ Database migrations are modifications made to a database. These modifications may include changing the schema of a table, updating the data in a set of records, seeding data or deleting a range of records. Database migrations are usually run before a... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/handle-mongodb-migrations-with-ts-migrate-mongoose/</link>
                <guid isPermaLink="false">6747274cb7666002fd1cb1a6</guid>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Orim Dominic Adah ]]>
                </dc:creator>
                <pubDate>Wed, 27 Nov 2024 14:06:04 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1732615343335/0a1b3e5e-bfa7-4f57-81a7-7f4bac9e8b0a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Database migrations are modifications made to a database. These modifications may include changing the schema of a table, updating the data in a set of records, seeding data or deleting a range of records.</p>
<p>Database migrations are usually run before an application starts and do not run successfully more than once for the same database. Database migration tools save a history of migrations that have run in a database so that they can be tracked for future purposes.</p>
<p>In this article, you’ll learn how to set up and run database migrations in a minimal Node.js API application. We will use <a target="_blank" href="https://www.npmjs.com/package/ts-migrate-mongoose">ts-migrate-mongoose</a> and an npm script to create a migration and seed data into a MongoDB database. ts-migrate-mongoose supports running migration scripts from TypeScript code as well as CommonJS code.</p>
<p>ts-migrate-mongoose is a migration framework for Node.js projects that use <a target="_blank" href="https://www.npmjs.com/package/mongoose">mongoose</a> as the object-data mapper. It provides a template for writing migration scripts. It also provides a configuration to run the scripts programmatically and from the CLI.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-the-project">How to Set Up the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-configure-ts-migrate-mongoose-for-the-project">How to Configure ts-migrate-mongoose for the Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-seed-user-data-with-ts-migrate-mongoose">How to Seed User Data with ts-migrate-mongoose</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-an-api-endpoint-to-fetch-seeded-data">How to Build an API Endpoint to Fetch Seeded Data</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-how-to-set-up-the-project">How to Set Up the Project</h2>
<p>To use ts-migrate-mongoose for database migrations, you need to have the following:</p>
<ol>
<li><p>A Node.js project with mongoose installed as a dependency.</p>
</li>
<li><p>A MongoDB database connected to the project.</p>
</li>
<li><p>MongoDB Compass (Optional – to enable us view the changes in the database).</p>
</li>
</ol>
<p>A starter repository which can be cloned from <a target="_blank" href="https://github.com/orimdominic/ts-migrate-mongoose-starter-repo">ts-migrate-mongoose-starter-repo</a> has been created for ease. Clone the repository, fill the environment variables and start the application by running the <code>npm start</code> command.</p>
<p>Visit <a target="_blank" href="http://localhost:8000">http://localhost:8000</a> with a browser or an API client such as Postman and the server will return a "Hello there!" text to show that the starter application runs as expected.</p>
<h2 id="heading-how-to-configure-ts-migrate-mongoose-for-the-project">How to Configure ts-migrate-mongoose for the Project</h2>
<p>To configure ts-migrate-mongoose for the project, install ts-migrate-mongoose with this command:</p>
<pre><code class="lang-bash">npm install ts-migrate-mongoose
</code></pre>
<p>ts-migrate-mongoose allows configuration with a JSON file, a TypeScript file, a <code>.env</code> file or via the CLI. It is advisable to use a <code>.env</code> file because the content of the configuration may contain a database password and it is not proper to have that exposed to the public. <code>.env</code> files are usually hidden via <code>.gitignore</code> files so they are more secure to use. This project will use a <code>.env</code> file for the ts-migrate-mongoose configuration.</p>
<p>The file should contain the following keys and their values:</p>
<ul>
<li><p><code>MIGRATE_MONGO_URI</code> - the URI of the Mongo database. It is the same as the database URL.</p>
</li>
<li><p><code>MIGRATE_MONGO_COLLECTION</code> - the name of the collection (or table) which migrations should be saved in. The default value is migrations which is what is used in this project. ts-migrate-mongoose saves migrations to MongoDB.</p>
</li>
<li><p><code>MIGRATE_MIGRATIONS_PATH</code> - the path to the folder for storing and reading migration scripts. The default value is <code>./migrations</code> which is what is used in this project.</p>
</li>
</ul>
<h2 id="heading-how-to-seed-user-data-with-ts-migrate-mongoose">How to Seed User Data with ts-migrate-mongoose</h2>
<p>We have been able to create a project and connect it successfully to a Mongo database. At this point, we want to seed user data into the database. We need to:</p>
<ol>
<li><p>Create a users collection (or table)</p>
</li>
<li><p>Use ts-migrate-mongoose to create a migration script to seed data</p>
</li>
<li><p>Use ts-migrate-mongoose to run the migration to seed the user data into the database before the application starts</p>
</li>
</ol>
<h3 id="heading-1-create-a-users-collection-using-mongoose">1. Create a users Collection using Mongoose</h3>
<p>Mongoose schema can be used to create a user collection (or table). User documents (or records) will have the following fields (or columns): <code>email</code>, <code>favouriteEmoji</code> and <code>yearOfBirth</code>.</p>
<p>To create a Mongoose schema for the user collection, create a <code>user.model.js</code> file in the root of the project containing the following code snippet:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">"mongoose"</span>);

<span class="hljs-keyword">const</span> userSchema = <span class="hljs-keyword">new</span> mongoose.Schema(
  {
    <span class="hljs-attr">email</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">lowercase</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
    },
    <span class="hljs-attr">favouriteEmoji</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,
      <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
    },
    <span class="hljs-attr">yearOfBirth</span>: {
      <span class="hljs-attr">type</span>: <span class="hljs-built_in">Number</span>,
      <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,
    },
  },
  {
    <span class="hljs-attr">timestamps</span>: <span class="hljs-literal">true</span>,
  }
);

<span class="hljs-built_in">module</span>.exports.UserModel = mongoose.model(<span class="hljs-string">"User"</span>, userSchema);
</code></pre>
<h3 id="heading-2-create-a-migration-script-with-ts-migrate-mongoose">2. Create a Migration Script with ts-migrate-mongoose</h3>
<p>ts-migrate-mongoose provides CLI commands which can be used to create migration scripts.</p>
<p>Running <code>npx migrate create &lt;name-of-script&gt;</code> in the root folder of the project will create a script in the <code>MIGRATE_MIGRATIONS_PATH</code> folder (<code>./migrations</code> in our case). <code>&lt;name-of-script&gt;</code> is the name we want the migration script file to have when it is created.</p>
<p>To create a migration script to seed user data, run:</p>
<pre><code class="lang-bash">npx migrate create seed-users
</code></pre>
<p>The command will create a file in the <code>./migrations</code> folder with a name in the form -<code>&lt;timestamp&gt;-seed-users.ts</code>. The file will have the following code snippet content:</p>
<pre><code class="lang-ts"><span class="hljs-comment">// Import your models here</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span> (<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-comment">// Write migration here</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span> (<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-comment">// Write migration here</span>
}
</code></pre>
<p>The <code>up</code> function is used to run the migration. The <code>down</code> function is used to reverse whatever the <code>up</code> function executes, if need be. In our case, we are trying to seed users into the database. The <code>up</code> function will contain code to seed users into the database and the <code>down</code> function will contain code to delete users created in the <code>up</code> function.</p>
<p>If the database is inspected with MongoDB Compass, the migrations collection will have a document that looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"_id"</span>: ObjectId(<span class="hljs-string">"6744740465519c3bd9c1a7d1"</span>),
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"seed-users"</span>,
  <span class="hljs-attr">"state"</span>: <span class="hljs-string">"down"</span>,
  <span class="hljs-attr">"createdAt"</span>: <span class="hljs-number">2024</span><span class="hljs-number">-11</span><span class="hljs-number">-25</span>T12:<span class="hljs-number">56</span>:<span class="hljs-number">36.316</span>+<span class="hljs-number">00</span>:<span class="hljs-number">00</span>,
  <span class="hljs-attr">"updatedAt"</span>: <span class="hljs-number">2024</span><span class="hljs-number">-11</span><span class="hljs-number">-25</span>T12:<span class="hljs-number">56</span>:<span class="hljs-number">36.316</span>+<span class="hljs-number">00</span>:<span class="hljs-number">00</span>,
  <span class="hljs-attr">"__v"</span>: <span class="hljs-number">0</span>
}
</code></pre>
<p>The <code>state</code> field of the migration document is set to <code>down</code>. After it runs successfully, it changes to <code>up</code>.</p>
<p>You can update the code in <code>./migrations/&lt;timestamp&gt;-seed-users.ts</code> to the one in the snippet below:</p>
<pre><code class="lang-typescript"><span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config() <span class="hljs-comment">// load env variables</span>
<span class="hljs-keyword">const</span> db = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../db.js"</span>)
<span class="hljs-keyword">const</span> { UserModel } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"../user.model.js"</span>);

<span class="hljs-keyword">const</span> seedUsers = [
  { email: <span class="hljs-string">"john@email.com"</span>, favouriteEmoji: <span class="hljs-string">"🏃"</span>, yearOfBirth: <span class="hljs-number">1997</span> },
  { email: <span class="hljs-string">"jane@email.com"</span>, favouriteEmoji: <span class="hljs-string">"🍏"</span>, yearOfBirth: <span class="hljs-number">1998</span> },
];

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span> (<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-keyword">await</span> db.connect(process.env.MONGO_URI)
  <span class="hljs-keyword">await</span> UserModel.create(seedUsers);}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">down</span> (<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">void</span>&gt; </span>{
  <span class="hljs-keyword">await</span> db.connect(process.env.MONGO_URI)
  <span class="hljs-keyword">await</span> UserModel.delete({
    email: {
      $in: seedUsers.map(<span class="hljs-function">(<span class="hljs-params">u</span>) =&gt;</span> u.email),
    },
  });
}
</code></pre>
<h3 id="heading-3-run-the-migration-before-the-application-starts">3. Run the Migration Before the Application Starts</h3>
<p>ts-migrate-mongoose provides us with CLI commands to run the <code>up</code> and <code>down</code> function of migration scripts.</p>
<p>With <code>npx migrate up &lt;name-of-script&gt;</code> we can run the <code>up</code> function of a specific script. With <code>npx migrate up</code> we can run the <code>up</code> function of all scripts in the <code>./migrations</code> folder with a <code>state</code> of <code>down</code> in the database.</p>
<p>To run the migration before the application starts, we make use of npm scripts. npm scripts with a prefix of <code>pre</code> will run before a script without the <code>pre</code> prefix. For example, if there is a <code>dev</code> script and a <code>predev</code> script, whenever the <code>dev</code> script is run with <code>npm run dev</code>, the <code>predev</code> script will automatically run before the <code>dev</code> script is run.</p>
<p>We will use this feature of npm scripts to place the ts-migrate-mongoose command in a <code>prestart</code> script so that the migration will run before the <code>start</code> script.</p>
<p>Update the <code>package.json</code> file to have a <code>prestart</code> script that runs the ts-migrate-mongoose command for running the <code>up</code> function of migration scripts in the project.</p>
<pre><code class="lang-json">  <span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"prestart"</span>: <span class="hljs-string">"npx migrate up"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"node index.js"</span>
  },
</code></pre>
<p>With this setup, when <code>npm run start</code> is executed to start the application, the <code>prestart</code> script will run to execute the migration using ts-migrate-mongoose and seed the database before the application starts.</p>
<p>You should have something similar to the snippet below after running <code>npm run start</code>:</p>
<pre><code class="lang-bash">Synchronizing database with file system migrations...
MongoDB connection successful
up: 1732543529744-seed-users.ts 
All migrations finished successfully

&gt; ts-migrate-mongoose-starter-repo@1.0.0 start
&gt; node index.js

MongoDB connection successful                      
Server listening on port 8000
</code></pre>
<p>Check out the <a target="_blank" href="https://github.com/orimdominic/ts-migrate-mongoose-starter-repo/tree/seed-users">seed-users</a> branch of the repository to see the current status of the codebase at this point in the article.</p>
<h2 id="heading-how-to-build-an-api-endpoint-to-fetch-seeded-data">How to Build an API Endpoint to Fetch Seeded Data</h2>
<p>We can build an API endpoint to fetch the seeded users data in our database. In the <code>server.js</code> file, update the code to the one in the snippet below:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> { UserModel } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"./user.model.js"</span>)

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">req, res</span>) </span>{
  <span class="hljs-keyword">const</span> users = <span class="hljs-keyword">await</span> UserModel.find({}) <span class="hljs-comment">// fetch all the users in the database</span>

  res.writeHead(<span class="hljs-number">200</span>, { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> });
  <span class="hljs-keyword">return</span> res.end(<span class="hljs-built_in">JSON</span>.stringify({ <span class="hljs-comment">// return a JSON representation of the fetched users data</span>
    <span class="hljs-attr">users</span>: users.map(<span class="hljs-function">(<span class="hljs-params">u</span>) =&gt;</span> ({
      <span class="hljs-attr">email</span>: u.email,
      <span class="hljs-attr">favouriteEmoji</span>: u.favouriteEmoji,
      <span class="hljs-attr">yearOfBirth</span>: u.yearOfBirth,
      <span class="hljs-attr">createdAt</span>: u.createdAt
    }))
  }, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>));
};
</code></pre>
<p>If we start the application and visit <a target="_blank" href="http://localhost:8000">http://localhost:8000</a> using Postman or a browser, we get a JSON response similar to the one below:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"users"</span>: [
    {
      <span class="hljs-attr">"email"</span>: <span class="hljs-string">"john@email.com"</span>,
      <span class="hljs-attr">"favouriteEmoji"</span>: <span class="hljs-string">"🏃"</span>,
      <span class="hljs-attr">"yearOfBirth"</span>: <span class="hljs-number">1997</span>,
      <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2024-11-25T14:18:55.416Z"</span>
    },
    {
      <span class="hljs-attr">"email"</span>: <span class="hljs-string">"jane@email.com"</span>,
      <span class="hljs-attr">"favouriteEmoji"</span>: <span class="hljs-string">"🍏"</span>,
      <span class="hljs-attr">"yearOfBirth"</span>: <span class="hljs-number">1998</span>,
      <span class="hljs-attr">"createdAt"</span>: <span class="hljs-string">"2024-11-25T14:18:55.416Z"</span>
    }
  ]
}
</code></pre>
<p>Notice that if the application is run again, the migration script does not run anymore because the <code>state</code> of the migration will now be <code>up</code> after it has run successfully.</p>
<p>Check out the <a target="_blank" href="https://github.com/orimdominic/ts-migrate-mongoose-starter-repo/tree/fetch-users">fetch-users</a> branch of the repository to see the current status of the codebase at this point in the article.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Migrations are useful when building applications and there is need to seed initial data for testing, seeding administrative users, updating database schema by adding or removing columns and updating the values of columns in many records at once.</p>
<p>ts-migrate-mongoose can help provide a framework for running migrations for your Node.js applications if you use Mongoose with MongoDB.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use Object Relational Mapping in Node.js – Optimize Database Interactions With Sequelize ORM ]]>
                </title>
                <description>
                    <![CDATA[ Databases play a vital role in the development of applications across mobile and web platforms. Adequate knowledge of data interactions between the application structure and the database is essential for storing relevant application data. Object-rela... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/object-relational-mapping-in-nodejs-with-sequelize-orm/</link>
                <guid isPermaLink="false">670fa2e2c038b5fdec128d46</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Object Oriented Programming ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwatobi ]]>
                </dc:creator>
                <pubDate>Wed, 16 Oct 2024 11:26:26 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1728644693143/667b7624-6dc2-407a-828b-f5b6c1844ac8.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Databases play a vital role in the development of applications across mobile and web platforms. Adequate knowledge of data interactions between the application structure and the database is essential for storing relevant application data.</p>
<p>Object-relational mapping, as a programming concept, is an efficient standard protocol for facilitating seamless connection with databases. But what does it really mean, and how do you set it up as a developer? We’ll answer these questions and highlight more about object-relational mapping.</p>
<p>Here are the prerequisites:</p>
<ul>
<li><p>Knowledge of Node.js</p>
</li>
<li><p>Use the Express framework</p>
</li>
<li><p>An installed MySQL database</p>
</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-is-an-orm">What is an ORM?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-your-nodejs-server">How to Set Up Your Node.js Server</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-integrate-relevant-packages">How to Integrate Relevant Packages</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-demo-project">Demo Project</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-additional-information">Additional Information</a></p>
</li>
</ul>
<h2 id="heading-what-is-an-orm">What is an ORM?</h2>
<p>Object Relational Mapping (ORM) is a database communication concept in programming that involves the abstraction of data types as compatible object-oriented programming variables. It simply eliminates the use of database-defined queries and storage types to allow ease of creating databases via the programming languages.</p>
<p>Its use has been widely adopted in the tech space as has more advantages than conventional database query methods. Here are some of them:</p>
<ul>
<li><p>It reduces the risk of data manipulation: SQL and non-SQL injections involve inputting malicious SQL syntaxes and queries into the database, which can compromise database security. Having an ORM in place adds an input validation scheme feature, and details the expected input variable syntax and processes it accordingly.</p>
</li>
<li><p>Ease of database communication: ORM serves to simplify the use of databases as a data tool without undergoing the process of learning a different database query language. The ORM schema can be highlighted in an object-oriented fashion in the application language and can be configured to automatically translate the code to queries compatible with the database.</p>
</li>
<li><p>This feature also allows easy code portability, achieving maintenance of a single database integration code base while changing the database without any adverse outcome. It is highly flexible and can be used in any database of choice.</p>
</li>
<li><p>It also has additional features included to allow database interactions. Database migration features and version control processes are provided. With these, we have seen some of its benefits, we will then highlight popular ORM tools used globally.</p>
</li>
</ul>
<p>Here are the popular ORM tools:</p>
<ul>
<li><p><a target="_blank" href="https://docs.sqlalchemy.org/">SQLAlchemy</a></p>
</li>
<li><p><a target="_blank" href="https://www.prisma.io/">Prisma ORM</a></p>
</li>
<li><p><a target="_blank" href="https://sequelize.org/">Sequelize</a></p>
</li>
<li><p><a target="_blank" href="https://guides.rubyonrails.org/active_record_basics.html">ActiveRecord</a></p>
</li>
<li><p><a target="_blank" href="https://typeorm.io/">TypeORM</a></p>
</li>
<li><p><a target="_blank" href="https://sailsjs.com/documentation/reference/waterline-orm">Waterline</a></p>
</li>
</ul>
<p>For this article, we’ll be streamlining our ORM use cases to a basic Node.js project linked to a MySQL database. We’ll use the Sequelize ORM as the tool of choice.</p>
<p>With an average package download of 8.5 million monthly and an active development community, Sequelize boasts robust features that seamlessly integrate databases with backend applications. It also provides a user oriented documentation which helps guide the user on setting up and using the tool.</p>
<p>Here is a link to <a target="_blank" href="https://sequelize.org/docs/v6/getting-started/">the documentation</a>. It also offers support for MySQL, DB2, and SQLite Microsoft SQL server, and it offers features such as read replication, lazy loading, and efficient database transaction properties.</p>
<p>Next, we’ll set up our web application and install Sequelize to connect us to a MySQL database hosted locally.</p>
<h2 id="heading-how-to-set-up-your-nodejs-server">How to Set Up Your Node.js Server</h2>
<p>In this section you’ll set up our Node server. Navigate to the command line and execute <code>npm init</code>. This command creates a new Node project structure for you.</p>
<p>Next, install the Express package – this will serve as the backend framework. You can do this by running the <code>npm i express</code> command.</p>
<h2 id="heading-how-to-integrate-relevant-packages">How to Integrate Relevant Packages</h2>
<p>For the purpose of this tutorial, we’ll install the Sequelize Node package manager in our Node application in order to set up the ORM communication to the database.</p>
<p>To set this up, execute <code>npm i sequelize</code>.</p>
<p>We’ll use a locally hosted MySQL database. To do this, we’ll install an npm package database driver. In this case, we will be installing <code>mysql2</code>. Here is a link to the <a target="_blank" href="https://www.npmjs.com/package/sequelize"><code>package</code></a></p>
<p>Run <code>npm i mysql2</code> to install it.</p>
<p>Let’s move on to configuring the connection to the database and building our demo project.</p>
<h2 id="heading-demo-project">Demo Project</h2>
<p>In this section we’ll build a simple backend server that performs Create-Read-Update-Delete operations, with the Sequelize library serving as the connection pipeline.</p>
<p>In order to begin the project, we’ll have to set up the database connection for our application. We’ll create a database connection file and set up our database credentials. You can name the file <strong>SequelizeConfig</strong>.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {

    <span class="hljs-attr">HOST</span>: <span class="hljs-string">"localhost"</span>,

    <span class="hljs-attr">USER</span>: <span class="hljs-string">"root"</span>,

    <span class="hljs-attr">PASSWORD</span>: <span class="hljs-string">""</span>,

    <span class="hljs-attr">DB</span>: <span class="hljs-string">"sequel"</span>,

    <span class="hljs-attr">dialect</span>: <span class="hljs-string">"mysql"</span>

}
</code></pre>
<p>In the code above, the database credentials were specified, along with the host address. In our case, the database is locally hosted, so localhost is the default host.</p>
<p>The database login details were also provided. The user here is the root, while the password was set to an empty string. This should be tweaked to ensure database security. I also created a defunct database named “sequel”.</p>
<p>The dialect refers to the type of database the user intends to use. In our case, the dialect is MySQL. Note that this can also be replicated on a cloud hosted database with the credentials obtained. With that, let's integrate the connection file with the application.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> SequelConfig = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../config/sequelize'</span>);

<span class="hljs-keyword">const</span> Sequelize = <span class="hljs-built_in">require</span>(<span class="hljs-string">'sequelize'</span>);

<span class="hljs-keyword">const</span> sequelize = <span class="hljs-keyword">new</span> Sequelize(SequelCOnfig.DB, SequelCOnfig.USER, SequelCOnfig.PASSWORD, {

    <span class="hljs-attr">host</span>: SequelCOnfig.HOST,

    <span class="hljs-attr">dialect</span>: SequelCOnfig.dialect

});
</code></pre>
<p>In order to facilitate a connection to the database, the variables in the config file were imported and initialized in the Sequelize setup file.</p>
<pre><code class="lang-javascript">

<span class="hljs-keyword">const</span> db = {};

db.Sequelize = Sequelize;

db.sequelize = sequelize;

db.user = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../model/user.model'</span>)(sequelize, Sequelize);

db.token = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../model/token.model'</span>)(sequelize, Sequelize)

<span class="hljs-built_in">module</span>.exports= db;
</code></pre>
<p>This file above imports the <code>config</code> file created previously and initializes the Sequelize library. The code then fetches the database details inputted in the config file and, when executed, creates the database.</p>
<p>Furthermore, the various database models which will be discussed subsequently are then integrated with the defunct database and generates a SQL database table .</p>
<p>To get this up and running, the database file created is invoked using the <code>sequelize.sync()</code> method. Any error encountered is logged and the database connection gets terminated.</p>
<pre><code class="lang-javascript">db.sequelize.sync().then(<span class="hljs-function">() =&gt;</span> {

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'user created '</span>);

}).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> {

  <span class="hljs-built_in">console</span>.error(err)

})
</code></pre>
<p>We’ll go on to discuss the database models.</p>
<h3 id="heading-models">Models</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Sequelize = <span class="hljs-built_in">require</span>(<span class="hljs-string">"sequelize"</span>);

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function">(<span class="hljs-params">sequelize</span>) =&gt;</span> {

sequelize.define(

<span class="hljs-string">"user"</span>, {

<span class="hljs-attr">firstName</span>: {

<span class="hljs-attr">type</span> : Sequelize.DataTypes.STRING,

<span class="hljs-attr">allowNull</span> : <span class="hljs-literal">false</span>

},

<span class="hljs-attr">lastName</span>: {

<span class="hljs-attr">type</span> : Sequelize.DataTypes.STRING,

<span class="hljs-attr">allowNull</span> : <span class="hljs-literal">false</span>

},

<span class="hljs-attr">email</span> : {

<span class="hljs-attr">type</span> : Sequelize.DataTypes.STRING,

<span class="hljs-attr">allowNull</span> : <span class="hljs-literal">false</span>, <span class="hljs-attr">unique</span>: <span class="hljs-literal">true</span>

},

<span class="hljs-attr">password</span>: {

<span class="hljs-attr">type</span> : Sequelize.DataTypes.STRING,

<span class="hljs-attr">allowNull</span> : <span class="hljs-literal">false</span>

},

<span class="hljs-attr">role</span>:  {

<span class="hljs-attr">type</span> : Sequelize.DataTypes.STRING,

<span class="hljs-attr">allowNull</span> : <span class="hljs-literal">false</span>

}

}

)

}
</code></pre>
<p>In the code above, the user model was initialized in Sequelize ORM and the field details were specified: <code>email</code>, <code>role</code>, <code>lastName</code>, and <code>password</code>. The type of data to be received was also specified.</p>
<p>It also provides an option to ensure the uniqueness of the user details, and the option to prevent the user from leaving some fields empty via the use of <code>allowNull = false</code>.</p>
<p>On execution of the application, the Sequelize ORM creates an SQL equivalent of the model as a data table.</p>
<p>Next, we’ll work on the CRUD functions in Node.js.</p>
<h3 id="heading-create-operation">Create Operation</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> createUser = <span class="hljs-keyword">async</span> (userInfo) =&gt; {

<span class="hljs-keyword">try</span> {

<span class="hljs-comment">// Check if the email already exists in the database</span>

<span class="hljs-keyword">const</span> ifEmailExists = <span class="hljs-keyword">await</span> User.findOne({ <span class="hljs-attr">where</span>: { <span class="hljs-attr">email</span>: userInfo.email } });

<span class="hljs-keyword">if</span> (ifEmailExists) {

<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ApiError(<span class="hljs-string">'Email has already been registered'</span>);

}

<span class="hljs-comment">// Create the new user</span>

<span class="hljs-keyword">const</span> newUser = <span class="hljs-keyword">await</span> User.create(userInfo);

<span class="hljs-keyword">return</span> newUser; <span class="hljs-comment">// Return the created user object</span>

} <span class="hljs-keyword">catch</span> (error) {

<span class="hljs-comment">// Handle errors such as validation or uniqueness constraint</span>

<span class="hljs-keyword">throw</span> error;

}

};
</code></pre>
<p>The function above highlights the controller function for creating user entries in the Express server.</p>
<p>The function is asynchronous, which allows for execution of some commands before eventual execution. The code ensures that the user email doesn’t exist in the database before cresting a new user.</p>
<p>In addition, we also ensured that each email field is unique. If the user details are entered into the database successfully, a “successful” response is sent back to the server. Additionally, any error encountered leads to termination of the function and the error gets sent back to the server.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728593905405/ae2b4fb6-0dfd-4e68-890b-a5d1afc88d71.png" alt="A POST request to create a new user endpoint" class="image--center mx-auto" width="930" height="386" loading="lazy"></p>
<h3 id="heading-read-operation">Read Operation</h3>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> FetchUser = <span class="hljs-keyword">async</span> (userId) =&gt; {

<span class="hljs-keyword">let</span> userDets;

<span class="hljs-keyword">if</span> (userId) {

<span class="hljs-comment">// Fetch a single user by ID if userId is provided</span>

userDets = <span class="hljs-keyword">await</span> User.findOne({ <span class="hljs-attr">where</span>: { <span class="hljs-attr">id</span>: userId } });

<span class="hljs-comment">// Check if the user exists</span>

<span class="hljs-keyword">if</span> (!userDets) {

<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ApiError(httpStatus.NOT_FOUND, <span class="hljs-string">'User not found'</span>);

}

} <span class="hljs-keyword">else</span> {

<span class="hljs-comment">// Fetch all users if no userId is provided</span>

userDets = <span class="hljs-keyword">await</span> User.findAll();

<span class="hljs-comment">// Check if any users were found</span>

<span class="hljs-keyword">if</span> (userDets.length === <span class="hljs-number">0</span>) {

<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ApiError(httpStatus.NOT_FOUND, <span class="hljs-string">'No users found'</span>);

}

}
</code></pre>
<p>The read operation fetches the desired query and sends it back to the user without modification. The user ID, which should be unique, is used to search for a specific user. In this scenario, we want access to all the users created in the database.</p>
<p>In case the requested query is not found, an appropriate error code is generated.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728593791809/3e4dae9d-98ad-4966-b17a-b9649c41921d.png" alt="A GET request  to fetch all the users in the database endpoint" class="image--center mx-auto" width="1009" height="364" loading="lazy"></p>
<h3 id="heading-update-operation">Update Operation</h3>
<pre><code class="lang-javascript">

<span class="hljs-keyword">const</span> updateUser = <span class="hljs-keyword">async</span> (userId, userDetails) =&gt; {

<span class="hljs-comment">// First, find the user by their ID</span>

<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ <span class="hljs-attr">where</span>: { <span class="hljs-attr">id</span>: userId } });

<span class="hljs-keyword">if</span> (!user) {

<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ApiError(httpStatus.BAD_REQUEST, <span class="hljs-string">"User doesn't exist"</span>);

}

<span class="hljs-comment">// Update the user with the new details</span>

<span class="hljs-keyword">await</span> User.update(userDetails, { <span class="hljs-attr">where</span>: { <span class="hljs-attr">id</span>: userId } });

<span class="hljs-comment">// Fetch the updated user to return it</span>

<span class="hljs-keyword">const</span> updatedUser = <span class="hljs-keyword">await</span> User.findOne({ <span class="hljs-attr">where</span>: { <span class="hljs-attr">id</span>: userId } });

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Updated user:'</span>, updatedUser); <span class="hljs-comment">// Log the updated user</span>

<span class="hljs-keyword">return</span> updatedUser; <span class="hljs-comment">// Return the updated user object</span>

};
</code></pre>
<p>The update operation aims to modify the data entered in previous operations. That is, to update some data fields.</p>
<p>In the case of Sequelize, the <code>update</code> method is invoked. To succeed with this, the particular user to be edited must be identified. The code above then generates the updated data field and sends it as the output of a successful request.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728593828831/33a2bf88-7f4c-4847-b139-e4d97dcd805b.png" alt="A PUT request to edit and update user details endpoint" class="image--center mx-auto" width="938" height="290" loading="lazy"></p>
<h3 id="heading-delete-operation">Delete Operation</h3>
<pre><code class="lang-javascript">

<span class="hljs-keyword">const</span> deleteUser = <span class="hljs-keyword">async</span> (userId) =&gt; {

<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ <span class="hljs-attr">where</span>: { <span class="hljs-attr">id</span>: userId } });

<span class="hljs-keyword">if</span> (!user) {

<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> ApiError(httpStatus.BAD_REQUEST, <span class="hljs-string">"User doesn't exist"</span>);

}

<span class="hljs-comment">// Delete the user</span>

<span class="hljs-keyword">await</span> user.destroy();

<span class="hljs-built_in">console</span>.log(<span class="hljs-string">'Deleted user:'</span>, user); <span class="hljs-comment">// Log the deleted user</span>

<span class="hljs-keyword">return</span> user; <span class="hljs-comment">// Return the deleted user object (useful for confirmation)</span>

};
</code></pre>
<p>The delete operation is invoked when data in the database table needs to be deleted. Sequelize makes provision for this via the use of the <code>destroy</code> method. This method deletes a specific user. When executed, a success response code is displayed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1728593876348/ad01b671-5e93-4e34-afbb-dc2a961d576e.png" alt="A DELETE request to remove a particular user detail from the database" class="image--center mx-auto" width="1032" height="248" loading="lazy"></p>
<h2 id="heading-additional-information">Additional Information</h2>
<p>So far, we have integrated an ORM library to serve as a connection between our backend application and our relational database. We also explored advanced concepts such as database migrations and CRUD operations. To learn more about this, you can explore the documentation and utilize it in building more complex projects, as hands-on learning is much encouraged.</p>
<p>Feel free to reach out to me on my <a target="_blank" href="http://dev.to/oluwatobi2001">blog</a> and check out my other articles <a target="_blank" href="https://linktr.ee/tobilyn77">here</a>. Till next time, keep on coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Work with SQLite in Python – A Handbook for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ SQLite is one of the most popular relational database management systems (RDBMS). It’s lightweight, meaning that it doesn’t take up much space on your system. One of its best features is that it’s serverless, so you don’t need to install or manage a ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/work-with-sqlite-in-python-handbook/</link>
                <guid isPermaLink="false">66fd1605986ae3c9e56b8ba4</guid>
                
                    <category>
                        <![CDATA[ SQLite ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ashutosh Krishna ]]>
                </dc:creator>
                <pubDate>Wed, 02 Oct 2024 09:44:37 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1727862097228/24433377-ebb8-49b5-b0ee-5736f629399d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>SQLite is one of the most popular relational database management systems (RDBMS). It’s lightweight, meaning that it doesn’t take up much space on your system. One of its best features is that it’s serverless, so you don’t need to install or manage a separate server to use it.</p>
<p>Instead, it stores everything in a simple file on your computer. It also requires zero configuration, so there’s no complicated setup process, making it perfect for beginners and small projects.</p>
<p>SQLite is a great choice for small to medium applications because it’s easy to use, fast, and can handle most tasks that bigger databases can do, but without the hassle of managing extra software. Whether you're building a personal project or prototyping a new app, SQLite is a solid option to get things up and running quickly.</p>
<p>In this tutorial, you'll learn how to work with SQLite using Python. Here’s what we’re going to cover in this tutorial:</p>
<ul>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-your-python-environment">How to Set Up Your Python Environment</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-an-sqlite-database">How to Create an SQLite Database</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-database-tables">How to Create Database Tables</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-insert-data-into-a-table">How to Insert Data into a Table</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-query-data">How to Query Data</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-update-and-delete-data">How to Update and Delete Data</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-use-transactions">How to Use Transactions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-optimize-sqlite-query-performance-with-indexing">How to Optimize SQLite Query Performance with Indexing</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-handle-errors-and-exceptions">How to Handle Errors and Exceptions</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-export-and-import-data-bonus-section">How to Export and Import Data [Bonus Section]</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></p>
</li>
</ul>
<p>This tutorial is perfect for anyone who wants to get started with databases without diving into complex setups.</p>
<h2 id="heading-how-to-set-up-your-python-environment">How to Set Up Your Python Environment</h2>
<p>Before working with SQLite, let’s ensure your Python environment is ready. Here’s how to set everything up.</p>
<h3 id="heading-installing-python">Installing Python</h3>
<p>If you don’t have Python installed on your system yet, you can download it from the official <a target="_blank" href="https://www.python.org/downloads/">Python website</a>. Follow the installation instructions for your operating system (Windows, macOS, or Linux).</p>
<p>To check if Python is installed, open your terminal (or command prompt) and type:</p>
<pre><code class="lang-bash">python --version
</code></pre>
<p>This should show the current version of Python installed. If it’s not installed, follow the instructions on the Python website.</p>
<h3 id="heading-installing-sqlite3-module">Installing SQLite3 Module</h3>
<p>The good news is that SQLite3 comes built-in with Python! You don’t need to install it separately because it’s included in the standard Python library. This means you can start using it right away without any additional setup.</p>
<h3 id="heading-how-to-create-a-virtual-environment-optional-but-recommended">How to Create a Virtual Environment (Optional but Recommended)</h3>
<p>It’s a good idea to create a virtual environment for each project to keep your dependencies organized. A virtual environment is like a clean slate where you can install packages without affecting your global Python installation.</p>
<p>To create a virtual environment, follow these steps:</p>
<ol>
<li><p>First, open your terminal or command prompt and navigate to the directory where you want to create your project.</p>
</li>
<li><p>Run the following command to create a virtual environment:</p>
</li>
</ol>
<pre><code class="lang-bash">python -m venv env
</code></pre>
<p>Here, <code>env</code> is the name of the virtual environment. You can name it anything you like.</p>
<ol start="3">
<li>Activate the virtual environment:</li>
</ol>
<pre><code class="lang-bash"><span class="hljs-comment"># Use the command for Windows</span>
env\Scripts\activate

<span class="hljs-comment"># Use the command for macOS/Linux:</span>
env/bin/activate
</code></pre>
<p>After activating the virtual environment, you’ll notice that your terminal prompt changes, showing the name of the virtual environment. This means you’re now working inside it.</p>
<h3 id="heading-installing-necessary-libraries">Installing Necessary Libraries</h3>
<p>We’ll need a few additional libraries for this project. Specifically, we’ll use:</p>
<ul>
<li><p><code>pandas</code>: This is an optional library for handling and displaying data in tabular format, useful for advanced use cases.</p>
</li>
<li><p><code>faker</code>: This library will help us generate fake data, like random names and addresses, which we can insert into our database for testing.</p>
</li>
</ul>
<p>To install <code>pandas</code> and <code>faker</code>, simply run the following commands:</p>
<pre><code class="lang-bash">pip install pandas faker
</code></pre>
<p>This installs both <code>pandas</code> and <code>faker</code> into your virtual environment. With this, your environment is set up, and you’re ready to start creating and managing your SQLite database in Python!</p>
<h2 id="heading-how-to-create-an-sqlite-database">How to Create an SQLite Database</h2>
<p>A database is a structured way to store and manage data so that it can be easily accessed, updated, and organized. It’s like a digital filing system that allows you to efficiently store large amounts of data, whether it’s for a simple app or a more complex system. Databases use tables to organize data, with rows and columns representing individual records and their attributes.</p>
<h3 id="heading-how-sqlite-databases-work">How SQLite Databases Work</h3>
<p>Unlike most other database systems, SQLite is a serverless database. This means that it doesn’t require setting up or managing a server, making it lightweight and easy to use. All the data is stored in a single file on your computer, which you can easily move, share, or back up. Despite its simplicity, SQLite is powerful enough to handle many common database tasks and is widely used in mobile apps, embedded systems, and small to medium-sized projects.</p>
<h3 id="heading-how-to-create-a-new-sqlite-database">How to Create a New SQLite Database</h3>
<p>Let’s create a new SQLite database and learn how to interact with it using Python’s <code>sqlite3</code> library.</p>
<h4 id="heading-connecting-to-the-database">Connecting to the Database</h4>
<p>Since <code>sqlite3</code> is pre-installed, you just need to import it in your Python script. To create a new database or connect to an existing one, we use the <code>sqlite3.connect()</code> method. This method takes the name of the database file as an argument. If the file doesn’t exist, SQLite will automatically create it.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Connect to the SQLite database (or create it if it doesn't exist)</span>
connection = sqlite3.connect(<span class="hljs-string">'my_database.db'</span>)
</code></pre>
<p>In this example, a file named <code>my_database.db</code> is created in the same directory as your script. If the file already exists, SQLite will just open the connection to it.</p>
<h4 id="heading-creating-a-cursor">Creating a Cursor</h4>
<p>Once you have a connection, the next step is to create a cursor object. The cursor is responsible for executing SQL commands and queries on the database.</p>
<pre><code class="lang-python"><span class="hljs-comment"># Create a cursor object</span>
cursor = connection.cursor()
</code></pre>
<h4 id="heading-closing-the-connection">Closing the Connection</h4>
<p>After you’ve finished working with the database, it’s important to close the connection to free up any resources. You can close the connection with the following command:</p>
<pre><code class="lang-python"><span class="hljs-comment"># Close the database connection</span>
connection.close()
</code></pre>
<p>However, you should only close the connection once you’re done with all your operations.</p>
<p>When you run your Python script, a file named <code>my_database.db</code> will be created in your current working directory. You’ve now successfully created your first SQLite database!</p>
<h3 id="heading-how-to-use-context-manager-to-open-and-close-connections">How to Use Context Manager to Open and Close Connections</h3>
<p>Python provides a more efficient and cleaner way to handle database connections using the <code>with</code> statement, also known as a context manager. The <code>with</code> statement automatically opens and closes the connection, ensuring that the connection is properly closed even if an error occurs during the database operations. This eliminates the need to manually call <code>connection.close()</code>.</p>
<p>Here’s how you can use the <code>with</code> statement to handle database connections:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Step 1: Use 'with' to connect to the database (or create one) and automatically close it when done</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:

    <span class="hljs-comment"># Step 2: Create a cursor object to interact with the database</span>
    cursor = connection.cursor()

    print(<span class="hljs-string">"Database created and connected successfully!"</span>)

<span class="hljs-comment"># No need to call connection.close(); it's done automatically!</span>
</code></pre>
<p>From now on, we’ll use the <code>with</code> statement in our upcoming code examples to manage database connections efficiently. This will make the code more concise and easier to maintain.</p>
<h2 id="heading-how-to-create-database-tables">How to Create Database Tables</h2>
<p>Now that we’ve created an SQLite database and connected to it, the next step is to create tables inside the database. A table is where we’ll store our data, organized in rows (records) and columns (attributes). For this example, we’ll create a table called <code>Students</code> to store information about students, which we’ll reuse in upcoming sections.</p>
<p>To create a table, we use SQL's <code>CREATE TABLE</code> statement. This command defines the table structure, including the column names and the data types for each column.</p>
<p>Here’s a simple SQL command to create a <code>Students</code> table with the following fields:</p>
<ul>
<li><p><code>id</code>: A unique identifier for each student (an integer).</p>
</li>
<li><p><strong>name</strong>: The student's name (text).</p>
</li>
<li><p><strong>age</strong>: The student's age (an integer).</p>
</li>
<li><p><strong>email</strong>: The student's email address (text).</p>
</li>
</ul>
<p>The SQL command to create this table would look like this:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> Students (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">INTEGER</span> PRIMARY <span class="hljs-keyword">KEY</span> AUTOINCREMENT,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    age <span class="hljs-built_in">INTEGER</span>,
    email <span class="hljs-built_in">TEXT</span>
);
</code></pre>
<p>We can execute this <code>CREATE TABLE</code> SQL command in Python using the <code>sqlite3</code> library. Let’s see how to do that.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Use 'with' to connect to the SQLite database and automatically close the connection when done</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:

    <span class="hljs-comment"># Create a cursor object</span>
    cursor = connection.cursor()

    <span class="hljs-comment"># Write the SQL command to create the Students table</span>
    create_table_query = <span class="hljs-string">'''
    CREATE TABLE IF NOT EXISTS Students (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        age INTEGER,
        email TEXT
    );
    '''</span>

    <span class="hljs-comment"># Execute the SQL command</span>
    cursor.execute(create_table_query)

    <span class="hljs-comment"># Commit the changes</span>
    connection.commit()

    <span class="hljs-comment"># Print a confirmation message</span>
    print(<span class="hljs-string">"Table 'Students' created successfully!"</span>)
</code></pre>
<ul>
<li><p><code>IF NOT EXISTS</code>: This ensures that the table is only created if it doesn’t already exist, preventing errors if the table has been created before.</p>
</li>
<li><p><code>connection.commit()</code>: This saves (commits) the changes to the database.</p>
</li>
</ul>
<p>When you run the Python code above, it will create the <code>Students</code> table in the <code>my_database.db</code> database file. You’ll also see a message in the terminal confirming that the table has been created successfully.</p>
<p>If you’re using Visual Studio Code, you can install the <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=qwtel.sqlite-viewer">SQLite Viewer</a> extension to view SQLite databases.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727514353100/522fc6f1-0363-41ca-a76a-b730470cb64a.png" alt="SQLite Viewer - VS Code Extension" class="image--center mx-auto" width="1240" height="532" loading="lazy"></p>
<h3 id="heading-data-types-in-sqlite-and-their-mapping-to-python">Data Types in SQLite and Their Mapping to Python</h3>
<p>SQLite supports several data types, which we need to understand when defining our tables. Here’s a quick overview of common SQLite data types and how they map to Python types:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>SQLite Data Type</td><td>Description</td><td>Python Equivalent</td></tr>
</thead>
<tbody>
<tr>
<td><strong>INTEGER</strong></td><td>Whole numbers</td><td><code>int</code></td></tr>
<tr>
<td><strong>TEXT</strong></td><td>Text strings</td><td><code>str</code></td></tr>
<tr>
<td><strong>REAL</strong></td><td>Floating-point numbers</td><td><code>float</code></td></tr>
<tr>
<td><strong>BLOB</strong></td><td>Binary data (e.g., images, files)</td><td><code>bytes</code></td></tr>
<tr>
<td><strong>NULL</strong></td><td>Represents no value or missing data</td><td><code>None</code></td></tr>
</tbody>
</table>
</div><p>In our <code>Students</code> table:</p>
<ul>
<li><p><code>id</code> is of type <code>INTEGER</code>, which maps to Python’s <code>int</code>.</p>
</li>
<li><p><code>name</code> and <code>email</code> are of type <code>TEXT</code>, which map to Python’s <code>str</code>.</p>
</li>
<li><p><code>age</code> is also of type <code>INTEGER</code>, mapping to Python’s <code>int</code>.</p>
</li>
</ul>
<h2 id="heading-how-to-insert-data-into-a-table">How to Insert Data into a Table</h2>
<p>Now that we have our <code>Students</code> table created, it’s time to start inserting data into the database. In this section, we’ll cover how to insert both single and multiple records using Python and SQLite, and how to avoid common security issues like SQL injection by using parameterized queries.</p>
<h3 id="heading-how-to-insert-a-single-record">How to Insert a Single Record</h3>
<p>To insert data into the database, we use the <code>INSERT INTO</code> SQL command. Let’s start by inserting a single record into our <code>Students</code> table.</p>
<p>Here’s the basic SQL syntax for inserting a single record:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> Students (<span class="hljs-keyword">name</span>, age, email) 
<span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'John Doe'</span>, <span class="hljs-number">20</span>, <span class="hljs-string">'johndoe@example.com'</span>);
</code></pre>
<p>However, instead of writing SQL directly in our Python script with hardcoded values, we’ll use parameterized queries to make our code more secure and flexible. Parameterized queries help prevent SQL injection, a common attack where malicious users can manipulate the SQL query by passing harmful input.</p>
<p>Here’s how we can insert a single record into the <code>Students</code> table using a parameterized query:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Use 'with' to open and close the connection automatically</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
    cursor = connection.cursor()

    <span class="hljs-comment"># Insert a record into the Students table</span>
    insert_query = <span class="hljs-string">'''
    INSERT INTO Students (name, age, email) 
    VALUES (?, ?, ?);
    '''</span>
    student_data = (<span class="hljs-string">'Jane Doe'</span>, <span class="hljs-number">23</span>, <span class="hljs-string">'jane@example.com'</span>)

    cursor.execute(insert_query, student_data)

    <span class="hljs-comment"># Commit the changes automatically</span>
    connection.commit()

    <span class="hljs-comment"># No need to call connection.close(); it's done automatically!</span>
    print(<span class="hljs-string">"Record inserted successfully!"</span>)
</code></pre>
<p>The <code>?</code> placeholders represent the values to be inserted into the table. The actual values are passed as a tuple (<code>student_data</code>) in the <code>cursor.execute()</code> method.</p>
<h3 id="heading-how-to-insert-multiple-records">How to Insert Multiple Records</h3>
<p>If you want to insert multiple records at once, you can use the <code>executemany()</code> method in Python. This method takes a list of tuples, where each tuple represents one record.</p>
<p>To make our example more dynamic, we can use the <code>Faker</code> library to generate random student data. This is useful for testing and simulating real-world scenarios.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> faker <span class="hljs-keyword">import</span> Faker
<span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Initialize Faker</span>
fake = Faker([<span class="hljs-string">'en_IN'</span>])

<span class="hljs-comment"># Use 'with' to open and close the connection automatically</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
    cursor = connection.cursor()

    <span class="hljs-comment"># Insert a record into the Students table</span>
    insert_query = <span class="hljs-string">'''
    INSERT INTO Students (name, age, email) 
    VALUES (?, ?, ?);
    '''</span>
    students_data = [(fake.name(), fake.random_int(
        min=<span class="hljs-number">18</span>, max=<span class="hljs-number">25</span>), fake.email()) <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(<span class="hljs-number">5</span>)]

    <span class="hljs-comment"># Execute the query for multiple records</span>
    cursor.executemany(insert_query, students_data)

    <span class="hljs-comment"># Commit the changes</span>
    connection.commit()

    <span class="hljs-comment"># Print confirmation message</span>
    print(<span class="hljs-string">"Fake student records inserted successfully!"</span>)
</code></pre>
<p>In this code:</p>
<ul>
<li><p><code>Faker()</code> generates random names, ages, and emails for students. Passing the locale(<code>[‘en_IN’]</code>) is optional.</p>
</li>
<li><p><code>cursor.executemany()</code>: This method allows us to insert multiple records at once, making the code more efficient.</p>
</li>
<li><p><code>students_data</code>: A list of tuples where each tuple represents one student’s data.</p>
</li>
</ul>
<h3 id="heading-how-to-handle-common-issues-sql-injection">How to Handle Common Issues: SQL Injection</h3>
<p>SQL injection is a security vulnerability where attackers can insert or manipulate SQL queries by providing harmful input. For example, an attacker might try to inject code like <code>'; DROP TABLE Students; --</code> to delete the table.</p>
<p>By using parameterized queries (as demonstrated above), we avoid this issue. The <code>?</code> placeholders in parameterized queries ensure that input values are treated as data, not as part of the SQL command. This makes it impossible for malicious code to be executed.</p>
<h2 id="heading-how-to-query-data">How to Query Data</h2>
<p>Now that we’ve inserted some data into our <code>Students</code> table, let’s learn how to retrieve the data from the table. We'll explore different methods for fetching data in Python, including <code>fetchone()</code>, <code>fetchall()</code>, and <code>fetchmany()</code>.</p>
<p>To query data from a table, we use the <code>SELECT</code> statement. Here’s a simple SQL command to select all columns from the <code>Students</code> table:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> Students;
</code></pre>
<p>This command retrieves all records and columns from the <code>Students</code> table. We can execute this <code>SELECT</code> query in Python and fetch the results.</p>
<h3 id="heading-how-to-fetch-all-records">How to Fetch All Records</h3>
<p>Here’s how we can fetch all records from the <code>Students</code> table:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Use 'with' to connect to the SQLite database</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:

    <span class="hljs-comment"># Create a cursor object</span>
    cursor = connection.cursor()

    <span class="hljs-comment"># Write the SQL command to select all records from the Students table</span>
    select_query = <span class="hljs-string">"SELECT * FROM Students;"</span>

    <span class="hljs-comment"># Execute the SQL command</span>
    cursor.execute(select_query)

    <span class="hljs-comment"># Fetch all records</span>
    all_students = cursor.fetchall()

    <span class="hljs-comment"># Display results in the terminal</span>
    print(<span class="hljs-string">"All Students:"</span>)
    <span class="hljs-keyword">for</span> student <span class="hljs-keyword">in</span> all_students:
        print(student)
</code></pre>
<p>In this example, the <code>fetchall()</code> method retrieves all rows returned by the query as a list of tuples.</p>
<pre><code class="lang-bash">All Students:
(1, <span class="hljs-string">'Jane Doe'</span>, 23, <span class="hljs-string">'jane@example.com'</span>)
(2, <span class="hljs-string">'Bahadurjit Sabharwal'</span>, 18, <span class="hljs-string">'tristanupadhyay@example.net'</span>)
(3, <span class="hljs-string">'Zayyan Arya'</span>, 20, <span class="hljs-string">'yashawinibhakta@example.org'</span>)
(4, <span class="hljs-string">'Hemani Shukla'</span>, 18, <span class="hljs-string">'gaurikanarula@example.com'</span>)
(5, <span class="hljs-string">'Warda Kara'</span>, 20, <span class="hljs-string">'npatil@example.net'</span>)
(6, <span class="hljs-string">'Mitali Nazareth'</span>, 19, <span class="hljs-string">'sparekh@example.org'</span>)
</code></pre>
<h3 id="heading-how-to-fetch-a-single-record">How to Fetch a Single Record</h3>
<p>If you want to retrieve only one record, you can use the <code>fetchone()</code> method:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Use 'with' to connect to the SQLite database</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:

    <span class="hljs-comment"># Create a cursor object</span>
    cursor = connection.cursor()

    <span class="hljs-comment"># Write the SQL command to select all records from the Students table</span>
    select_query = <span class="hljs-string">"SELECT * FROM Students;"</span>

    <span class="hljs-comment"># Execute the SQL command</span>
    cursor.execute(select_query)

    <span class="hljs-comment"># Fetch one record</span>
    student = cursor.fetchone()

    <span class="hljs-comment"># Display the result</span>
    print(<span class="hljs-string">"First Student:"</span>)
    print(student)
</code></pre>
<p>Output:</p>
<pre><code class="lang-bash">First Student:
(1, <span class="hljs-string">'Jane Doe'</span>, 23, <span class="hljs-string">'jane@example.com'</span>)
</code></pre>
<h3 id="heading-how-to-fetch-multiple-records">How to Fetch Multiple Records</h3>
<p>To fetch a specific number of records, you can use <code>fetchmany(size)</code>:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Use 'with' to connect to the SQLite database</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:

    <span class="hljs-comment"># Create a cursor object</span>
    cursor = connection.cursor()

    <span class="hljs-comment"># Write the SQL command to select all records from the Students table</span>
    select_query = <span class="hljs-string">"SELECT * FROM Students;"</span>

    <span class="hljs-comment"># Execute the SQL command</span>
    cursor.execute(select_query)

    <span class="hljs-comment"># Fetch three records</span>
    three_students = cursor.fetchmany(<span class="hljs-number">3</span>)

    <span class="hljs-comment"># Display results</span>
    print(<span class="hljs-string">"Three Students:"</span>)
    <span class="hljs-keyword">for</span> student <span class="hljs-keyword">in</span> three_students:
        print(student)
</code></pre>
<p>Output:</p>
<pre><code class="lang-bash">Three Students:
(1, <span class="hljs-string">'Jane Doe'</span>, 23, <span class="hljs-string">'jane@example.com'</span>)
(2, <span class="hljs-string">'Bahadurjit Sabharwal'</span>, 18, <span class="hljs-string">'tristanupadhyay@example.net'</span>)
(3, <span class="hljs-string">'Zayyan Arya'</span>, 20, <span class="hljs-string">'yashawinibhakta@example.org'</span>)
</code></pre>
<h3 id="heading-how-to-use-pandas-for-better-data-presentation">How to Use <code>pandas</code> for Better Data Presentation</h3>
<p>For better data presentation, we can use the <code>pandas</code> library to create a <code>DataFrame</code> from our query results. This makes it easier to manipulate and visualize the data.</p>
<p>Here’s how to fetch all records and display them as a pandas DataFrame:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd

<span class="hljs-comment"># Use 'with' to connect to the SQLite database</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
    <span class="hljs-comment"># Write the SQL command to select all records from the Students table</span>
    select_query = <span class="hljs-string">"SELECT * FROM Students;"</span>

    <span class="hljs-comment"># Use pandas to read SQL query directly into a DataFrame</span>
    df = pd.read_sql_query(select_query, connection)

<span class="hljs-comment"># Display the DataFrame</span>
print(<span class="hljs-string">"All Students as DataFrame:"</span>)
print(df)
</code></pre>
<p>Output:</p>
<pre><code class="lang-bash">All Students as DataFrame:
   id                  name  age                        email
0   1              Jane Doe   23             jane@example.com
1   2  Bahadurjit Sabharwal   18  tristanupadhyay@example.net
2   3           Zayyan Arya   20  yashawinibhakta@example.org
3   4         Hemani Shukla   18    gaurikanarula@example.com
4   5            Warda Kara   20           npatil@example.net
5   6       Mitali Nazareth   19          sparekh@example.org
</code></pre>
<p>The <code>pd.read_sql_query()</code> function executes the SQL query and directly returns the results as a pandas DataFrame.</p>
<h2 id="heading-how-to-update-and-delete-data">How to Update and Delete Data</h2>
<p>In this section, we’ll learn how to update existing records and delete records from our <code>Students</code> table using SQL commands in Python. This is essential for managing and maintaining your data effectively.</p>
<h3 id="heading-updating-existing-records">Updating Existing Records</h3>
<p>To modify existing records in a database, we use the SQL <code>UPDATE</code> command. This command allows us to change the values of specific columns in one or more rows based on a specified condition.</p>
<p>For example, if we want to update a student's age, the SQL command would look like this:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">UPDATE</span> Students 
<span class="hljs-keyword">SET</span> age = <span class="hljs-number">21</span> 
<span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">name</span> = <span class="hljs-string">'Jane Doe'</span>;
</code></pre>
<p>Now, let’s write Python code to update a specific student's age in our <code>Students</code> table.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Use 'with' to connect to the SQLite database</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
    cursor = connection.cursor()

    <span class="hljs-comment"># SQL command to update a student's age</span>
    update_query = <span class="hljs-string">'''
    UPDATE Students 
    SET age = ? 
    WHERE name = ?;
    '''</span>

    <span class="hljs-comment"># Data for the update</span>
    new_age = <span class="hljs-number">21</span>
    student_name = <span class="hljs-string">'Jane Doe'</span>

    <span class="hljs-comment"># Execute the SQL command with the data</span>
    cursor.execute(update_query, (new_age, student_name))

    <span class="hljs-comment"># Commit the changes to save the update</span>
    connection.commit()

    <span class="hljs-comment"># Print a confirmation message</span>
    print(<span class="hljs-string">f"Updated age for <span class="hljs-subst">{student_name}</span> to <span class="hljs-subst">{new_age}</span>."</span>)
</code></pre>
<p>In this example, we used parameterized queries to prevent SQL injection.</p>
<h3 id="heading-how-to-delete-records-from-the-table">How to Delete Records from the Table</h3>
<p>To remove records from a database, we use the SQL <code>DELETE</code> command. This command allows us to delete one or more rows based on a specified condition.</p>
<p>For example, if we want to delete a student named 'Jane Doe', the SQL command would look like this:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">FROM</span> Students 
<span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">name</span> = <span class="hljs-string">'Jane Doe'</span>;
</code></pre>
<p>Let’s write Python code to delete a specific student from our <code>Students</code> table using the <code>with</code> statement.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Use 'with' to connect to the SQLite database</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
    cursor = connection.cursor()

    <span class="hljs-comment"># SQL command to delete a student</span>
    delete_query = <span class="hljs-string">'''
    DELETE FROM Students 
    WHERE name = ?;
    '''</span>

    <span class="hljs-comment"># Name of the student to be deleted</span>
    student_name = <span class="hljs-string">'Jane Doe'</span>

    <span class="hljs-comment"># Execute the SQL command with the data</span>
    cursor.execute(delete_query, (student_name,))

    <span class="hljs-comment"># Commit the changes to save the deletion</span>
    connection.commit()

    <span class="hljs-comment"># Print a confirmation message</span>
    print(<span class="hljs-string">f"Deleted student record for <span class="hljs-subst">{student_name}</span>."</span>)
</code></pre>
<h4 id="heading-important-considerations">Important Considerations</h4>
<ul>
<li><p><strong>Conditions</strong>: Always use the <code>WHERE</code> clause when updating or deleting records to avoid modifying or removing all rows in the table. Without a <code>WHERE</code> clause, the command affects every row in the table.</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1727519069500/f22be4cc-e75f-4492-af01-ed08f31361f3.jpeg" alt="357089 rows affected Meme" class="image--center mx-auto" width="939" height="939" loading="lazy"></p>
</li>
<li><p><strong>Backup</strong>: It’s good practice to back up your database before performing updates or deletions, especially in production environments.</p>
</li>
</ul>
<h2 id="heading-how-to-use-transactions">How to Use Transactions</h2>
<p>A transaction is a sequence of one or more SQL operations that are treated as a single unit of work. In the context of a database, a transaction allows you to perform multiple operations that either all succeed or none at all. This ensures that your database remains in a consistent state, even in the face of errors or unexpected issues.</p>
<p>For example, if you are transferring money between two bank accounts, you would want both the debit from one account and the credit to the other to succeed or fail together. If one operation fails, the other should not be executed to maintain consistency.</p>
<h3 id="heading-why-use-transactions">Why Use Transactions?</h3>
<ol>
<li><p><strong>Atomicity</strong>: Transactions ensure that a series of operations are treated as a single unit. If one operation fails, none of the operations will be applied to the database.</p>
</li>
<li><p><strong>Consistency</strong>: Transactions help maintain the integrity of the database by ensuring that all rules and constraints are followed.</p>
</li>
<li><p><strong>Isolation</strong>: Each transaction operates independently of others, preventing unintended interference.</p>
</li>
<li><p><strong>Durability</strong>: Once a transaction is committed, the changes are permanent, even in the event of a system failure.</p>
</li>
</ol>
<h3 id="heading-when-to-use-transactions">When to Use Transactions?</h3>
<p>You should use transactions when:</p>
<ul>
<li><p>Performing multiple related operations that must succeed or fail together.</p>
</li>
<li><p>Modifying critical data that requires consistency and integrity.</p>
</li>
<li><p>Working with operations that can potentially fail, such as financial transactions or data migrations.</p>
</li>
</ul>
<h3 id="heading-how-to-manage-transactions-in-python">How to Manage Transactions in Python</h3>
<p>In SQLite, transactions are managed using the <code>BEGIN</code>, <code>COMMIT</code>, and <code>ROLLBACK</code> commands. However, when using the <code>sqlite3</code> module in Python, you typically manage transactions through the connection object.</p>
<h5 id="heading-starting-a-transaction">Starting a Transaction</h5>
<p>A transaction begins implicitly when you execute any SQL statement. To start a transaction explicitly, you can use the <code>BEGIN</code> command:</p>
<pre><code class="lang-python">cursor.execute(<span class="hljs-string">"BEGIN;"</span>)
</code></pre>
<p>However, it’s usually unnecessary to start a transaction manually, as SQLite starts a transaction automatically when you execute an SQL statement.</p>
<h5 id="heading-how-to-commit-a-transaction">How to Commit a Transaction</h5>
<p>To save all changes made during a transaction, you use the <code>commit()</code> method. This makes all modifications permanent in the database.</p>
<pre><code class="lang-python">connection.commit()
</code></pre>
<p>We have already used the <code>commit()</code> method in the above provided examples.</p>
<h5 id="heading-rolling-back-a-transaction">Rolling Back a Transaction</h5>
<p>If something goes wrong and you want to revert the changes made during a transaction, you can use the <code>rollback()</code> method. This will undo all changes made since the transaction started.</p>
<pre><code class="lang-python">connection.rollback()
</code></pre>
<h3 id="heading-example-of-using-transactions-in-python">Example of Using Transactions in Python</h3>
<p>To illustrate the use of transactions in a real-world scenario, we’ll create a new table called <code>Customers</code> to manage customer accounts. In this example, we’ll assume each customer has a <code>balance</code>. We will add two customers to this table and perform a funds transfer operation between them.</p>
<p>First, let's create the <code>Customers</code> table and insert two customers:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3

<span class="hljs-comment"># Create the Customers table and add two customers</span>
<span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
    cursor = connection.cursor()

    <span class="hljs-comment"># Create Customers table</span>
    create_customers_table = <span class="hljs-string">'''
    CREATE TABLE IF NOT EXISTS Customers (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL UNIQUE,
        balance REAL NOT NULL
    );
    '''</span>
    cursor.execute(create_customers_table)

    <span class="hljs-comment"># Insert two customers</span>
    cursor.execute(
        <span class="hljs-string">"INSERT INTO Customers (name, balance) VALUES (?, ?);"</span>, (<span class="hljs-string">'Ashutosh'</span>, <span class="hljs-number">100.0</span>))
    cursor.execute(
        <span class="hljs-string">"INSERT INTO Customers (name, balance) VALUES (?, ?);"</span>, (<span class="hljs-string">'Krishna'</span>, <span class="hljs-number">50.0</span>))

    connection.commit()
</code></pre>
<p>Now, let’s perform the funds transfer operation between Ashutosh and Krishna:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">transfer_funds</span>(<span class="hljs-params">from_customer, to_customer, amount</span>):</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-keyword">try</span>:
            <span class="hljs-comment"># Start a transaction</span>
            cursor.execute(<span class="hljs-string">"BEGIN;"</span>)

            <span class="hljs-comment"># Deduct amount from the sender</span>
            cursor.execute(
                <span class="hljs-string">"UPDATE Customers SET balance = balance - ? WHERE name = ?;"</span>, (amount, from_customer))
            <span class="hljs-comment"># Add amount to the receiver</span>
            cursor.execute(
                <span class="hljs-string">"UPDATE Customers SET balance = balance + ? WHERE name = ?;"</span>, (amount, to_customer))

            <span class="hljs-comment"># Commit the changes</span>
            connection.commit()
            print(
                <span class="hljs-string">f"Transferred <span class="hljs-subst">{amount}</span> from <span class="hljs-subst">{from_customer}</span> to <span class="hljs-subst">{to_customer}</span>."</span>)

        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            <span class="hljs-comment"># If an error occurs, rollback the transaction</span>
            connection.rollback()
            print(<span class="hljs-string">f"Transaction failed: <span class="hljs-subst">{e}</span>"</span>)


<span class="hljs-comment"># Example usage</span>
transfer_funds(<span class="hljs-string">'Ashutosh'</span>, <span class="hljs-string">'Krishna'</span>, <span class="hljs-number">80.0</span>)
</code></pre>
<p>In this example, we first created a <code>Customers</code> table and inserted two customers, Ashutosh with a balance of ₹100, and Krishna with a balance of ₹50. We then performed a funds transfer of ₹80 from Ashutosh to Krishna. By using transactions, we ensure that both the debit from Ashutosh's account and the credit to Krishna's account are executed as a single atomic operation, maintaining data integrity in the event of any errors. If the transfer fails (for example, due to insufficient funds), the transaction will roll back, leaving both accounts unchanged.</p>
<h2 id="heading-how-to-optimize-sqlite-query-performance-with-indexing">How to Optimize SQLite Query Performance with Indexing</h2>
<p>Indexing is a powerful technique used in databases to improve query performance. An index is essentially a data structure that stores the location of rows based on specific column values, much like an index at the back of a book helps you quickly locate a topic.</p>
<p>Without an index, SQLite has to scan the entire table row by row to find the relevant data, which becomes inefficient as the dataset grows. By using an index, SQLite can jump directly to the rows you need, significantly speeding up query execution.</p>
<h3 id="heading-how-to-populate-the-database-with-fake-data">How to Populate the Database with Fake Data</h3>
<p>To effectively test the impact of indexing, we need a sizable dataset. Instead of manually adding records, we can use the <code>faker</code> library to quickly generate fake data. In this section, we’ll generate 10,000 fake records and insert them into our <code>Students</code> table. This will simulate a real-world scenario where databases grow large, and query performance becomes important.</p>
<p>We will use the <code>executemany()</code> method to insert the records as below:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3
<span class="hljs-keyword">from</span> faker <span class="hljs-keyword">import</span> Faker

<span class="hljs-comment"># Initialize the Faker library</span>
fake = Faker([<span class="hljs-string">'en_IN'</span>])


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">insert_fake_students</span>(<span class="hljs-params">num_records</span>):</span>
    <span class="hljs-string">"""Generate and insert fake student data into the Students table."""</span>
    fake_data = [(fake.name(), fake.random_int(min=<span class="hljs-number">18</span>, max=<span class="hljs-number">25</span>),
                  fake.email()) <span class="hljs-keyword">for</span> _ <span class="hljs-keyword">in</span> range(num_records)]

    <span class="hljs-comment"># Use 'with' to handle the database connection</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># Insert fake data into the Students table</span>
        cursor.executemany(<span class="hljs-string">'''
        INSERT INTO Students (name, age, email) 
        VALUES (?, ?, ?);
        '''</span>, fake_data)

        connection.commit()

    print(<span class="hljs-string">f"<span class="hljs-subst">{num_records}</span> fake student records inserted successfully."</span>)


<span class="hljs-comment"># Insert 10,000 fake records into the Students table</span>
insert_fake_students(<span class="hljs-number">10000</span>)
</code></pre>
<p>By running this script, 10,000 fake student records will be added to the <code>Students</code> table. In the next section, we'll query the database and compare the performance of queries with and without indexing.</p>
<h3 id="heading-how-to-query-without-indexes">How to Query Without Indexes</h3>
<p>In this section, we’ll query the <code>Students</code> table without any indexes to observe how SQLite performs when there are no optimizations in place. This will serve as a baseline to compare the performance when we add indexes later.</p>
<p>Without indexes, SQLite performs a full table scan, which means that it must check every row in the table to find matching results. For small datasets, this is manageable, but as the number of records grows, the time taken to search increases dramatically. Let’s see this in action by running a basic <code>SELECT</code> query to search for a specific student by name and measure how long it takes.</p>
<p>First, we’ll query the <code>Students</code> table by looking for a student with a specific name. We’ll log the time taken to execute the query using Python’s <code>time</code> module to measure the performance.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3
<span class="hljs-keyword">import</span> time


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">query_without_index</span>(<span class="hljs-params">search_name</span>):</span>
    <span class="hljs-string">"""Query the Students table by name without an index and measure the time taken."""</span>

    <span class="hljs-comment"># Connect to the database using 'with'</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># Measure the start time</span>
        start_time = time.perf_counter_ns()

        <span class="hljs-comment"># Perform a SELECT query to find a student by name</span>
        cursor.execute(<span class="hljs-string">'''
        SELECT * FROM Students WHERE name = ?;
        '''</span>, (search_name,))

        <span class="hljs-comment"># Fetch all results (there should be only one or a few in practice)</span>
        results = cursor.fetchall()

        <span class="hljs-comment"># Measure the end time</span>
        end_time = time.perf_counter_ns()

        <span class="hljs-comment"># Calculate the total time taken</span>
        elapsed_time = (end_time - start_time) / <span class="hljs-number">1000</span>

        <span class="hljs-comment"># Display the results and the time taken</span>
        print(<span class="hljs-string">f"Query completed in <span class="hljs-subst">{elapsed_time:<span class="hljs-number">.5</span>f}</span> microseconds."</span>)
        print(<span class="hljs-string">"Results:"</span>, results)


<span class="hljs-comment"># Example: Searching for a student by name</span>
query_without_index(<span class="hljs-string">'Ojasvi Dhawan'</span>)
</code></pre>
<p>Here’s the output:</p>
<pre><code class="lang-bash">Query completed <span class="hljs-keyword">in</span> 1578.10000 microseconds.
Results: [(104, <span class="hljs-string">'Ojasvi Dhawan'</span>, 21, <span class="hljs-string">'lavanya26@example.com'</span>)]
</code></pre>
<p>By running the above script, you'll see how long it takes to search the <code>Students</code> table without any indexes. For example, if there are 10,000 records in the table, the query might take 1000-2000 microseconds depending on the size of the table and your hardware. This may not seem too slow for a small dataset, but the performance will degrade as more records are added.</p>
<p>We use <code>time.perf_counter_ns()</code> to measure the time taken for the query execution in nanoseconds. This method is highly accurate for benchmarking small time intervals. We convert the time to microseconds(<code>us</code>) for easier readability.</p>
<h3 id="heading-introducing-the-query-plan">Introducing the Query Plan</h3>
<p>When working with databases, understanding how queries are executed can help you identify performance bottlenecks and optimize your code. SQLite provides a helpful tool for this called <code>EXPLAIN QUERY PLAN</code>, which allows you to analyze the steps SQLite takes to retrieve data.</p>
<p>In this section, we’ll introduce how to use <code>EXPLAIN QUERY PLAN</code> to visualize and understand the inner workings of a query—specifically, how SQLite performs a full table scan when no index is present.</p>
<p>Let’s use <code>EXPLAIN QUERY PLAN</code> to see how SQLite retrieves data from the <code>Students</code> table without any indexes. We’ll search for a student by name, and the query plan will reveal the steps SQLite takes to find the matching rows.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">explain_query</span>(<span class="hljs-params">search_name</span>):</span>
    <span class="hljs-string">"""Explain the query execution plan for a SELECT query without an index."""</span>

    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># Use EXPLAIN QUERY PLAN to analyze how the query is executed</span>
        cursor.execute(<span class="hljs-string">'''
        EXPLAIN QUERY PLAN
        SELECT * FROM Students WHERE name = ?;
        '''</span>, (search_name,))

        <span class="hljs-comment"># Fetch and display the query plan</span>
        query_plan = cursor.fetchall()

        print(<span class="hljs-string">"Query Plan:"</span>)
        <span class="hljs-keyword">for</span> step <span class="hljs-keyword">in</span> query_plan:
            print(step)


<span class="hljs-comment"># Example: Analyzing the query plan for searching by name</span>
explain_query(<span class="hljs-string">'Ojasvi Dhawan'</span>)
</code></pre>
<p>When you run this code, SQLite will return a breakdown of how it plans to execute the query. Here’s an example of what the output might look like:</p>
<pre><code class="lang-bash">Query Plan:
(2, 0, 0, <span class="hljs-string">'SCAN Students'</span>)
</code></pre>
<p>This indicates that SQLite is scanning the entire <code>Students</code> table (a full table scan) to find the rows where the <code>name</code> column matches the provided value (<code>Ojasvi Dhawan</code>). Since there is no index on the <code>name</code> column, SQLite must examine each row in the table.</p>
<h3 id="heading-how-to-create-an-index">How to Create an Index</h3>
<p>Creating an index on a column allows SQLite to find rows more quickly during query operations. Instead of scanning the entire table, SQLite can use the index to jump directly to the relevant rows, significantly speeding up queries—especially those involving large datasets.</p>
<p>To create an index, use the following SQL command:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">EXISTS</span> <span class="hljs-keyword">index</span>-<span class="hljs-keyword">name</span> <span class="hljs-keyword">ON</span> <span class="hljs-keyword">table</span> (<span class="hljs-keyword">column</span>(s));
</code></pre>
<p>In this example, we will create an index on the <code>name</code> column of the <code>Students</code> table. Here’s how you can do it using Python:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3
<span class="hljs-keyword">import</span> time


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">create_index</span>():</span>
    <span class="hljs-string">"""Create an index on the name column of the Students table."""</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># SQL command to create an index on the name column</span>
        create_index_query = <span class="hljs-string">'''
        CREATE INDEX IF NOT EXISTS idx_name ON Students (name);
        '''</span>

        <span class="hljs-comment"># Measure the start time</span>
        start_time = time.perf_counter_ns()

        <span class="hljs-comment"># Execute the SQL command to create the index</span>
        cursor.execute(create_index_query)

        <span class="hljs-comment"># Measure the start time</span>
        end_time = time.perf_counter_ns()

        <span class="hljs-comment"># Commit the changes</span>
        connection.commit()

        print(<span class="hljs-string">"Index on 'name' column created successfully!"</span>)

        <span class="hljs-comment"># Calculate the total time taken</span>
        elapsed_time = (end_time - start_time) / <span class="hljs-number">1000</span>

        <span class="hljs-comment"># Display the results and the time taken</span>
        print(<span class="hljs-string">f"Query completed in <span class="hljs-subst">{elapsed_time:<span class="hljs-number">.5</span>f}</span> microseconds."</span>)


<span class="hljs-comment"># Call the function to create the index</span>
create_index()
</code></pre>
<p>Output:</p>
<pre><code class="lang-bash">Index on <span class="hljs-string">'name'</span> column created successfully!
Query completed <span class="hljs-keyword">in</span> 102768.60000 microseconds.
</code></pre>
<p>Even though creating the index takes this long (102768.6 microseconds), it's a one-time operation. You will still get substantial speed-up when running multiple queries. In the following sections, we will query the database again to observe the performance improvements made possible by this index.</p>
<h3 id="heading-how-to-query-with-indexes">How to Query with Indexes</h3>
<p>In this section, we will perform the same <code>SELECT</code> query we executed earlier, but this time we will take advantage of the index we created on the <code>name</code> column of the <code>Students</code> table. We'll measure and log the execution time to observe the performance improvements provided by the index.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3
<span class="hljs-keyword">import</span> time


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">query_with_index</span>(<span class="hljs-params">student_name</span>):</span>
    <span class="hljs-string">"""Query the Students table using an index on the name column."""</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># SQL command to select a student by name</span>
        select_query = <span class="hljs-string">'SELECT * FROM Students WHERE name = ?;'</span>

        <span class="hljs-comment"># Measure the execution time</span>
        start_time = time.perf_counter_ns()  <span class="hljs-comment"># Start the timer</span>

        <span class="hljs-comment"># Execute the query with the provided student name</span>
        cursor.execute(select_query, (student_name,))
        result = cursor.fetchall()  <span class="hljs-comment"># Fetch all results</span>

        end_time = time.perf_counter_ns()  <span class="hljs-comment"># End the timer</span>

        <span class="hljs-comment"># Calculate the elapsed time in microseconds</span>
        execution_time = (end_time - start_time) / <span class="hljs-number">1000</span>

        <span class="hljs-comment"># Display results and execution time</span>
        print(<span class="hljs-string">f"Query result: <span class="hljs-subst">{result}</span>"</span>)
        print(<span class="hljs-string">f"Execution time with index: <span class="hljs-subst">{execution_time:<span class="hljs-number">.5</span>f}</span> microseconds"</span>)


<span class="hljs-comment"># Example: Searching for a student by name</span>
query_with_index(<span class="hljs-string">'Ojasvi Dhawan'</span>)
</code></pre>
<p>Here’s what we get in the output:</p>
<pre><code class="lang-bash">Query result: [(104, <span class="hljs-string">'Ojasvi Dhawan'</span>, 21, <span class="hljs-string">'lavanya26@example.com'</span>)]
Execution time with index: 390.70000 microseconds
</code></pre>
<p>We can observe a significant reduction in execution time compared to when the query was performed without an index.</p>
<p>Let’s analyze the query execution plan for the query with the index on the <code>name</code> column of the <code>Students</code> table. If you execute the same script again to explain the query, you’ll get the below output:</p>
<pre><code class="lang-bash">Query Plan:
(3, 0, 0, <span class="hljs-string">'SEARCH Students USING INDEX idx_name (name=?)'</span>)
</code></pre>
<p>The plan now shows that the query uses the index <code>idx_name</code>, significantly reducing the number of rows that need to be scanned, which leads to faster query execution.</p>
<h3 id="heading-comparing-performance-results">Comparing Performance Results</h3>
<p>Now, let's summarize the performance results we obtained when querying with and without indexes.</p>
<h4 id="heading-execution-time-comparison">Execution Time Comparison</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Query Type</td><td>Execution Time (microseconds)</td></tr>
</thead>
<tbody>
<tr>
<td>Without Index</td><td>1578.1</td></tr>
<tr>
<td>With Index</td><td>390.7</td></tr>
</tbody>
</table>
</div><h4 id="heading-performance-improvement-summary">Performance Improvement Summary</h4>
<ul>
<li><p>The query with the index is approximately 4.04 times faster than the query without the index.</p>
</li>
<li><p>The execution time improved by about 75.24% after adding the index.</p>
</li>
</ul>
<h3 id="heading-best-practices-for-using-indexes">Best Practices for Using Indexes</h3>
<p>Indexes can significantly enhance the performance of your SQLite database, but they should be used judiciously. Here are some best practices to consider when working with indexes:</p>
<h4 id="heading-when-and-why-to-use-indexes">When and Why to Use Indexes</h4>
<ol>
<li><p><strong>Frequent Query Columns</strong>: Use indexes on columns that are frequently used in <code>SELECT</code> queries, especially those used in <code>WHERE</code>, <code>JOIN</code>, and <code>ORDER BY</code> clauses. This is because indexing these columns can drastically reduce query execution time.</p>
</li>
<li><p><strong>Uniqueness Constraints</strong>: When you have columns that must hold unique values (like usernames or email addresses), creating an index can enforce this constraint efficiently.</p>
</li>
<li><p><strong>Large Datasets</strong>: For tables with a large number of records, indexes become increasingly beneficial. They enable quick lookups, which is essential for maintaining performance as your data grows.</p>
</li>
<li><p><strong>Composite Indexes</strong>: Consider creating composite indexes for queries that filter or sort by multiple columns. For example, if you often search for students by both <code>name</code> and <code>age</code>, an index on both columns can optimize such queries.</p>
</li>
</ol>
<h4 id="heading-potential-downsides-of-indexes">Potential Downsides of Indexes</h4>
<p>While indexes provide significant advantages, there are some potential downsides:</p>
<ol>
<li><p><strong>Slower Insert/Update Operations</strong>: When you insert or update records in a table with indexes, SQLite must also update the index, which can slow down these operations. This is because each insert or update requires additional overhead to maintain the index structure.</p>
</li>
<li><p><strong>Increased Storage Requirements</strong>: Indexes consume additional disk space. For large tables, the storage cost can be substantial. Consider this when designing your database schema, especially for systems with limited storage resources.</p>
</li>
<li><p><strong>Complex Index Management</strong>: Having too many indexes can complicate database management. It may lead to situations where you have redundant indexes, which can degrade performance rather than enhance it. Regularly reviewing and optimizing your indexes is a good practice.</p>
</li>
</ol>
<p>Indexes are powerful tools for optimizing database queries, but they require careful consideration. Striking a balance between improved read performance and the potential overhead on write operations is key. Here are some strategies for achieving this balance:</p>
<ul>
<li><p><strong>Monitor Query Performance</strong>: Use SQLite’s <code>EXPLAIN QUERY PLAN</code> to analyze how your queries perform with and without indexes. This can help identify which indexes are beneficial and which may be unnecessary.</p>
</li>
<li><p><strong>Regular Maintenance</strong>: Periodically review your indexes and assess whether they are still needed. Remove redundant or rarely used indexes to streamline your database operations.</p>
</li>
<li><p><strong>Test and Evaluate</strong>: Before implementing indexes in a production environment, conduct thorough testing to understand their impact on both read and write operations.</p>
</li>
</ul>
<p>By following these best practices, you can leverage the benefits of indexing while minimizing potential drawbacks, ultimately enhancing the performance and efficiency of your SQLite database.</p>
<h2 id="heading-how-to-handle-errors-and-exceptions">How to Handle Errors and Exceptions</h2>
<p>In this section, we’ll discuss how to handle errors and exceptions when working with SQLite in Python. Proper error handling is crucial for maintaining the integrity of your database and ensuring that your application behaves predictably.</p>
<h3 id="heading-common-errors-in-sqlite-operations">Common Errors in SQLite Operations</h3>
<p>When interacting with an SQLite database, several common errors may arise:</p>
<ol>
<li><p><strong>Constraint Violations</strong>: This occurs when you try to insert or update data that violates a database constraint, such as primary key uniqueness or foreign key constraints. For example, trying to insert a duplicate primary key will trigger an error.</p>
</li>
<li><p><strong>Data Type Mismatches</strong>: Attempting to insert data of the wrong type (for example, inserting a string where a number is expected) can lead to an error.</p>
</li>
<li><p><strong>Database Locked Errors</strong>: If a database is being written to by another process or connection, trying to access it can result in a "database is locked" error.</p>
</li>
<li><p><strong>Syntax Errors</strong>: Mistakes in your SQL syntax will result in errors when you try to execute your commands.</p>
</li>
</ol>
<h3 id="heading-how-to-use-pythons-exception-handling">How to Use Python's Exception Handling</h3>
<p>Python’s built-in <a target="_blank" href="https://blog.ashutoshkrris.in/exception-handling-in-python">exception handling</a> mechanisms (<code>try</code> and <code>except</code>) are essential for managing errors in SQLite operations. By using these constructs, you can catch exceptions and respond appropriately without crashing your program.</p>
<p>Here’s a basic example of how to handle errors when inserting data into the database:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">add_customer_with_error_handling</span>(<span class="hljs-params">name, balance</span>):</span>
    <span class="hljs-string">"""Add a new customer with error handling."""</span>
    <span class="hljs-keyword">try</span>:
        <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
            cursor = connection.cursor()
            cursor.execute(
                <span class="hljs-string">"INSERT INTO Customers (name, balance) VALUES (?, ?);"</span>, (name, balance))
            connection.commit()
            print(<span class="hljs-string">f"Added customer: <span class="hljs-subst">{name}</span> with balance: <span class="hljs-subst">{balance}</span>"</span>)

    <span class="hljs-keyword">except</span> sqlite3.IntegrityError <span class="hljs-keyword">as</span> e:
        print(<span class="hljs-string">f"Error: Integrity constraint violated - <span class="hljs-subst">{e}</span>"</span>)

    <span class="hljs-keyword">except</span> sqlite3.OperationalError <span class="hljs-keyword">as</span> e:
        print(<span class="hljs-string">f"Error: Operational issue - <span class="hljs-subst">{e}</span>"</span>)

    <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
        print(<span class="hljs-string">f"An unexpected error occurred: <span class="hljs-subst">{e}</span>"</span>)


<span class="hljs-comment"># Example usage</span>
add_customer_with_error_handling(<span class="hljs-string">'Vishakha'</span>, <span class="hljs-number">100.0</span>)  <span class="hljs-comment"># Valid</span>
add_customer_with_error_handling(<span class="hljs-string">'Vishakha'</span>, <span class="hljs-number">150.0</span>)  <span class="hljs-comment"># Duplicate entry</span>
</code></pre>
<p>In this example:</p>
<ul>
<li><p>We catch <code>IntegrityError</code>, which is raised for violations like unique constraints.</p>
</li>
<li><p>We catch <code>OperationalError</code> for general database-related issues (like database locked errors).</p>
</li>
<li><p>We also have a generic <code>except</code> block to handle any unexpected exceptions.</p>
</li>
</ul>
<p>Output:</p>
<pre><code class="lang-bash">Added customer: Vishakha with balance: 100.0
Error: Integrity constraint violated - UNIQUE constraint failed: Customers.name
</code></pre>
<h3 id="heading-best-practices-for-ensuring-database-integrity">Best Practices for Ensuring Database Integrity</h3>
<ol>
<li><p><strong>Use Transactions</strong>: Always use transactions (as discussed in the previous section) when performing multiple related operations. This helps ensure that either all operations succeed or none do, maintaining consistency.</p>
</li>
<li><p><strong>Validate Input Data</strong>: Before executing SQL commands, validate the input data to ensure it meets the expected criteria (for example, correct types, within allowable ranges).</p>
</li>
<li><p><strong>Catch Specific Exceptions</strong>: Always catch specific exceptions to handle different types of errors appropriately. This allows for clearer error handling and debugging.</p>
</li>
<li><p><strong>Log Errors</strong>: Instead of just printing errors to the console, consider logging them to a file or monitoring system. This will help you track issues in production.</p>
</li>
<li><p><strong>Graceful Degradation</strong>: Design your application to handle errors gracefully. If an operation fails, provide meaningful feedback to the user rather than crashing the application.</p>
</li>
<li><p><strong>Regularly Backup Data</strong>: Regularly back up your database to prevent data loss in case of critical failures or corruption.</p>
</li>
<li><p><strong>Use Prepared Statements</strong>: Prepared statements help prevent SQL injection attacks and can also provide better performance for repeated queries.</p>
</li>
</ol>
<h2 id="heading-how-to-export-and-import-data-bonus-section">How to Export and Import Data [Bonus Section]</h2>
<p>In this section, we will learn how to export data from an SQLite database to common formats like CSV and JSON, as well as how to import data into SQLite from these formats using Python. This is useful for data sharing, backup, and integration with other applications.</p>
<h3 id="heading-exporting-data-from-sqlite-to-csv">Exporting Data from SQLite to CSV</h3>
<p>Exporting data to a CSV (Comma-Separated Values) file is straightforward with Python’s built-in libraries. CSV files are widely used for data storage and exchange, making them a convenient format for exporting data.</p>
<p>Here’s how to export data from an SQLite table to a CSV file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> sqlite3
<span class="hljs-keyword">import</span> csv

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">export_to_csv</span>(<span class="hljs-params">file_name</span>):</span>
    <span class="hljs-string">"""Export data from the Customers table to a CSV file."""</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># Execute a query to fetch all customer data</span>
        cursor.execute(<span class="hljs-string">"SELECT * FROM Customers;"</span>)
        customers = cursor.fetchall()

        <span class="hljs-comment"># Write data to CSV</span>
        <span class="hljs-keyword">with</span> open(file_name, <span class="hljs-string">'w'</span>, newline=<span class="hljs-string">''</span>) <span class="hljs-keyword">as</span> csv_file:
            csv_writer = csv.writer(csv_file)
            csv_writer.writerow([<span class="hljs-string">'ID'</span>, <span class="hljs-string">'Name'</span>, <span class="hljs-string">'Balance'</span>])  <span class="hljs-comment"># Writing header</span>
            csv_writer.writerows(customers)  <span class="hljs-comment"># Writing data rows</span>

        print(<span class="hljs-string">f"Data exported successfully to <span class="hljs-subst">{file_name}</span>."</span>)

<span class="hljs-comment"># Example usage</span>
export_to_csv(<span class="hljs-string">'customers.csv'</span>)
</code></pre>
<h3 id="heading-how-to-export-data-to-json">How to Export Data to JSON</h3>
<p>Similarly, you can export data to a <a target="_blank" href="https://blog.ashutoshkrris.in/a-beginners-guide-to-the-json-module-in-python">JSON</a> (JavaScript Object Notation) file, which is a popular format for data interchange, especially in web applications.</p>
<p>Here’s an example of how to export data to JSON:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> sqlite3


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">export_to_json</span>(<span class="hljs-params">file_name</span>):</span>
    <span class="hljs-string">"""Export data from the Customers table to a JSON file."""</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># Execute a query to fetch all customer data</span>
        cursor.execute(<span class="hljs-string">"SELECT * FROM Customers;"</span>)
        customers = cursor.fetchall()

        <span class="hljs-comment"># Convert data to a list of dictionaries</span>
        customers_list = [{<span class="hljs-string">'ID'</span>: customer[<span class="hljs-number">0</span>], <span class="hljs-string">'Name'</span>: customer[<span class="hljs-number">1</span>],
                           <span class="hljs-string">'Balance'</span>: customer[<span class="hljs-number">2</span>]} <span class="hljs-keyword">for</span> customer <span class="hljs-keyword">in</span> customers]

        <span class="hljs-comment"># Write data to JSON</span>
        <span class="hljs-keyword">with</span> open(file_name, <span class="hljs-string">'w'</span>) <span class="hljs-keyword">as</span> json_file:
            json.dump(customers_list, json_file, indent=<span class="hljs-number">4</span>)

        print(<span class="hljs-string">f"Data exported successfully to <span class="hljs-subst">{file_name}</span>."</span>)


<span class="hljs-comment"># Example usage</span>
export_to_json(<span class="hljs-string">'customers.json'</span>)
</code></pre>
<h3 id="heading-how-to-import-data-into-sqlite-from-csv">How to Import Data into SQLite from CSV</h3>
<p>You can also import data from a CSV file into an SQLite database. This is useful for populating your database with existing datasets.</p>
<p>Here's how to import data from a CSV file:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> csv
<span class="hljs-keyword">import</span> sqlite3


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">import_from_csv</span>(<span class="hljs-params">file_name</span>):</span>
    <span class="hljs-string">"""Import data from a CSV file into the Customers table."""</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># Open the CSV file for reading</span>
        <span class="hljs-keyword">with</span> open(file_name, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> csv_file:
            csv_reader = csv.reader(csv_file)
            next(csv_reader)  <span class="hljs-comment"># Skip the header row</span>

            <span class="hljs-comment"># Insert each row into the Customers table</span>
            <span class="hljs-keyword">for</span> row <span class="hljs-keyword">in</span> csv_reader:
                cursor.execute(
                    <span class="hljs-string">"INSERT INTO Customers (name, balance) VALUES (?, ?);"</span>, (row[<span class="hljs-number">1</span>], row[<span class="hljs-number">2</span>]))

        connection.commit()
        print(<span class="hljs-string">f"Data imported successfully from <span class="hljs-subst">{file_name}</span>."</span>)


<span class="hljs-comment"># Example usage</span>
import_from_csv(<span class="hljs-string">'customer_data.csv'</span>)
</code></pre>
<h3 id="heading-how-to-import-data-into-sqlite-from-json">How to Import Data into SQLite from JSON</h3>
<p>Similarly, importing data from a JSON file is simple. You can read the JSON file and insert the data into your SQLite table.</p>
<p>Here's how to do it:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> json
<span class="hljs-keyword">import</span> sqlite3


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">import_from_json</span>(<span class="hljs-params">file_name</span>):</span>
    <span class="hljs-string">"""Import data from a JSON file into the Customers table."""</span>
    <span class="hljs-keyword">with</span> sqlite3.connect(<span class="hljs-string">'my_database.db'</span>) <span class="hljs-keyword">as</span> connection:
        cursor = connection.cursor()

        <span class="hljs-comment"># Open the JSON file for reading</span>
        <span class="hljs-keyword">with</span> open(file_name, <span class="hljs-string">'r'</span>) <span class="hljs-keyword">as</span> json_file:
            customers_list = json.load(json_file)

            <span class="hljs-comment"># Insert each customer into the Customers table</span>
            <span class="hljs-keyword">for</span> customer <span class="hljs-keyword">in</span> customers_list:
                cursor.execute(<span class="hljs-string">"INSERT INTO Customers (name, balance) VALUES (?, ?);"</span>, (customer[<span class="hljs-string">'Name'</span>], customer[<span class="hljs-string">'Balance'</span>]))

        connection.commit()
        print(<span class="hljs-string">f"Data imported successfully from <span class="hljs-subst">{file_name}</span>."</span>)


<span class="hljs-comment"># Example usage</span>
import_from_json(<span class="hljs-string">'customer_data.json'</span>)
</code></pre>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>And that’s a wrap! This guide has introduced you to the fundamentals of working with SQLite in Python, covering everything from setting up your environment to querying and manipulating data, as well as exporting and importing information. I hope you found it helpful and that it has sparked your interest in using SQLite for your projects.</p>
<p>Now it's time to put your newfound knowledge into practice! I encourage you to create your project using SQLite and Python. Whether it’s a simple application for managing your library, a budgeting tool, or something unique, the possibilities are endless.</p>
<p>Once you’ve completed your project, share it on Twitter and tag me! I’d love to see what you’ve created and celebrate your accomplishments.</p>
<p>You can find all the code from this tutorial on <a target="_blank" href="https://github.com/ashutoshkrris/sqlite-tutorial">GitHub</a>. Thank you for following along, and happy coding!</p>
<blockquote>
<p>Generate Table of Contents for your freeCodeCamp articles for free using the <a target="_blank" href="https://toc-generator.ashutoshkrris.in/freecodecamp">TOC Generator</a> tool.</p>
</blockquote>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Full Stack Development with Next.js, Clerk, and Neon Postgres ]]>
                </title>
                <description>
                    <![CDATA[ Full stack development is constantly evolving, with new developer tools and products being introduced that allow us to build secure and reliable applications more efficiently. In this tutorial, I’ll walk you through how to build highly performant web... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/nextjs-clerk-neon-fullstack-development/</link>
                <guid isPermaLink="false">66c375561784344f009b632f</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ankur Tyagi ]]>
                </dc:creator>
                <pubDate>Wed, 10 Jul 2024 15:31:12 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/Orange---Yellow-Gradient-Make-Design-Blog-Banner--77-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Full stack development is constantly evolving, with new developer tools and products being introduced that allow us to build secure and reliable applications more efficiently.</p>
<p>In this tutorial, I’ll walk you through how to build highly performant web applications with <a target="_blank" href="https://neon.tech">Neon – a serverless PostgreSQL</a> database designed for the cloud. You'll also learn how to perform CRUD (Create, Read, Update, and Delete) operations with Neon.</p>
<p>By the end of this tutorial, you will have the basic knowledge required to start building advanced and scalable web applications with Neon.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-neon">What is Neon?</a></li>
<li><a class="post-section-overview" href="#heading-why-neon">Why Neon?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-neon-to-a-nextjs-app">How to add Neon to a Next.js app</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-neon-serverless-driver-with-drizzle-orm-in-nextjs">How to set up Neon Serverless Driver with Drizzle ORM in Next.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-application-interface-with-nextjs">How to Build the Application Interface with Next.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-authenticate-users-with-clerk">How to Authenticate Users with Clerk</a></li>
<li><a class="post-section-overview" href="#heading-crud-operations-with-the-neon-database">CRUD Operations with the Neon Database</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
<li><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></li>
</ul>
<h2 id="heading-what-is-neon">What is Neon?</h2>
<p><a target="_blank" href="https://github.com/neondatabase/neon">Neon</a> is an open-source, scalable, and efficient Postgres DB that separates compute from storage. This means that database computation processes (queries, transactions, and so on) are handled by one set of resources (compute), while the data itself is stored on a separate set of resources (storage). </p>
<p>This architecture allows for greater scalability and performance, making Neon a solid choice for modern web applications.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXcT4hh-liS2uYYcatl8jC6h9gFqArEw113_WaPzoTFxeps_G97JIVhJKVSQq5DC52NJ0GOoQm4sYL5QhyLhC_e_xocDjSp7iks6j8kv6WSnhRzLVy8TxftshzFnwK238QuVsGdnnBpL_nLDmXju3klwlB6T?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="Neon - a serverless Postgres database" width="1600" height="416" loading="lazy">
<em><a target="_blank" href="https://neon.tech">Neon - a serverless Postgre</a>s database</em></p>
<h3 id="heading-3-things-to-remember-about-neon">3 Things to Remember About Neon:</h3>
<ul>
<li>🐘 <strong>Postgres</strong>: Neon is built on the foundation of Postgres. It supports the same extensions, drivers, and SQL syntax as Postgres, ensuring familiarity and ease of use.</li>
<li>☁️ <strong>Serverless</strong>: Neon operates on a serverless model. Your database is represented as a simple URL, and Neon automatically scales up and down based on workload demands. Say goodbye to over-provisioning.</li>
<li>🌱 <strong>Branching</strong>: Just like version control for code, Neon allows you to create instant, isolated copies of your data. This feature is invaluable for development, testing, and maintaining separate environments.</li>
</ul>
<h2 id="heading-why-neon">Why Neon?</h2>
<p>Neon brings the serverless experience to Postgres. Developers can build faster and scale their products effortlessly, without the need to dedicate big teams or big budgets to the database.</p>
<p>Neon supports <a target="_blank" href="https://neon.tech/docs/introduction#framework-and-language-quickstarts">multiple languages and frameworks</a> – but what are the unique features that make Neon stand out?</p>
<h3 id="heading-instant-branching-and-auto-scaling">Instant branching and auto-scaling</h3>
<p><a target="_blank" href="https://neon.tech/blog/why-you-want-a-database-that-scales-to-zero">Neon</a> allows you to <a target="_blank" href="https://neon.tech/branching">create database branches instantly</a> for testing, development, and staging environments. This lets you experiment without affecting the production database. </p>
<p>It also provides an <a target="_blank" href="https://neon.tech/docs/introduction/autoscaling">auto-scaling capability</a> that automatically adjusts resources based on the application's workload, ensuring optimal performance and cost-efficiency.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXc1HSzmptXUYsu49JPbWSizwp64G-JVME-7kmmwSKNYLcc1wUmwWXvBa6kuVxncpyazqSPgj_N4ABZddjNG2rDTHE8MFIGm3yKy1DPpiV6C7GZ1tOcTzOFkvtDwpleeJZC--V4efudtYnPe-XKs8K2P740R?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="Neon DB Main Branch Dashboard" width="1600" height="929" loading="lazy">
<em>Neon DB Main Dashboard</em></p>
<h3 id="heading-support-for-ai-applications">Support for AI applications</h3>
<p>Neon <a target="_blank" href="https://neon.tech/ai">supports AI and machine learning applications</a> by providing a high-performance and scalable infrastructure. It enables you to perform semantic and similarity searches in Postgres and handles complex queries and large datasets efficiently, making it ideal for AI or LLM applications.</p>
<h3 id="heading-open-source">Open-source</h3>
<p>Neon is backed by a <a target="_blank" href="https://github.com/neondatabase/neon">vibrant community</a> of Postgres hackers, systems engineers, and cloud engineers who are all huge fans of Postgres. </p>
<p>As an open-source platform, Neon offers transparency and flexibility. You can also reach out to the team and contributors to ask questions, contribute, and help improve the software.</p>
<h3 id="heading-serverless-architecture">Serverless Architecture</h3>
<p><a target="_blank" href="https://neon.tech/blog/architecture-decisions-in-neon">Neon</a> eliminates the need for manual server management, allowing you to focus on building applications rather than maintaining infrastructure. Its serverless nature provides on-demand scalability, ensuring that your application can handle varying loads without manual intervention.</p>
<h3 id="heading-built-upon-postgres">Built upon Postgres</h3>
<p>Postgres is one of the most reliable open-source relational <a target="_blank" href="https://neon.tech/blog/get-page-at-lsn">database</a> systems. Neon inherits all the advanced features, stability, and performance optimizations of Postgres, including support for ACID transactions, advanced SQL, and NoSQL/JSON, to create a cheaper and more efficient database for cloud environments.</p>
<h2 id="heading-how-to-add-neon-to-a-nextjs-app">How to Add Neon to a Next.js App</h2>
<p>Neon supports multiple frameworks and libraries and provides clear and detailed documentation on adding Neon to them. The Neon serverless driver enables us to connect and interact with Neon in a Next.js application.</p>
<p>Before we proceed, let’s <a target="_blank" href="https://neon.tech/docs/guides/nextjs">create a Neon account and project</a>.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXeLMMmCdF3yK_mflMt4mqz3woiTLibcrGCMK5-AE1f2KftVKYduH39BybuVdu68G2am8uwDWHJUyisFittXeqCmcgxNyhcZXIiXHjxIIu-eymmM_-VVdAMW0LWTVA7NrXI-QXNEYso3Sj1FrLX0tvSP6yNK?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="Neon DB Projects Overview" width="1600" height="938" loading="lazy">
<em>Neon DB Projects Overview: View and manage all your projects in one place.</em></p>
<p>Within your project dashboard, you'll find a database connection string. You'll use this to interact with your Neon database.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXfpIPj10xMleoJEKIMFghRGp2ofgC0zJEA0C2ExMr2ijz673RM_45Kuh1RXVKkEn_uW-hU6-YIPd35C73gYMZtN_7lvChQ4MSK47CIWovh8MyUzRQluguEhAEXdvRZ8wxpOLINIyVfp50u1gIOf3foBAzg3?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="Neon DB Project Dashboard" width="1600" height="897" loading="lazy">
<em>Neon DB Project Dashboard: Manage database settings with ease from the project dashboard.</em></p>
<p>Create a TypeScript Next.js project by running the following code snippet in your terminal:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npx create-next-app neon-blog-with-clerk</span></p></td></tr></tbody></table>

<p>Next, install the Neon Serverless package:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npm install @neondatabase/serverless</span></p></td></tr></tbody></table>

<p>Create a .env.local file and copy your database connection string into the file:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>NEON_DATABASE_URL=</span><span>"postgres://&lt;user&gt;:&lt;password&gt;@&lt;endpoint_hostname&gt;.neon.tech:&lt;port&gt;/&lt;dbname&gt;?sslmode=require"</span></p></td></tr></tbody></table>

<p>Create a 'db' folder containing an index.ts file within the Next.js app directory and copy the code snippet below into the file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { neon } <span class="hljs-keyword">from</span> <span class="hljs-string">'@neondatabase/serverless'</span>;

<span class="hljs-keyword">if</span> (!process.env.NEON_DATABASE_URL) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'NEON_DATABASE_URL must be a Neon postgres connection string'</span>)
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDBVersion = <span class="hljs-keyword">async</span>() =&gt; {
    <span class="hljs-keyword">const</span> sql = neon(process.env.NEON_DATABASE_URL!);
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> sql<span class="hljs-string">`SELECT version()`</span>;
    <span class="hljs-keyword">return</span> { <span class="hljs-attr">version</span>: response[<span class="hljs-number">0</span>].version }
}
</code></pre>
<p>Convert the app/page.tsx file to a server component and execute the <code>getDBVersion()</code> function:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { getDBVersion } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> { version } = <span class="hljs-keyword">await</span> getDBVersion();
    <span class="hljs-built_in">console</span>.log({version})

   <span class="hljs-keyword">return</span> (<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{/** — UI elements — */}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>)

}
</code></pre>
<p>The <code>getDBVersion()</code> function establishes a connection with the Neon database and allows us to run SQL queries using the Postgres client. This function returns the database version, which is then logged to the console.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>{</span><span><br></span><span>&nbsp; version: </span><span>'PostgreSQL 16.3 on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit'</span><span><br></span><span>}</span></p></td></tr></tbody></table>

<p>Congratulations – you’ve successfully added Neon to your Next.js application.</p>
<p>But interacting with the Neon database by writing SQL queries directly can require extra learning or introduce complexities for developers who are not familiar with SQL. It can also lead to errors or performance issues when performing complex queries. </p>
<p>This is why Neon supports database ORMs such as Drizzle ORM, which provide a higher-level interface for interacting with the database. <a target="_blank" href="https://orm.drizzle.team/docs/overview">Drizzle ORM</a> enables you to write complex query functions and interact with the database easily using TypeScript.</p>
<h2 id="heading-how-to-set-up-neon-serverless-driver-with-drizzle-orm-in-nextjs">How to Set Up Neon Serverless Driver with Drizzle ORM in Next.js</h2>
<p><a target="_blank" href="https://orm.drizzle.team/docs/overview">Drizzle ORM</a> lets you query data and perform various operations on the database using simple TypeScript query commands. It is lightweight, typesafe, and easy to use.</p>
<p>First, you'll need to install the <a target="_blank" href="https://orm.drizzle.team/kit-docs/overview">Drizzle Kit</a> and the <a target="_blank" href="https://orm.drizzle.team/docs/overview">Drizzle ORM</a> package.</p>
<p>Drizzle Kit lets you manage the database schema and migrations.</p>
<pre><code class="lang-bash">npm i drizzle-orm
npm i -D drizzle-kit
</code></pre>
<p>Inside the db folder, add an actions.ts, and schema.ts file:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> db
touch actions.ts schema.ts
</code></pre>
<p>Add the code snippet below into the db/schema.ts file. It contains the database schema.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {  text, serial, pgTable, timestamp } <span class="hljs-keyword">from</span> <span class="hljs-string">"drizzle-orm/pg-core"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> postsTable = pgTable(<span class="hljs-string">"posts"</span>, {
    <span class="hljs-attr">id</span>: serial(<span class="hljs-string">"id"</span>).primaryKey().notNull(),
    <span class="hljs-attr">content</span>: text(<span class="hljs-string">"content"</span>).notNull(),
    <span class="hljs-attr">author</span>: text(<span class="hljs-string">"author"</span>).notNull(),
    <span class="hljs-attr">author_id</span>: text(<span class="hljs-string">"author_id"</span>).notNull(),
    <span class="hljs-attr">title</span>: text(<span class="hljs-string">"title"</span>).notNull(),
    <span class="hljs-attr">created_at</span>: timestamp(<span class="hljs-string">"created_at"</span>).defaultNow(),
    <span class="hljs-attr">slug</span>: text(<span class="hljs-string">"slug"</span>).notNull(),
});
</code></pre>
<p>Update the db/index.ts file to connect to the Neon database and export the Drizzle instance (db). This will be used to execute typesafe SQL queries against your Postgres database hosted by Neon.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { neon } <span class="hljs-keyword">from</span> <span class="hljs-string">'@neondatabase/serverless'</span>;
<span class="hljs-keyword">import</span> { drizzle } <span class="hljs-keyword">from</span> <span class="hljs-string">'drizzle-orm/neon-http'</span>;
<span class="hljs-keyword">import</span> { postsTable } <span class="hljs-keyword">from</span> <span class="hljs-string">'./schema'</span>;


<span class="hljs-keyword">if</span> (!process.env.NEON_DATABASE_URL) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'DATABASE_URL must be a Neon postgres connection string'</span>)
}
<span class="hljs-keyword">const</span> sql = neon(process.env.NEON_DATABASE_URL!);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> db = drizzle(sql, {
  <span class="hljs-attr">schema</span>: { postsTable }
});
</code></pre>
<p>Next, create a drizzle.config.ts file at the root of the Next.js folder and add the following configuration:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> type { Config } <span class="hljs-keyword">from</span> <span class="hljs-string">'drizzle-kit'</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">"dotenv"</span>;

dotenv.config();

<span class="hljs-keyword">if</span> (!process.env.NEON_DATABASE_URL) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">'NEON DATABASE_URL not found in environment'</span>);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
  <span class="hljs-attr">schema</span>: <span class="hljs-string">'./src/app/db/schema.ts'</span>,
  <span class="hljs-attr">out</span>: <span class="hljs-string">'./src/app/db/migrations'</span>,
 <span class="hljs-attr">dialect</span>: <span class="hljs-string">"postgresql"</span>,
  <span class="hljs-attr">dbCredentials</span>: {
    <span class="hljs-attr">url</span>: process.env.NEON_DATABASE_URL,
 },
  <span class="hljs-attr">strict</span>: <span class="hljs-literal">true</span>,
} satisfies Config;
</code></pre>
<p>The drizzle.config.ts file contains all the information about your database connection, migration folder, and schema files. </p>
<p>Finally, update the package.json file to include the Drizzle Kit commands for generating database migrations and updating the tables.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>{</span><span><br></span><span> "scripts" : {</span><span><br></span><span>&nbsp; "migrate": </span><span>"npx drizzle-kit generate -- dotenv_config_path='.env.local'"</span><span>,</span><span><br></span><span>&nbsp; "db-create": </span><span>"npx drizzle-kit push -- dotenv_config_path='.env.local'"</span><span><br></span><span> }</span><span><br></span><span>}</span></p></td></tr></tbody></table>

<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-8.png" alt="Neon DB Tables Dashboard" width="600" height="400" loading="lazy">
<em>Neon DB Tables Dashboard: Effortlessly manage your database tables and view all data.</em></p>
<h2 id="heading-how-to-build-the-application-interface-with-nextjs">How to Build the Application Interface with Next.js</h2>
<p>In this section, you’ll learn how to build a blog application that allows users to read posts and authenticate authors, enabling them to create and delete posts from the Neon database. </p>
<p>The application is divided into 3 pages:</p>
<ul>
<li>Home Page: displays all the available blog posts.</li>
<li>Post Details Page (/posts/[slug]): displays the content of a particular blog post.</li>
<li>Create Post Page (/posts/create): allows authors to create new blog posts.</li>
</ul>
<p>Install the following packages:</p>
<pre><code class="lang-javascript">npm install date-fns react-simplemde-editor easymde react-markdown remark-gfm dotenv
</code></pre>
<p>The <a target="_blank" href="https://github.com/date-fns/date-fns">Date Fns package</a> allows us to convert the posts' timestamps to human-readable forms for display within the application. The <a target="_blank" href="https://www.npmjs.com/package/react-simplemde-editor">React SimpleMDE Editor</a> provides a WYSIWYG editor for creating content in markdown formats using an interactive editor, and the <a target="_blank" href="https://www.npmjs.com/package/react-markdown">React Markdown package</a> converts the markdown texts to their corresponding plain formats.</p>
<p>Next, create a utils.ts file within the Next.js app folder and copy the code snippet below into the file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { format } <span class="hljs-keyword">from</span> <span class="hljs-string">"date-fns"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> formatDateString = (dateString: <span class="hljs-built_in">Date</span> | <span class="hljs-literal">null</span>): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
    <span class="hljs-keyword">if</span> (!dateString) <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
    <span class="hljs-keyword">const</span> date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(dateString);
    <span class="hljs-keyword">const</span> formattedDate = format(date, <span class="hljs-string">"MMMM do yyyy, h:mma"</span>);
    <span class="hljs-keyword">return</span> formattedDate;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> slugifySentences = (sentence: string): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> slug = sentence
 .toLowerCase()
 .replace(<span class="hljs-regexp">/[^a-z0-9\s-]/g</span>, <span class="hljs-string">""</span>)
 .replace(<span class="hljs-regexp">/\s+/g</span>, <span class="hljs-string">"-"</span>);

    <span class="hljs-comment">// Generate 5 random letters</span>
    <span class="hljs-keyword">const</span> randomLetters = <span class="hljs-built_in">Array</span>.from({ <span class="hljs-attr">length</span>: <span class="hljs-number">5</span> }, <span class="hljs-function">() =&gt;</span>
        <span class="hljs-built_in">String</span>.fromCharCode(<span class="hljs-number">97</span> + <span class="hljs-built_in">Math</span>.floor(<span class="hljs-built_in">Math</span>.random() * <span class="hljs-number">26</span>))
 ).join(<span class="hljs-string">""</span>);

    <span class="hljs-keyword">return</span> <span class="hljs-string">`<span class="hljs-subst">${slug}</span>-<span class="hljs-subst">${randomLetters}</span>`</span>;
};
</code></pre>
<p>The <code>formatDateString</code> function accepts a Date object and returns the date and time in a human-readable format using the date-fns package. The <code>slugifySentences</code> function creates a slug for each post using the post's title, which is useful for implementing the routes for each post.</p>
<p>Copy the code snippet below into the app/page.tsx file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> { formatDateString, slugifySentences } <span class="hljs-keyword">from</span> <span class="hljs-string">"./utils"</span>;

interface Post {
    <span class="hljs-attr">author_id</span>: string;
    title: string;
    content: string;
    author: string;
    slug: string;
    id: number | <span class="hljs-literal">null</span>;
    created_at: <span class="hljs-built_in">Date</span> | <span class="hljs-literal">null</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">// dummy posts</span>
    <span class="hljs-keyword">const</span> posts: Post[] = [
        {
            <span class="hljs-attr">author_id</span>: <span class="hljs-string">"1"</span>,
            <span class="hljs-attr">title</span>: <span class="hljs-string">"Welcome to Neon Tutorial"</span>,
            <span class="hljs-attr">content</span>: <span class="hljs-string">"This is a test post"</span>,
            <span class="hljs-attr">author</span>: <span class="hljs-string">"John Doe"</span>,
            <span class="hljs-attr">slug</span>: slugifySentences(<span class="hljs-string">"Welcome to Neon Tutorial"</span>),
            <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>,
            <span class="hljs-attr">created_at</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
        },
        {
            <span class="hljs-attr">author_id</span>: <span class="hljs-string">"1"</span>,
            <span class="hljs-attr">title</span>: <span class="hljs-string">"Hello World"</span>,
            <span class="hljs-attr">content</span>: <span class="hljs-string">"This is a test post"</span>,
            <span class="hljs-attr">author</span>: <span class="hljs-string">"Jane Doe"</span>,
            <span class="hljs-attr">slug</span>: slugifySentences(<span class="hljs-string">"Hello World"</span>),
            <span class="hljs-attr">id</span>: <span class="hljs-number">2</span>,
            <span class="hljs-attr">created_at</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
        },
    ];

    <span class="hljs-comment">// shorten posts with longer title</span>
    <span class="hljs-keyword">const</span> shortenText = (text: string): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
        <span class="hljs-keyword">return</span> text.length &lt;= <span class="hljs-number">55</span> ? text : text.slice(<span class="hljs-number">0</span>, <span class="hljs-number">55</span>) + <span class="hljs-string">"..."</span>;
    };

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'md:px-8 py-8 px-4 w-full bg-white'</span>&gt;</span>
                {posts?.map((post) =&gt; (
                    <span class="hljs-tag">&lt;<span class="hljs-name">Link</span>
                        <span class="hljs-attr">href</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">posts</span>/${<span class="hljs-attr">post.slug</span>}`}
                        <span class="hljs-attr">className</span>=<span class="hljs-string">'rounded w-full border-[1px] p-4 text-blue-500 hover:bg-blue-50 hover:drop-shadow-md transition-all duration-200 ease-in-out flex items-center justify-between gap-4 mb-4'</span>
                        <span class="hljs-attr">key</span>=<span class="hljs-string">{post.id}</span>
                    &gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-lg font-semibold'</span>&gt;</span>{shortenText(post.title)}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex items-center justify-between'</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-xs text-gray-500'</span>&gt;</span>
                                {formatDateString(post?.created_at)}
                            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
                ))}
            <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
    );
}
</code></pre>
<p>The app/page.tsx file represents the home page of the application and displays all the available posts.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-5.png" alt="blog-app" width="600" height="400" loading="lazy">
<em>It's live - see the power of serverless PostgreSQL and Next.js</em></p>
<p>Next, add the routes for creating posts and reading the contents of each post. Within the Next.js app folder, create a posts directory containing /posts/create and /posts/[slug] subdirectories.</p>
<p>Create a page.tsx file within the /posts/create folder and copy the code snippet below into the file:</p>
<pre><code class="lang-javascript">use client<span class="hljs-string">";
import { useState, useCallback } from "</span>react<span class="hljs-string">";
import { useRouter } from "</span>next/navigation<span class="hljs-string">";
import SimpleMDE from "</span>react-simplemde-editor<span class="hljs-string">";
import "</span>easymde/dist/easymde.min.css<span class="hljs-string">";
import { slugifySentences } from "</span>@/app/utils<span class="hljs-string">";

export default function PostCreate() {
    const [publishing, setPublishing] = useState&lt;boolean&gt;(false);
    const [content, setContent] = useState&lt;string&gt;("</span><span class="hljs-string">");
    const [title, setTitle] = useState&lt;string&gt;("</span><span class="hljs-string">");
    const router = useRouter();

    const onChangeContent = useCallback((value: string) =&gt; {
        setContent(value);
    }, []);

    const handleCreatePost = async (e: React.FormEvent&lt;HTMLFormElement&gt;) =&gt; {
        e.preventDefault();
        console.log({ title, content });
        router.push("</span>/<span class="hljs-string">");
    };

    return (
        &lt;div className='min-h-[100vh]'&gt;
            &lt;main className='md:px-8 py-8 px-4 w-full'&gt;
                &lt;form className='flex flex-col w-full' onSubmit={handleCreatePost}&gt;
                    &lt;label htmlFor='title' className='text-sm text-blue-600'&gt;
                        Title
                    &lt;/label&gt;
                    &lt;input
                        type='text'
                        name='title'
                        id='title'
                        value={title}
                        required
                        onChange={(e) =&gt; setTitle(e.target.value)}
                        className='px-4 py-3 border-2 rounded-md text-lg mb-4'
                    /&gt;

                    &lt;label htmlFor='content' className='text-sm text-blue-600'&gt;
                        Content
                    &lt;/label&gt;
                    &lt;SimpleMDE value={content} onChange={onChangeContent} id='content' /&gt;

                    &lt;button
                        type='submit'
                        disabled={publishing}
                        className='bg-blue-600 mt-2 text-white py-3 rounded-md'
                    &gt;
                        {publishing ? "</span>Publishing....please wait<span class="hljs-string">" : "</span>Publish Post<span class="hljs-string">"}
                    &lt;/button&gt;
                &lt;/form&gt;
            &lt;/main&gt;
        &lt;/div&gt;
    );
}</span>
</code></pre>
<p>The /posts/create page renders a form that accepts the title and content of the post, allowing authors to create new blog posts.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXed0zewJjjPH6pDp2z4E1dZ-F2d717R5LJuqqrqC-8pzv_PAHcNWem1q_19NgnpUoM9hgBa7MhHtFs0fn_kaEMQOXaRHGPPdoIi1qiOovdTbiPSPlut1EX3Yo-2APt3wvwW08K2BrjJ6rx-R3EDEdxfyNwu?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="How to Create a Post in Blog App" width="1600" height="836" loading="lazy">
<em>Create your next blog post with ease</em></p>
<p>Finally, update the /posts/[slug] page to display each post's content and include a button that allows only the posts' authors to delete posts. (You'll learn how to implement this later in the tutorial.)</p>
<pre><code class="lang-javascript">use client<span class="hljs-string">";
import { useRouter, useParams } from "</span>next/navigation<span class="hljs-string">";
import ReactMarkdown from "</span>react-markdown<span class="hljs-string">";
import { useEffect, useState, useCallback } from "</span>react<span class="hljs-string">";
import remarkGfm from "</span>remark-gfm<span class="hljs-string">";
import { formatDateString } from "</span>@/app/utils<span class="hljs-string">";

export default function Post() {
    const router = useRouter();
    const [loading, setLoading] = useState&lt;boolean&gt;(true);
    const [post, setPost] = useState&lt;Post | null&gt;(null);
    const params = useParams&lt;{ slug: string }&gt;();

    const deletePost = async () =&gt; {
        if (confirm("</span>Are you sure you want to <span class="hljs-keyword">delete</span> <span class="hljs-built_in">this</span> post?<span class="hljs-string">")) {
            alert(`Delete ${params.slug}`);
            router.push("</span>/<span class="hljs-string">");
        }
    };

    return (
        &lt;div&gt;
            &lt;main className='w-full md:px-8 px-4'&gt;
                &lt;header className='mb-6 py-4'&gt;
                    &lt;div className='flex items-center justify-between mb-2'&gt;
                        &lt;h2 className='text-3xl text-blue-700 font-bold'&gt;{post?.title}&lt;/h2&gt;

                        &lt;div className='flex items-center'&gt;
                            &lt;button
                                className='px-4 py-2 rounded text-xs bg-red-200 hover:bg-red-40 mr-3'
                                onClick={() =&gt; deletePost()}
                            &gt;
                                Delete
                            &lt;/button&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;

                    &lt;div className='flex'&gt;
                        &lt;p className='text-red-500 mr-8 text-sm'&gt;
                            Author: &lt;span className='text-gray-700'&gt;{post?.author}&lt;/span&gt;
                        &lt;/p&gt;
                        &lt;p className='text-red-500 mr-6 text-sm'&gt;
                            Posted on:{"</span> <span class="hljs-string">"}
                            &lt;span className='text-gray-700'&gt;
                                {formatDateString(post?.created_at!)}
                            &lt;/span&gt;
                        &lt;/p&gt;
                    &lt;/div&gt;
                &lt;/header&gt;

                &lt;div className='text-sm text-justify'&gt;
                    &lt;ReactMarkdown remarkPlugins={[remarkGfm]}&gt;
                        {post?.content!}
                    &lt;/ReactMarkdown&gt;
                &lt;/div&gt;
            &lt;/main&gt;
        &lt;/div&gt;
    );
}</span>
</code></pre>
<p>The /posts/[slug] page accepts the unique slug for each blog post, fetches the post's content, and allows post authors to delete their own posts.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-4.png" alt="Blog Post " width="600" height="400" loading="lazy">
<em>Blog Post</em></p>
<p>Congratulations! You've completed the user interface for the application.</p>
<h2 id="heading-how-to-authenticate-users-with-clerk">How to Authenticate Users with Clerk</h2>
<p><a target="_blank" href="https://github.com/clerkinc">Clerk</a> is a complete user management platform that enables you to add various forms of authentication to your software applications. It provides easy-to-use, flexible UI components and APIs that can be integrated seamlessly into your application.</p>
<p>Install the <a target="_blank" href="https://clerk.com/docs/quickstarts/nextjs">Clerk Next.js SDK</a> by running the following code snippet in your terminal.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npm install @clerk/nextjs</span></p></td></tr></tbody></table>

<p>Create a middleware.ts file within the Next.js src folder and copy the code snippet below into the file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { clerkMiddleware, createRouteMatcher } <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs/server"</span>;


<span class="hljs-comment">// the createRouteMatcher function accepts an array of routes to be protected</span>
<span class="hljs-keyword">const</span> protectedRoutes = createRouteMatcher([<span class="hljs-string">"/posts/create"</span>]);

<span class="hljs-comment">// protects the route</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> clerkMiddleware(<span class="hljs-function">(<span class="hljs-params">auth, req</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (protectedRoutes(req)) {
        auth().protect();
 }
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = {
    <span class="hljs-attr">matcher</span>: [<span class="hljs-string">"/((?!.*\\..*|_next).*)"</span>, <span class="hljs-string">"/"</span>, <span class="hljs-string">"/(api|trpc)(.*)"</span>],
};
</code></pre>
<p>The <code>createRouteMatcher</code> function accepts an array containing routes to be protected from unauthenticated users and the <code>clerkMiddleware()</code> function ensures the routes are protected.</p>
<p>Next, import the following Clerk components into the app/layout.tsx file and update the RootLayout function as shown below:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {
    ClerkProvider,
    SignInButton,
    SignedIn,
    SignedOut,
    UserButton,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@clerk/nextjs"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
    children,
}: {
    children: React.ReactNode;
}</span>) </span>{
    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ClerkProvider</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">'en'</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{inter.className}</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full py-4 border-b-[1px] md:px-8 px-4 text-center flex items-center justify-between sticky top-0 bg-white z-10 '</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'/'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-xl font-extrabold text-blue-700'</span>&gt;</span>
                            Neon Blog
                        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>

                        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex items-center gap-5'</span>&gt;</span>
                            {/*-- if user is signed out --*/}
                            <span class="hljs-tag">&lt;<span class="hljs-name">SignedOut</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">SignInButton</span> <span class="hljs-attr">mode</span>=<span class="hljs-string">'modal'</span> /&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">SignedOut</span>&gt;</span>
                            {/*-- if user is signed in --*/}
                            <span class="hljs-tag">&lt;<span class="hljs-name">SignedIn</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'/posts/create'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">''</span>&gt;</span>
                                    Create Post
                                <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">UserButton</span> <span class="hljs-attr">showName</span> /&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">SignedIn</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>

                    {children}
                <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">ClerkProvider</span>&gt;</span></span>
    );
}
</code></pre>
<p>When a user is not signed in, the <a target="_blank" href="https://clerk.com/docs/components/unstyled/sign-in-button">Sign in button</a> component is rendered. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-3.png" alt="Clerk UI" width="600" height="400" loading="lazy">
<em>Seamless sign-ups redefined with Clerk UI</em></p>
<p>Then, after signing into the application, the Clerk <a target="_blank" href="https://clerk.com/docs/components/user/user-button">User Button component</a> and a link to create a new post are displayed.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-17.png" alt="Clerk's User Button component" width="600" height="400" loading="lazy">
<em>After sign-in: Use Clerk's User Button to create a new post</em></p>
<p>Next, create a <a target="_blank" href="https://clerk.com/">Clerk account</a> and add a new application project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-7.png" alt="Clerk's sleek UI dashboard" width="600" height="400" loading="lazy">
<em>Clerk's sleek UI dashboard</em></p>
<p>Select username as the authentication method and create the Clerk project.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/07/image-6.png" alt="clerk-dashboard" width="600" height="400" loading="lazy">
<em>Clerk's sleek UI dashboard</em></p>
<p>Finally, add your Clerk publishable and secret keys into the .env.local file.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=&lt;your_publishable_key&gt;</span><span><br></span><span>CLERK_SECRET_KEY=&lt;your_secret_key&gt;</span></p></td></tr></tbody></table>

<p>Clerk provides various ways to <a target="_blank" href="https://clerk.com/docs/references/nextjs/read-session-data">read user's data</a> on the client and the server, which is essential for identifying users within the application.</p>
<h2 id="heading-crud-operations-with-the-neon-database">CRUD Operations with the Neon Database</h2>
<p>In this section, you’ll learn how to perform CRUD (Create, Read, Update, Delete) operations with the Neon database. These fundamental operations are essential for interacting with and managing data within any application. </p>
<p>The db/actions.ts file will contain the CRUD operations. Add the following code snippet to the file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { db } <span class="hljs-keyword">from</span> <span class="hljs-string">"."</span>;
<span class="hljs-keyword">import</span> { postsTable } <span class="hljs-keyword">from</span> <span class="hljs-string">'./schema'</span>;
<span class="hljs-keyword">import</span> { desc, eq } <span class="hljs-keyword">from</span> <span class="hljs-string">"drizzle-orm"</span>;

<span class="hljs-comment">// add a new row to the posts table</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> createPost = <span class="hljs-keyword">async</span> (post: Post) =&gt; {
    <span class="hljs-keyword">await</span> db.insert(postsTable).values({
        <span class="hljs-attr">content</span>: post.content,
        <span class="hljs-attr">author</span>: post.author,
        <span class="hljs-attr">author_id</span>: post.author_id,
        <span class="hljs-attr">title</span>: post.title,
        <span class="hljs-attr">slug</span>: post.slug,
    });
};

<span class="hljs-comment">// get all the posts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAllPosts = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> db.select().from(postsTable).orderBy(desc(postsTable.created_at));
};

<span class="hljs-comment">// get a post using its slug</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getSinglePost = <span class="hljs-keyword">async</span> (slug: string) =&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> db.query.postsTable.findFirst({
        <span class="hljs-attr">where</span>: <span class="hljs-function">(<span class="hljs-params">post, { eq }</span>) =&gt;</span> eq(post.slug, slug)
    });
};

<span class="hljs-comment">// delete a post</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> deletePost = <span class="hljs-keyword">async</span> (id: number) =&gt; {
    <span class="hljs-keyword">await</span> db.delete(postsTable).where(eq(postsTable.id, id));
};

<span class="hljs-comment">// update a post's content</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> updatePost = <span class="hljs-keyword">async</span> (content: string, <span class="hljs-attr">id</span>: number) =&gt; {
    <span class="hljs-keyword">await</span> db.update(postsTable)
        .set({ <span class="hljs-attr">content</span>: content })
        .where(eq(postsTable.id, id));
};
</code></pre>
<p>From the code snippet above:</p>
<ul>
<li>This <code>createPost</code> function takes a post object as an argument and inserts a new row into the <code>postsTable</code> with the specified post content, author, author ID, title, and slug.</li>
<li>The <code>getAllPosts</code> function retrieves all the posts from the <code>postsTable</code> and sorts them in descending order by their creation date (created_at).</li>
<li>This <code>getSinglePost</code> function takes a slug as an argument and retrieves the first post that matches the given slug from the <code>postsTable</code>. The slug is unique, so it will return a single object.</li>
<li>This <code>deletePost</code> function takes an id as an argument and deletes the post with the matching ID from the <code>postsTable</code>.</li>
<li>This <code>updatePost</code> function accepts <code>content</code> and a post's <code>id</code> as arguments and updates the post's content with the matching ID in the <code>postsTable</code>.</li>
</ul>
<p>Finally, you can execute the CRUD functions on the server via API endpoints or <a target="_blank" href="https://nextjs.org/docs/app/building-your-application/data-fetching/fetching-caching-and-revalidating#fetching-data-on-the-server-with-fetch">Next.js server fetch requests</a>. </p>
<p>For instance, you can fetch all the existing blog posts within the Neon database and display them within the application using the Next.js server data fetching method:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { getAllPosts } <span class="hljs-keyword">from</span> <span class="hljs-string">"./db/actions"</span>;

<span class="hljs-keyword">const</span> getPosts = <span class="hljs-keyword">async</span> () =&gt; <span class="hljs-keyword">await</span> getAllPosts()

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> posts = <span class="hljs-keyword">await</span> getPosts()

    <span class="hljs-keyword">return</span> (<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>{/** -- UI elements --*/}<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>)
}
</code></pre>
<p>You can also create a Next.js API endpoint that returns all the available blog posts. Create a /api/posts/all endpoint that returns the posts:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { getPosts } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/db/actions"</span>;
<span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params"></span>) </span>{


    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> getPosts()
        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Post fetched"</span>, data }, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
 } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"Post not available"</span>, err },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
 );
 }
}
</code></pre>
<p>Congratulations! You’ve completed the project for this tutorial.</p>
<p>You can find the code for the app we built <a target="_blank" href="https://github.com/tyaga001/serverless-postgres-nextjs-handbook">here</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you’ve learned what a Neon database is, how to create one, and how to perform CRUD operations with Neon and Drizzle ORM in a Next.js application.</p>
<p>Neon's serverless architecture, combined with its scalability and performance optimizations, makes it an excellent choice for modern web applications. Neon also provides a smooth developer experience and a community of passionate individuals ready to help you achieve your application goals. Thank you for reading.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>By now, you should have a good understanding of how to build full-stack applications with Neon and Next.js.</p>
<p>If you'd like to learn more about how you can leverage Neon to build advanced and scalable applications, you can check out the following resources:</p>
<ul>
<li><a target="_blank" href="https://neon.tech/docs/introduction">Neon documentation</a></li>
<li><a target="_blank" href="https://github.com/neondatabase/examples">Neon example projects</a></li>
<li><a target="_blank" href="https://neon.tech/docs/guides/vercel">How to integrate Neon with Vercel</a></li>
<li><a target="_blank" href="https://neon.tech/docs/import/import-from-postgres">How to import your data from a Postgres database to Neon</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/j4Vak4J10KU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-thanks-for-reading"><strong>Thanks for Reading!</strong></h2>
<p>That's it for this tutorial. I hope you learned something new today.</p>
<p>If you did, please share so that it reaches others as well.</p>
<p>You can connect with me on <a target="_blank" href="https://twitter.com/TheAnkurTyagi">Twitter</a> or subscribe to my <a target="_blank" href="https://bytesizedbets.com/">newsletter</a>. </p>
<h3 id="heading-want-to-read-more-interesting-blog-posts"><strong>Want to read more interesting blog posts?</strong></h3>
<p>You can read more tutorials like this one on my <strong><a target="_blank" href="https://theankurtyagi.com/">blog</a>.</strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Optimize Your Database – Optimization Principles and Best Practices ]]>
                </title>
                <description>
                    <![CDATA[ Databases are an integral component of building applications, whether web, desktop or mobile. They symbolically serve as the mitochondria of the application, as their primary function is to manage data. Database management is a critical skill a devel... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/database-optimization-principles/</link>
                <guid isPermaLink="false">66bb58c3965d5c9ed5487ba2</guid>
                
                    <category>
                        <![CDATA[ best practices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Oluwatobi ]]>
                </dc:creator>
                <pubDate>Fri, 10 May 2024 15:21:56 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/Acid.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Databases are an integral component of building applications, whether web, desktop or mobile. They symbolically serve as the mitochondria of the application, as their primary function is to manage data.</p>
<p>Database management is a critical skill a developer must possess in building scalable applications that have a high level of efficiency. If not handled properly, it can result in data loss and mismanagement on the part of the database developer.</p>
<p>Hence, databases must be structured and built with the users in mind and built utilizing the best practices available.</p>
<p>This article aims to highlight general principles of database best practices and also explain each peculiarity. But before we discuss that in detail, let’s review what database transactions are all about.</p>
<h2 id="heading-what-are-database-transactions">What are Database Transactions?</h2>
<p>Database transactions are simply groups of operations which can be termed as a unit of a work process performed on a database within a database management system. </p>
<p>It encompasses basic operations such as CRUD operations to more advanced operations such as database indexing, caching, and normalization.</p>
<p>With so many users performing many transactions at the same time, it’s important to ensure that the database is concurrency-enabled to prevent data interference between two or more users accessing the same resource. </p>
<p>Hence, there is the need for the ACID principle.  What then does ACID represent?</p>
<ul>
<li>Atomicity</li>
<li>Consistency</li>
<li>Isolation</li>
<li>Durability</li>
</ul>
<p>Subsequently, we will be discussing each point in detail. First on our list is atomicity.</p>
<h2 id="heading-what-is-the-database-atomicity-principle">What is the Database Atomicity Principle?</h2>
<p>What does database atomicity entail? The atomicity of a database simply means that a database operation can’t be broken down further as a unit. This means that the database operation or transactions gets executed completely, and in case any error comes up during the execution process, the entire operation gets completely cancelled, preventing room for partial operation execution.</p>
<p>If the database isn’t atomic, this can result in the provision of misleading incomplete data and ultimately result in entire system chaos. How does the database ensure atomicity? It does this by creating a copy of the existing database before the operation gets executed and then initiates a crash recovery and backup restoration operation in the event of an operation failure.</p>
<p>It is also important to note that other database principles such as consistency and durability rely on the need for the database to be atomic to be truly fulfilled.</p>
<p>Having discussed this, let’s move on to the database consistency principle.</p>
<h2 id="heading-what-is-the-database-consistency-principle">What is the Database Consistency Principle?</h2>
<p>This principle entails that the database has certain constraints, cascades, triggers and other requirements in place, which needs to be fulfilled while making changes to an established database. Failure to fulfill this requirement will lead to consistency errors, returning the database to its previous stable state.</p>
<p>Also, consistency as a principle ensures that the data updated by a user is made available as the latest version of the data in the database to all users who desire to read the database. Having this in place eliminates the occurrence of inconsistencies and aids faster information retrieval.</p>
<p>Understanding what it means for a database to be consistent involves ensuring the operation performed on the database passes the integrity check before being successfully executed. Having exhausted this in detail, let's discuss the database isolation principle.</p>
<h2 id="heading-what-is-the-database-isolation-principle">What is the Database Isolation Principle?</h2>
<p>Why should we isolate a database and how does one make a database operation independent from other database operations?</p>
<p>Isolation is necessary in a database management system to ensure that the user's access to information on the database is not interfered with by other concurrent transactions undertaken by other users on the database. To enforce this, the use of isolation levels in each database operation helps to preserve information integrity.</p>
<p>To effectively guarantee the database integrity, specific database isolation levels must be used. Here are some of the isolation levels ranked in order of hierarchy:</p>
<ul>
<li>Read uncommitted</li>
<li>Read committed</li>
<li>Repeatable read</li>
<li>Serializability</li>
</ul>
<h3 id="heading-read-uncommitted-isolation-level">Read Uncommitted Isolation Level</h3>
<p>The read uncommitted database isolation level allows other users to have access to read current database transactions which has not yet been completely or successfully executed. It allows access to read what is being referred to as dirty read, which is one of the data inconsistencies that can be seen. This level of data isolation isn’t advised.</p>
<h3 id="heading-read-committed-isolation-level">Read Committed Isolation Level</h3>
<p>This database isolation level disallows other users to read or have access to a database transaction that has not yet been committed. Hence it prevents other users from seeing, updating or overwriting it until it has been completely executed.</p>
<h3 id="heading-repeatable-read-isolation-level">Repeatable Read Isolation Level</h3>
<p>This isolation level exclusively isolates a transaction from other transactions occurring concurrently, preventing other users access to read and update the transactions.</p>
<h3 id="heading-serializability-isolation-level">Serializability Isolation Level</h3>
<p>This is the highest level of data isolation and is referred to as the strictest level. It isolates the multiple transactions performed concurrently and executes them efficiently as they are executed serially. It also prevents database inconsistencies.</p>
<p>Without these levels in place, inconsistent database mishaps such as dirty reads, non-repeatable reads, phantom reads and many others may be experienced. With this, let's move on to the last point about database durability and discuss it in detail.</p>
<h2 id="heading-what-is-the-database-durability-principle">What is the Database Durability Principle?</h2>
<p>What does it imply when we describe a database as durable and how do we ensure the durability of a database? Durability as it sounds is a principle which ensures that databases have a high level of immortality.</p>
<p>Irrespective of any adverse outcomes that the database management system might face such as outages and crashes, there shouldn't be any loss of database information.</p>
<p>How do databases try to achieve this? The database creates a transactional log that contains the recorded data before any new operation gets executed. In the event of any of these adverse events, the transaction log serves as the backup store, ensuring that the database info is well preserved up to the point before the operation occurred, thereby mitigating against data breaches and loss.</p>
<p>We'll also highlight other helpful database operations best practices that can also be implemented.</p>
<h2 id="heading-other-database-operations-best-practices">Other Database Operations Best Practices</h2>
<p>The BASE principle, which is more suited for NoSQL databases such as MongoDB, Redis, and Cassandra, and so on. It entails a database to be:</p>
<ul>
<li>Basically available</li>
<li>Existing in a soft state</li>
<li>And be eventually consistent.</li>
</ul>
<h3 id="heading-basically-available">Basically Available</h3>
<p>This entails that the database prioritizes the availability of the database operations over consistency and concurrency. This is quite applicable to distributed systems which rely on a high level of efficiency to function effectively.</p>
<h3 id="heading-soft-state">Soft State</h3>
<p>This ensures easy flexibility of the database, allowing for size scaling, operations and increased concurrency for optimal database performance at all times. This allows the data to maintain resiliency.</p>
<h3 id="heading-eventually-consistent">Eventually Consistent</h3>
<p>This entails that, irrespective of how the transactions get executed in the sequences, it eventually achieves efficient consistency. This is achieved by conflict resolution and reconciliation. This eventually contributes to the building of a resilient data system.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>With this, we have come to the end of the tutorial. We hope you’ve learned essentially about optimizing database operations and their efficiency using the ACID principle and other best practices available.</p>
<p>Feel free to drop comments and questions in the box below, and also check out my other articles <a target="_blank" href="https://www.freecodecamp.org/news/p/2a9a2ef7-b659-4655-97ce-fea0f3a9f668/linktr.ee/tobilyn77">here</a>. Till next time, keep on coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Run a Postgres Database in Azure Kubernetes Service and Integrate it with a Node.js Express Application ]]>
                </title>
                <description>
                    <![CDATA[ Hey everyone! Today, you're going to learn about deploying a Postgres container in Azure Kubernetes Service (AKS) and connecting it to a Node.js application. In this fast-paced development landscape, deploying via containers, particularly with Kubern... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-run-postgres-in-kubernetes/</link>
                <guid isPermaLink="false">66d45dd851f567b42d9f8435</guid>
                
                    <category>
                        <![CDATA[ containerization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express JS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Kubernetes ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ayomide Wilfred Adeyemi ]]>
                </dc:creator>
                <pubDate>Wed, 08 May 2024 20:43:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/05/Azure-K8s-article-image.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hey everyone! Today, you're going to learn about deploying a Postgres container in Azure Kubernetes Service (AKS) and connecting it to a Node.js application.</p>
<p>In this fast-paced development landscape, deploying via containers, particularly with Kubernetes, is becoming increasingly popular. Some companies perform numerous deployments daily, so it's crucial for you to learn these technologies.</p>
<p>Kubernetes is a popular choice to deploy containerized applications like web servers, databases, and APIs. You can set up Kubernetes either locally or in the cloud. In this tutorial, we'll explore setting up Kubernetes on a cloud platform, specifically Azure.</p>
<p>I'll walk you through the process of setting up Kubernetes using Azure Kubernetes Service (AKS). You'll configure your YAML file using StatefulSet, Persistent Volume, and Services to deploy a PostgreSQL database on Azure Kubernetes. Then, you'll obtain the PostgreSQL database credentials running inside the AKS and use them to establish a connection with a Node.js application.</p>
<p>We'll cover key concepts such as deployment, stateful sets, persistent volumes, and services, preparing you to deploy a Postgres container effectively on AKS. I'll also help you connect your Node.js Express app to the Postgres container within the AKS cluster.</p>
<p>So find a comfortable seat and get ready, as we're about to dive in.</p>
<h3 id="heading-prerequisites"><strong>Prerequisites</strong></h3>
<p>Before you begin, it's important to understand some basic concepts in <a target="_blank" href="https://kubernetes.io">Kubernetes</a> like <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/pods/">pods</a>, <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">deployments</a>, <a target="_blank" href="https://kubernetes.io/docs/concepts/services-networking/service/">services</a>, and <a target="_blank" href="https://kubernetes.io/docs/concepts/architecture/nodes/">nodes</a>.</p>
<p>If you're new to this, I recommend checking out the Stashchuk freeCodeCamp <a target="_blank" href="https://www.youtube.com/watch?v=d6WC5n9G_sM">video</a> for a beginner-friendly tutorial.</p>
<p>You'll also need an active <a target="_blank" href="https://azure.microsoft.com/en-us/get-started/azure-portal">Azure</a> account and subscription to follow along.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#challenges-were-trying-to-solve">Challenges We're Trying to Solve</a><br>  – <a class="post-section-overview" href="#heading-deployments">Deployments</a><br>  – <a class="post-section-overview" href="#heading-statefulsets">StatefulSets</a><br>  – <a class="post-section-overview" href="#heading-persistent-volumes">Persistent Volumes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-azure-kubernetes-service-aks">Azure Kubernetes Service (AKS)</a><br>  – <a class="post-section-overview" href="#heading-step-1-sign-in-to-your-azure-portal">Sign in to Your Azure Portal</a><br>  – <a class="post-section-overview" href="#heading-step-2-create-a-resource">Create a Resource</a><br>  – <a class="post-section-overview" href="#heading-step-3-create-a-new-container">Create a new container</a><br>  – <a class="post-section-overview" href="#heading-step-4-create-a-new-azure-kubernetes-service-aks">Create a new Azure Kubernetes Service(AKS)</a><br>  – <a class="post-section-overview" href="#heading-step-5-create-a-new-resource-group">Create a new resource group</a><br>  – <a class="post-section-overview" href="#heading-step-6-give-your-kubernetes-cluster-a-name">Give your Kubernetes cluster a name</a><br>  – <a class="post-section-overview" href="#heading-step-7-navigate-to-the-node-pool-page">Navigate to the node pool page</a><br>  – <a class="post-section-overview" href="#heading-step-8-enable-container-logs-and-set-up-alerts">Enable container logs and set up alerts</a><br>  – <a class="post-section-overview" href="#heading-step-9-advanced-section">Advanced Section</a><br>  – <a class="post-section-overview" href="#heading-step-10-tags">Tags</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-connect-to-your-aks-cluster-using-the-command-line">Connect to Your AKS Cluster</a><br>  – <a class="post-section-overview" href="#heading-download-azure-cli-and-kubectl">Download Azure CLI and kubectl</a><br>  – <a class="post-section-overview" href="#heading-verify-if-the-azure-cli-is-installed-by-typing-the-command-az-version">Verify if Azure CLI is installed</a><br>  – <a class="post-section-overview" href="#heading-verify-if-kubectl-is-installed">Verify if kubectl is installed</a><br>  – <a class="post-section-overview" href="#heading-login-to-your-azure-account">Login to Azure account</a><br>  – <a class="post-section-overview" href="#heading-configure-kubectl-to-connect-to-your-azure-kubernetes">Configure kubectl</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-create-resources-with-yaml">How to Create Resources with YAML</a><br>  – <a class="post-section-overview" href="#heading-clone-the-repository">Clone the Repository</a><br>  – <a class="post-section-overview" href="#heading-open-the-cloned-repository-in-any-text-editor">Open the Repository</a><br>  – <a class="post-section-overview" href="#heading-install-project-dependencies">Install Dependencies</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-yaml-configuration">YAML Configuration</a><br>  – <a class="post-section-overview" href="#heading-storageclass">StorageClass</a><br>  – <a class="post-section-overview" href="#heading-persistentvolumeclaim">PersistentVolumeClaim</a><br>  – <a class="post-section-overview" href="#heading-configmap">ConfigMap</a><br>  – <a class="post-section-overview" href="#heading-statefulset">StatefulSet</a><br>  – <a class="post-section-overview" href="#heading-service">Service</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-deploy-yaml-resource-to-azure-kubernetes-service-aks">How to Deploy YAML Resource to Azure</a><br>  – <a target="_blank" href="https://www.freecodecamp.org/news/p/a37cba54-1e70-4fb6-99d4-d9ee63e66e1b/deploy-the-yaml-resource">Deploy the YAML resource</a></p>
</li>
<li><p><a class="post-section-overview" href="#nodejs-application">Node.js Application</a><br>  – <a class="post-section-overview" href="#heading-configure-your-nodejs-application">Configure Nodejs</a><br>  – <a class="post-section-overview" href="#heading-run-your-nodejs-application">Run Nodejs Application</a><br>  – <a class="post-section-overview" href="#heading-test-the-application">Test the Application</a><br>  – <a class="post-section-overview" href="#heading-open-your-postman-application">Open Postman</a><br>  – <a class="post-section-overview" href="#heading-confirm-the-data">Confirm the Data</a><br>  – <a class="post-section-overview" href="#heading-delete-the-pod-to-confirm-data-persistence">Delete Pod</a><br>  – <a class="post-section-overview" href="#heading-data-persistence">Data Persistence</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-challenges-were-trying-to-solve">Challenges We're Trying to Solve</h2>
<p>Firstly, what is Kubernetes? Well, it's like a manager for your software containers. It helps you run and manage lots of containers like web servers, databases, microservices, and APIs which are like little packages holding your applications.</p>
<p>Kubernetes takes care of things like starting, stopping, and scaling these containers, so your apps run smoothly even when there is more load on your application. It's popular because it makes running software in the cloud easier and more reliable.</p>
<p>Now, let's talk about how to tackle some challenges you might face with a real-world application running Postgres in a Kubernetes production cluster.</p>
<p>Imagine that the infrastructure hosting your Postgres crashes, causing you to lose all the services and data stored in the database. Or, picture a scenario where the Postgres database becomes corrupted, leading to data loss.</p>
<p>In both cases, you need a way to back up your application so you can restore it to a working state if disaster strikes.</p>
<p>So, how do you capture a comprehensive application backup that includes all the necessary data? This backup should allow you to restore the entire application, including the database, if you lose your cluster or encounter data loss.</p>
<p>In Kubernetes, think of a Pod as the tiniest unit that you can deploy. It's like a small box that holds one thing, like a web server or a database. So, if your Pod isn't running, your web server or database isn't either.</p>
<p>This means that if the cluster where your Pod runs gets destroyed, all the data in the Pod disappears too. All the nodes (virtual machines that run your application over the network) will also be wiped out.</p>
<p>How can you make a pod stay on one specific node where the data is and never move? And how can you make sure that each pod can be found separately when you're using a load balancer?</p>
<p>One solution is to consider how you deploy your application on Kubernetes. Typically, you create a <strong>deployment</strong> and expose it using a service, specifying the service type as either Cluster type, NodePort, or LoadBalancer.</p>
<p>But not all applications are the same when it comes to state. Some applications, known as stateless applications, don't rely on storing data locally, so losing their state isn't a big issue.</p>
<p>But for applications like databases or caches, maintaining state is crucial because they rely on storage. In Kubernetes, deploying stateful applications like databases using just deployment isn't ideal. You need a solution that ensures your application's data is safely stored and can be recovered in case of failure.</p>
<h3 id="heading-deploymentshttpskubernetesiodocsconceptsworkloadscontrollersdeployment"><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/">Deployments</a></h3>
<p>You might be wondering why we can't just use a Kubernetes deployment to deploy Postgres in the Kubernetes cluster? Well, the thing is, many people aren't aware of the difference between a deployment and a stateful set.</p>
<p>Let's imagine you have a pod running in your cluster that you created using a deployment. Then you scaled up to two pods, so you now have Pod A and Pod B.</p>
<p>The problem arises because, by default, pods created as part of the same deployment share the same <strong>persistent volume</strong> (PV) across the cluster. So, when you scaled up, both instances of Postgres would write to the same storage, which could lead to data corruption.</p>
<p>Another issue arises from a networking perspective. Pods A and B don't have a dependable way to communicate with each other over the network. By default, Kubernetes pods don't have their own DNS names. Instead, you rely on <strong>services</strong> to expose ports to other applications in the cluster.</p>
<p>If you take a closer look at pod names, you'll notice that pods are assigned a random hash at the end of their names. Because of this, pods lack a consistent network identity. Every time a pod is destroyed and recreated, it receives a new randomized name. This inconsistency isn't ideal for reliable networking.</p>
<p>Postgres isn't naturally made for <strong>Kubernetes</strong>, and Kubernetes can be tough when handling stateful tasks. To set up a Postgres instance, you've got to know the right Kubernetes setup. You can't just throw it in a pod, because if the pod goes down, so does your data. But, for a quick integration, a pod could work fine.</p>
<p>Deployments aren't ideal either, since you don't want your pod randomly placed on a node. But for testing, deployments are handy if you just need a Postgres instance to run temporarily.</p>
<p>What you really want is a pod that sticks to a particular node where your data resides, and stays put. Plus, you also want your pod to be individually addressable. for this we need what we called a <a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/"><strong>statefulSet</strong></a>.</p>
<h3 id="heading-statefulsetshttpskubernetesiodocsconceptsworkloadscontrollersstatefulset"><a target="_blank" href="https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/"><strong>StatefulSets</strong></a></h3>
<p>When you update your deployment to become a <strong>StatefulSet</strong>, Kubernetes introduces some improvements for deploying stateful workloads. One major change is how it handles scaling.</p>
<p>If you specify that you want three replicas of your StatefulSet, Kubernetes won't create all three pods at once. Instead, it creates them one by one. Each pod gets its own unique DNS name, starting with the pod's name followed by an ordinal number starting from zero. So, when you scale up, the ordinal number increases for each new pod.</p>
<p>Here's the cool part: if a pod like Pod-0 is destroyed and needs to be remade, it will return with the same name. This means each pod has a specific address, even if it's replaced.</p>
<p>And here's another cool feature: each pod in a StatefulSet gets its own persistent volume (PV). This lets you keep the same storage even if you scale up or down. This brings us to another concept called persistent volumes.</p>
<h3 id="heading-persistent-volumeshttpskubernetesiodocsconceptsstoragepersistent-volumes"><a target="_blank" href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/">Persistent Volumes</a></h3>
<p>Let's forget about pods, deployment, and containers for a moment. What exactly is "state"? In simple terms, state is the data that your applications need to work properly.</p>
<p>Now, when we talk about processes, there are two types: stateless and stateful. <strong>Stateless</strong> processes don't rely on any data to work. They just do their thing without needing any specific information. On the other hand, <strong>stateful</strong> processes need data or state to function properly.</p>
<p>Now, where do you store this state? There are two main places: memory and disk. <strong>Memory</strong> allows for quick access to data, which is great for applications like Redis, MongoDB, Postgres, or MySQL. They store their state on memory for quick access. But for persistent, they store it on <strong>disk</strong> on the file system (for more permanent storage).</p>
<p>Why the file system? Because it's the only way to keep the state persistent even when the system reboots. So, when a process dies and gets recreated, it can read its state from the file system.</p>
<p>I like breaking things down because I used to teach tech stuff. Now, let's get into setting up Kubernetes in Azure.</p>
<h2 id="heading-azure-kubernetes-service-aks"><strong>Azure Kubernetes Service (AKS)</strong></h2>
<p>In this section, I'll guide you through setting up a Kubernetes cluster on Azure.</p>
<h3 id="heading-step-1-sign-in-to-your-azure-portal">Step 1: Sign in to your Azure portal</h3>
<p>To begin, you will have to sign into your <a target="_blank" href="https://azure.microsoft.com/en-us/get-started/azure-portal">Azure</a> portal. Once logged in, you should see a dashboard similar to this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.39.46.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Azure portal homepage</em></p>
<h3 id="heading-step-2-create-a-resource">Step 2: Create a resource</h3>
<p>Click on "create a resource" to create a resource.</p>
<p>Resources are the various services, components, and assets that you can create and manage within the Azure cloud platform. These resources can include virtual machines, databases, storage accounts, networking components, web applications, and more.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.39.46--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating a resource in Azure portal</em></p>
<h3 id="heading-step-3-create-a-new-container">Step 3: Create a new container</h3>
<p>Next, navigate to the "Containers" category from the options available on the left pane. Click on Containers as shown by the arrow in the screenshot.</p>
<p>Again, Kubernetes is a container orchestration platform. It manages and orchestrates the deployment, scaling, and operation of application containers across clusters of machines. Kubernetes provides a framework for automating the deployment, scaling, and management of containerized applications.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.42.12--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating new container (Kubernetes) in Azure</em></p>
<h3 id="heading-step-4-create-a-new-azure-kubernetes-service-aks"><strong>Step 4: Create a new Azure Kubernetes Service (AKS)</strong></h3>
<p>Select "Azure Kubernetes Service (AKS)" from the list of available container services and click Create. This will take you to the AKS creation page.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.42.37--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating a new Azure Kubernetes service</em></p>
<h3 id="heading-step-5-create-a-new-resource-group">Step 5: Create a new resource group</h3>
<p>In the "Resource group" section, click on "Create new" to create a new resource group for your Azure Kubernetes Service (AKS) deployment.</p>
<p>In Azure, a "resource group" is a logical container used to group together related Azure resources. It serves as a way to organize and manage these resources collectively, rather than individually.</p>
<p>When you create resources such as virtual machines, databases, storage accounts, or any other Azure service, you typically associate them with a resource group.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.43.09--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating a new resource group in azure portal</em></p>
<p>Let's name the resource group "AZURE-POSTGRES-RG" as shown below. You can name it anything you like. Then click ok.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.43.45.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Inputting name for the resource group</em></p>
<h3 id="heading-step-6-give-your-kubernetes-cluster-a-name">Step 6: Give your Kubernetes cluster a name</h3>
<p>Now let's name the session for configuring the Kubernetes cluster "Kubernetes Cluster Name".</p>
<p>In Azure, a Kubernetes cluster is a managed container orchestration service provided by Azure Kubernetes Service (AKS). It allows you to deploy, manage, and scale containerized applications using Kubernetes without having to manage the underlying infrastructure.</p>
<p>Give it a name like "AZURE-POSTGRES-KC" and and select a region that's close to you. In my case I select (Asia Pacific) East Asia and click next.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.47.34--3-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Naming the Kubernetes cluster name</em></p>
<h3 id="heading-step-7-navigate-to-the-node-pool-page">Step 7: Navigate to the node pool page</h3>
<p>Now it's time to configure the node pool session by clicking on the agentpool.</p>
<p>In Azure, a node pool is a group of virtual machines (VMs) that are provisioned and managed together within an Azure Kubernetes Service (AKS) cluster. Each node pool runs a specific version of Kubernetes and has its own set of configurations, such as VM size, OS image, and node count.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.47.50--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Editing agentpool</em></p>
<p>Set the minimum node count to 1, maximum node count to 2, and the maximum pods per node to 30 to minimise cost. Then click update.</p>
<p>These parameters help control the size and behavior of the node pool in an Azure Kubernetes Service (AKS) cluster:</p>
<ol>
<li><p><strong>Minimum Node Count</strong>: Ensures a minimum number of nodes are always available for consistent performance and availability, even during low-demand periods.</p>
</li>
<li><p><strong>Maximum Node Count</strong>: Sets an upper limit on the number of nodes in the node pool to manage costs and prevent over-provisioning.</p>
</li>
<li><p><strong>Maximum Pods per Node</strong>: Defines the maximum number of pods that can run on each node, optimizing resource utilization and preventing overcrowding.</p>
</li>
</ol>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.48.29--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Updating agentpool details</em></p>
<p>Once you've clicked "Update," you'll be directed to the "Networking" section as shown below. Keep the page as is and proceed by clicking "Next." This will take you to Integration session.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.48.55--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Navigating to Next Page</em></p>
<p>Azure Container Registry (ACR) is a fully managed private Docker registry service provided by Microsoft Azure. It enables developers to store, manage, and deploy Docker container images securely within their Azure environment.</p>
<p>You will need a place to store the Docker image that's pulled.</p>
<p>To begin, select "Create New" to set up a new container registry. This action will bring up a page where you can input the necessary details, as illustrated on the right side of the image below. Enter the details as indicated by the arrows and then click "Okay." Once you're done, proceed by clicking "Next."</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.49.36--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Naming and editing Azure Container Registry details</em></p>
<h3 id="heading-step-8-enable-container-logs-and-set-up-alerts">Step 8: Enable container logs and set up alerts</h3>
<p>The <strong>Enable Container Logs</strong> option allows you to turn on logging for your containers. Logging records important information about what's happening inside your containers, like errors, warnings, and other events. It's useful for troubleshooting and monitoring your applications.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.50.25--2-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Choosing container logs</em></p>
<h3 id="heading-step-9-advanced-section">Step 9: Advanced section</h3>
<p>Keep the Monitoring section unchanged and proceed by clicking "Next."</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.50.32.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Navigating to Next Page</em></p>
<h3 id="heading-step-10-tags">Step 10: Tags</h3>
<p>Keep the Tags section unchanged and proceed by clicking "Next."</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.50.44.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Navigating to Next Page</em></p>
<h3 id="heading-step-11-click-review-create-to-finalize-the-deployment">Step 11: Click "Review + create" to finalize the deployment</h3>
<p>Once completed, your resource group, Azure Kubernetes Service (AKS), Azure Container Registry, and Kubernetes cluster will be created.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-17.51.39--1-.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Completing Azure Kubernetes Setup</em></p>
<p>The screenshot below shows that the deployment was successful.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-18.01.53.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Successful Deployment</em></p>
<p>You've just successfully created an Azure Kubernetes Service from the Azure portal. Congrats!</p>
<h2 id="heading-how-to-connect-to-your-aks-cluster-using-the-command-line">How to Connect to Your AKS Cluster Using the Command Line</h2>
<p>After successfully creating a new AKS in the Azure portal, the next step is to establish a connection to that cluster.</p>
<p>In this section, I'll guide you through Azure login, configuring kubectl to use the current context, and creating the YAML file for our Postgres container. This file will include StatefulSet, persistent volume, persistent volume claim, config map, and using Azure File for data storage.</p>
<p>I'll also show you how to run a Node.js Express application locally, use Postman to test the endpoints, and receive a response confirming that data was sent to the database successfully.</p>
<h3 id="heading-download-azure-cli-and-kubectl">Download Azure CLI and kubectl</h3>
<p>To start, you'll need to download the Azure CLI and kubectl.</p>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli"><strong>Azure CLI</strong></a> <strong>(Command-Line Interface)</strong>: a command-line tool provided by Microsoft for managing Azure resources. It allows users to interact with Azure services and resources directly from the command line, making it easy to automate tasks, create scripts, and manage Azure resources programmatically.</p>
</li>
<li><p><a target="_blank" href="https://kubernetes.io/docs/tasks/tools/"><strong>kubectl</strong></a>: a command-line tool for managing Kubernetes clusters, used to deploy, scale, and manage containerized applications. It allows users to perform operations like deploying applications, managing pods, services, and deployments, inspecting cluster resources, scaling applications, and debugging issues, simplifying management of containerized workloads in a Kubernetes environment.</p>
</li>
</ul>
<p>I'm using the warp terminal. <a target="_blank" href="https://www.warp.dev/">Warp</a> is the terminal reimagined with AI and collaborative tools for better productivity. You can run the command using PowerShell on Windows or Terminal on Mac. I'm using a MacBook.</p>
<h3 id="heading-verify-if-the-azure-cli-is-installed-by-typing-the-command-az-version">Verify if the Azure CLI is installed by typing the command <code>az --version</code></h3>
<p>Once the download finishes, verify whether Azure CLI is installed on your computer by running the command <code>az --version</code>. If the installation is successful, you should see an output similar to this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-21.18.48.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Verifying Azure CLI Installation</em></p>
<h3 id="heading-verify-if-kubectl-is-installed">Verify if kubectl is installed</h3>
<p>To check if kubectl is installed, just type <code>kubectl version</code> in the command line.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-21.31.01.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Verifying Kubectl Installation</em></p>
<h3 id="heading-login-to-your-azure-account">Login to your Azure account</h3>
<p>Enter <code>az login</code> in the command line. This will open your browser and prompt you to sign in to your Azure account.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-21.47.47.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Logging into Azure</em></p>
<p>After signing in, it shows details about your Azure subscription, including the subscription name, ID, and user information.</p>
<h3 id="heading-select-an-azure-subscription">Select an Azure subscription</h3>
<p>Azure subscriptions are logical containers used to provision resources in Azure. You'll need to locate the subscription ID that you plan to use in this module. Use the command to list your Azure subscriptions:</p>
<pre><code class="lang-bash">az account list --output table
</code></pre>
<p>Use the following command to ensure you're using an Azure subscription that allows you to create resources for the purpose of this module, substituting your subscription ID (SubscriptionId):</p>
<pre><code class="lang-bash">az account <span class="hljs-built_in">set</span> --subscription <span class="hljs-string">"Name of the subscription"</span>
</code></pre>
<h3 id="heading-configure-kubectl-to-connect-to-your-azure-kubernetes">Configure kubectl to connect to your Azure Kubernetes</h3>
<p>Replace <code>Your_Azure_Resource_groups_name</code> in the code below with the name you chose when creating a resource group. Also, replace <code>your_azure_kubernetes_service_name</code> with the name of your Kubernetes cluster. Then, execute the following command:</p>
<pre><code class="lang-bash">az aks get-credentials --resource-group [Your_Azure_Resource_groups_name] --name [your_azure_kubernetes_service_name]
</code></pre>
<p>The output should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.07.20.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Merging kubectl with Azure Kubernetes Service</em></p>
<h3 id="heading-verify-if-kubectl-has-been-merged-successfully">Verify if kubectl has been merged successfully</h3>
<p>Run the following command <code>kubectl get nodes</code>:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.09.33.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Verifying if merged is successful</em></p>
<p>When you run this command, Kubernetes communicates with the cluster's control plane to fetch a list of all the nodes that are part of the cluster you created. As you can see, this is the node that was running in the Kubernetes cluster we created inside Azure.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-18.04.07.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Virtual Node running in AKS cluster</em></p>
<h3 id="heading-run-the-command-kubectl-get-pods">Run the command <code>kubectl get pods</code></h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.18.19.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Displaying Pod Information</em></p>
<p>When you run the command <code>kubectl get pods</code>, Kubernetes attempts to retrieve information about all pods within the default namespace of your cluster. But in this case, the output indicates that there are no resources (pods) found within the default namespace, implying that no pods currently exist in that namespace.</p>
<p>A <strong>namespace</strong> in Kubernetes is a virtual cluster environment within which resources like pods, services, and deployments are organized and isolated. It's a way to divide cluster resources between multiple users, teams, or projects. Namespaces provide a scope for names and make it easier to manage and control access to resources.</p>
<p>By default, Kubernetes starts with a "default" namespace, but you can create additional namespaces to organize and manage resources more effectively. Namespaces help prevent naming conflicts and provide a logical separation of resources, allowing different teams or projects to work independently within the same Kubernetes cluster.</p>
<h3 id="heading-create-a-namespace">Create a namespace</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.24.40.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Creating Namespace</em></p>
<p>When you run the command <code>kubectl create namespace database</code>, Kubernetes creates a new namespace named "database." The output "namespace/database created" confirms that the namespace has been successfully created.</p>
<p>You can now use this namespace to organize and manage resources related to databases within the Kubernetes cluster.</p>
<h3 id="heading-confirm-the-namespace">Confirm the namespace</h3>
<p>The command <code>kubectl get namespace</code> lists all namespaces in the Kubernetes cluster including the database namespace we just created, showing their names, status (active), and age.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.26.22.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Confirming namespace</em></p>
<h3 id="heading-get-pod-information-in-database-namespace">Get pod information in database namespace</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-05-at-22.35.29.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Displaying Pod Information related to database namespace</em></p>
<p>This command, <code>kubectl get pods -n database</code>, attempts to fetch information about pods specifically within the "database" namespace. But the output <code>No resources found in database namespace</code> indicates that there are currently no pods deployed in the "database" namespace.</p>
<h2 id="heading-how-to-create-resources-with-yaml">How to Create Resources with YAML</h2>
<p>Let's explore creating resources with YAML to provision our PostgreSQL database running in an Azure Kubernetes cluster. But first, what exactly is YAML?</p>
<p>Kubernetes <a target="_blank" href="https://www.redhat.com/en/topics/automation/what-is-yaml"><strong>YAML</strong></a> is a configuration file written in YAML (YAML Ain't Markup Language). They define how Kubernetes resources like pods, deployments, and services should be set up within a cluster. These files are easy to read and specify details like resource names, types, specifications, labels, and annotations. They're crucial for deploying applications and infrastructure on Kubernetes clusters.</p>
<p>YAML is what you will use to create Kubernetes resources that will run Postgres.</p>
<p>First, you need to <a target="_blank" href="https://github.com/ayowilfred95/Azure-k8s-postgres.git">clone this GitHub repository</a>. Inside, you'll find a Node.js Express application and a YAML file. The Node.js app allows users to register with their email, password, and full name, and also enables them to log in by verifying their details in the database. If their details are found, it displays a success message.</p>
<h3 id="heading-clone-the-repository">Clone the repository</h3>
<p>Create a new folder on your computer and then clone this <a target="_blank" href="https://github.com/ayowilfred95/Azure-k8s-postgres.git">repository</a> into it.</p>
<p>Open your terminal or PowerShell, go to the folder you want, and use the command below to clone the repository into your computer in that location.</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/ayowilfred95/Azure-k8s-postgres.git
</code></pre>
<h3 id="heading-open-the-cloned-repository-in-any-text-editor">Open the cloned repository in any text editor</h3>
<p>I'm using Visual Studio Code, but feel free to use any text editor you prefer. Here's the structure of the project:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-04.15.29.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Project folder structure</em></p>
<h3 id="heading-install-project-dependencies">Install project dependencies</h3>
<p>Open the terminal in VS Code and go to the main directory of the project. Next, execute the command <code>npm install</code> to install all the required packages and dependencies for the project:</p>
<pre><code class="lang-bash">npm install
</code></pre>
<p>Since the backend application is a Node.js Express app, you use npm to install dependencies (similar to how we use <code>maven clean install</code> in Java).</p>
<p>After the dependencies are installed, open the file named "postgres.yaml". It holds all the YAML configurations required to set up your PostgreSQL database that will run in the Kubernetes cluster.</p>
<h2 id="heading-yaml-configuration">YAML Configuration</h2>
<p>In the postgres.yaml file, there are five configurations separated by ---. It's important to use this "---" symbol when declaring different types of Kubernetes resources. If you forget to do this, you'll encounter an error.</p>
<h3 id="heading-storageclass">StorageClass</h3>
<p>The first one is the <code>StorageClass</code>. This YAML configuration defines a StorageClass in Kubernetes for managing storage resources.</p>
<pre><code class="lang-bash">kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: azuredisk-premium-retain
provisioner: kubernetes.io/azure-disk
reclaimPolicy: Retain   <span class="hljs-comment"># Retain or Delete</span>
volumeBindingMode: WaitForFirstConsumer   <span class="hljs-comment"># WaitForFirstConsumer or Immediate</span>
allowVolumeExpansion: <span class="hljs-literal">true</span>    <span class="hljs-comment"># true or false</span>
parameters:
  storageaccounttype: Premium_LRS   <span class="hljs-comment"># Premium or Standard</span>
  kind: Managed
</code></pre>
<p>Let's break down what each part means:</p>
<ul>
<li><p><code>kind: StorageClass</code>: Indicates the type of Kubernetes resource being defined, which is a <code>StorageClass</code>. A <code>StorageClass</code> defines the class of storage offered by a cluster.</p>
</li>
<li><p><code>apiVersion: storage.k8s.io/v1</code>: Specifies the Kubernetes API version being used for this resource.</p>
</li>
<li><p><code>metadata: name: azuredisk-premium-retain</code>: Provides metadata for the <code>StorageClass</code>, including its name, which in this case is "azuredisk-premium-retain".</p>
</li>
<li><p><code>provisioner: kubernetes.io/azure-disk</code>: Specifies the provisioner responsible for provisioning storage. In this case, it's "kubernetes.io/azure-disk", indicating that Azure Disk will be used as the storage provisioner.</p>
</li>
<li><p><code>reclaimPolicy: Retain</code>: Defines the reclaim policy for the storage resources. It specifies what action should be taken when the associated persistent volume is released. Here, it's set to "Retain", meaning the volume is retained even after it's no longer used by a pod.</p>
</li>
<li><p><code>volumeBindingMode: WaitForFirstConsumer</code>: Specifies the volume binding mode, which determines when volume binding should occur. In this case, it's set to "WaitForFirstConsumer", meaning the volume will be bound when the first pod using it is created.</p>
</li>
<li><p><code>allowVolumeExpansion: true</code>: Indicates whether volume expansion is allowed. Setting it to "true" means that the size of the volume can be increased if needed.</p>
</li>
<li><p><code>parameters</code>: Contains additional parameters specific to the provisioner. Here, it specifies the storage account type as "Premium_LRS" and the kind of storage as "Managed".</p>
</li>
</ul>
<p>Overall, this configuration sets up a <code>StorageClass</code> named "azuredisk-premium-retain" using Azure Disk as the provisioner, with specific policies and parameters tailored for Azure storage.</p>
<h3 id="heading-persistentvolumeclaim">PersistentVolumeClaim</h3>
<p>The second configuration in the postgres.yaml file is the <strong>persistent volume claim</strong>.</p>
<p>This YAML configuration defines a <code>PersistentVolumeClaim</code> (PVC) in Kubernetes, which is used to request storage resources.</p>
<pre><code class="lang-bash">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: azure-managed-disk-pvc
spec:
  accessModes:
  - ReadWriteOnce   <span class="hljs-comment"># ReadWriteOnce, ReadOnlyMany or ReadWriteMany</span>
  storageClassName: azuredisk-premium-retain
  resources:
    requests:
      storage: 4Gi
</code></pre>
<p>Let's break down what each part means:</p>
<ul>
<li><p><code>apiVersion: v1</code>: Specifies the Kubernetes API version being used for this resource.</p>
</li>
<li><p><code>kind: PersistentVolumeClaim</code>: Indicates the type of Kubernetes resource being defined, which is a PersistentVolumeClaim. A PVC is used by pods to request storage resources.</p>
</li>
<li><p><code>metadata: name: azure-managed-disk-pvc</code>: Provides metadata for the PersistentVolumeClaim, including its name, which is "azure-managed-disk-pvc".</p>
</li>
<li><p><code>spec</code>: Describes the desired state of the PersistentVolumeClaim.</p>
</li>
<li><p><code>accessModes: - ReadWriteOnce</code>: Specifies the access mode for the volume. Here, it's set to "ReadWriteOnce", meaning the volume can be mounted as read-write by a single node at a time.</p>
</li>
<li><p><code>storageClassName: azuredisk-premium-retain</code>: Specifies the <code>StorageClass</code> to use for provisioning the volume. This PVC will use the <code>StorageClass</code> named "azuredisk-premium-retain" defined previously.</p>
</li>
<li><p><code>resources: requests: storage: 4Gi</code>: Specifies the desired storage capacity for the volume. Here, it requests 4 gigabytes (Gi) of storage.</p>
</li>
</ul>
<p>Overall, this configuration sets up a <code>PersistentVolumeClaim</code> named "azure-managed-disk-pvc" requesting storage resources with specific access modes, storage class, and storage capacity.</p>
<h3 id="heading-configmap">ConfigMap</h3>
<p>The third configuration in the postgres.yaml file is the <strong>config map</strong>. This YAML configuration defines a ConfigMap in Kubernetes, which is used to store configuration data in key-value pairs.</p>
<pre><code class="lang-bash">apiVersion: v1
kind: ConfigMap
metadata:
  name: postgres-config
  labels:
    app: postgres
data:
  POSTGRES_DB: freecodecamp
  POSTGRES_USER: freecodecamp1
  POSTGRES_PASSWORD: freecodecamp@
  PGDATA: /var/lib/postgresql/data/pgdata
</code></pre>
<p>Let's break down what each part means:</p>
<ul>
<li><p><code>apiVersion: v1</code>: Specifies the Kubernetes API version being used for this resource.</p>
</li>
<li><p><code>kind: ConfigMap</code>: Indicates the type of Kubernetes resource being defined, which is a <code>ConfigMap</code>. A <code>ConfigMap</code> is used to store non-confidential data in key-value pairs.</p>
</li>
<li><p><code>metadata: name: postgres-config</code>: Provides metadata for the <code>ConfigMap</code>, including its name, which is "postgres-config".</p>
</li>
<li><p><code>labels: app: postgres</code>: Labels are key-value pairs used to organize and select resources. Here, a label "app" with the value "postgres" is applied to the <code>ConfigMap</code>.</p>
</li>
<li><p><code>data</code>: Contains the key-value pairs of configuration data.</p>
</li>
<li><p><code>POSTGRES_DB: pisonitsha</code>: Specifies the name of the PostgreSQL database as "pisonitsha".</p>
</li>
<li><p><code>POSTGRES_USER: pisonitsha1</code>: Specifies the username for accessing the PostgreSQL database as "pisonitsha1".</p>
</li>
<li><p><code>POSTGRES_PASSWORD: pisonitsha@</code>: Specifies the password for accessing the PostgreSQL database as "pisonitsha@".</p>
</li>
<li><p><code>PGDATA: /var/lib/postgresql/data/pgdata</code>: Specifies the location of PostgreSQL data directory as "/var/lib/postgresql/data/pgdata".</p>
</li>
</ul>
<p>Overall, this configuration sets up a ConfigMap named "postgres-config" containing key-value pairs of configuration data, such as database name, username, password, and data directory location, which can be used by other Kubernetes resources.</p>
<p><strong>Note:</strong> It's recommended to avoid hardcoding secret variables such as <code>POSTGRES_DB</code>, <code>POSTGRES_PASSWORD</code>,<code>PGDATA</code> and instead store them in secret files, for the sake of simplicity in this tutorial, we'll keep them hardcoded.</p>
<h3 id="heading-statefulset">StatefulSet</h3>
<p>The fourth configuration is the <strong>stateful set</strong>.This YAML configuration defines a <code>StatefulSet</code> in Kubernetes, which is used to manage stateful applications like databases.</p>
<pre><code class="lang-bash">apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
spec:
  serviceName: postgres
  selector:
    matchLabels:
      app: postgres
  replicas: 1
  template:
    metadata:
      labels:
        app: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:10.4
          imagePullPolicy: <span class="hljs-string">"IfNotPresent"</span>
          ports:
          - containerPort: 5432
          envFrom:
          - configMapRef:
              name: postgres-config
          volumeMounts:
          - name: azure-managed-disk-pvc
            mountPath: /var/lib/postgresql/data
      volumes:
      - name: azure-managed-disk-pvc
        persistentVolumeClaim:
          claimName: azure-managed-disk-pvc
    ```

Let<span class="hljs-string">'s break down what each part means:

* `apiVersion: apps/v1`: Specifies the Kubernetes API version being used for this resource.
* `kind: StatefulSet`: Indicates the type of Kubernetes resource being defined, which is a `StatefulSet`. `StatefulSets` are used to manage stateful applications by providing unique **identities** and stable **network** identities to each pod.
* `metadata: name: postgres`: Provides metadata for the `StatefulSet`, including its name, which is "postgres".
* `spec`: Describes the desired state of the `StatefulSet`.
* `serviceName: postgres`: Specifies the name of the Kubernetes service that will be used to access the `StatefulSet` pods.
* `selector: matchLabels: app: postgres`: Selects the pods controlled by this `StatefulSet` based on the label "app: postgres".
* `replicas: 1`: Specifies the desired number of replicas (instances) of the StatefulSet, which is 1 in this case.
* `template`: Defines the pod template used to create pods managed by the `StatefulSet`.
* `metadata: labels: app: postgres`: Labels applied to the pods created from this template.
* `spec`: Describes the specification of the containers within the pod.
* `containers`: Specifies the containers running in the pod.
* `name: postgres`: Defines the name of the container as "postgres".
* `image: postgres:10.4`: Specifies the Docker image used for the container, which is "postgres:10.4".
* `imagePullPolicy: "IfNotPresent"`: Specifies the policy for pulling the container image, which is "IfNotPresent", meaning it will only pull the image if it'</span>s not already present on the node.
* `ports: containerPort: 5432`: Specifies the port that the PostgreSQL service inside the container is listening on.
* `envFrom: configMapRef: name: postgres-config`: Injects environment variables from a ConfigMap named <span class="hljs-string">"**postgres-config**"</span> that you defined earlier.
* `volumeMounts: name: azure-managed-disk-pvc mountPath: /var/lib/postgresql/data`: Mounts a persistent volume claim named <span class="hljs-string">"azure-managed-disk-pvc"</span> to the container at the specified path.
* `volumes: name: azure-managed-disk-pvc persistentVolumeClaim: claimName: azure-managed-disk-pvc`: Defines the persistent volume claim named <span class="hljs-string">"azure-managed-disk-pvc"</span> to be used by the pod.

Overall, this configuration sets up a StatefulSet named <span class="hljs-string">"postgres"</span> with one replica, running a PostgreSQL container with specific settings and mounted persistent storage.

<span class="hljs-comment">### Service</span>

The fifth configuration is the **service**. This YAML configuration defines a **Service** <span class="hljs-keyword">in</span> Kubernetes, <span class="hljs-built_in">which</span> is used to expose the `StatefulSet` we declared earlier as a network service.

```bash
apiVersion: v1
kind: Service
metadata:
  name: postgres
  labels:
    app: postgres
spec:
  <span class="hljs-built_in">type</span>: LoadBalancer
  selector:
    app: postgres
  ports:
    - protocol: TCP
      name: https
      port: 5432
      targetPort: 5432
</code></pre>
<p>Let's break down what each part means:</p>
<ul>
<li><p><code>apiVersion: v1</code>: Specifies the Kubernetes API version being used for this resource.</p>
</li>
<li><p><code>kind: Service</code>: Indicates the type of Kubernetes resource being defined, which is a Service. <strong>Services</strong> allow pods to be accessed by other pods or external users.</p>
</li>
<li><p><code>metadata: name: postgres</code>: Provides metadata for the Service, including its name, which is "postgres".</p>
</li>
<li><p><code>labels: app: postgres</code>: Labels are key-value pairs used to organize and select resources. Here, a label "app" with the value "postgres" is applied to the Service.</p>
</li>
<li><p><code>spec</code>: Describes the desired state of the Service.</p>
</li>
<li><p><code>type: LoadBalancer</code>: Specifies the type of Service, which is "LoadBalancer". This type allows the <strong>Service</strong> to be exposed externally with a cloud provider's load balancer.</p>
</li>
<li><p><code>selector: app: postgres</code>: Selects the pods controlled by the Service based on the label "app: postgres".</p>
</li>
<li><p><code>ports</code>: Specifies the ports that the Service will listen on.</p>
</li>
<li><p><code>protocol: TCP</code>: Specifies the protocol used for the port, which is TCP.</p>
</li>
<li><p><code>name:https</code> : Specifies a name for the port, which is "https".</p>
</li>
<li><p><code>port: 5432</code>: Specifies the port number on which the Service will listen, which is 5432.</p>
</li>
<li><p><code>targetPort: 5432</code>: Specifies the target port on the pods to which traffic will be forwarded, which is also 5432. This means that traffic received on port 5432 of the Service will be forwarded to port 5432 on the pods.</p>
</li>
</ul>
<p>Overall, this configuration sets up a Service named "postgres" with a LoadBalancer type, forwarding traffic on port 5432 to pods labeled with "app: postgres".</p>
<h2 id="heading-how-to-deploy-yaml-resource-to-azure-kubernetes-service-aks">How to Deploy YAML Resource to Azure Kubernetes Service (AKS)</h2>
<p>You've previously connected "kubectl" with the Azure Kubernetes Service (AKS) you set up. Let's double-check it.</p>
<p>In your VS Code terminal, rerun the command <code>kubectl get nodes</code>. You'll see an output like this, though your node's value will be different.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/08/Screenshot-2024-05-06-at-05.49.43.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Displaying node information running in Azure Kubernetes cluster</em></p>
<p>Next, verify the namespace you previously created by executing the command: <code>kubectl get namespace database</code>. Your output should resemble this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-05.49.12.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Retrieving Namespace Information</em></p>
<h3 id="heading-deploy-the-yaml-resource">Deploy the YAML resource</h3>
<p>Once you've confirmed everything is set, you can deploy the YAML resource. This will establish your PostgreSQL database in the Azure Kubernetes cluster you've configured.</p>
<p>Run the below command in the main directory where the configuration file is located. Currently, I'm in the project's root directory (azure-k8s-postgres). To deploy the database, just execute this command below:</p>
<pre><code class="lang-bash">kubectl apply -n database -f postgres.yaml
</code></pre>
<p>Your output should look like this. This output confirms that all these components have been successfully created in Kubernetes.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-05.57.52.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Applying Configuration to Namespace</em></p>
<p>Execute the command below to verify that the pod is running:</p>
<pre><code class="lang-bash">kubectl get pods -n database
</code></pre>
<p>Your output should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-06.01.33.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Fetching Pods in Namespace</em></p>
<p>This output confirms that a pod name "postgres-0" is running in your Azure Kubernetes Cluster. But it was not the only pod you created. As I said earlier, to connect to a pod, you need what is called service. And you have declared a service resource in our configuration file which has also been deployed into your Kubernetes.</p>
<p>To get the status of the service, run this command:</p>
<pre><code class="lang-bash">kubectl get services -n database
</code></pre>
<p>Your output should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-06.07.12.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Retrieving Services in Namespace</em></p>
<p>This output displays the services in the "database" namespace, including a service named "postgres" with the type "LoadBalancer," its internal cluster IP, external IP, and port mappings. You'll utilize the external IP along with the Postgres port "5432" to connect your database with the Node.js application. Note that your external IP will differ from mine.</p>
<h2 id="heading-nodejs-application">Node.js Application</h2>
<p>In this section, I'll guide you through setting up your Node.js app to connect to a PostgreSQL database in your Azure Kubernetes Service.</p>
<p>We'll cover sending data into the database and retrieving it using Postman. Also, I'll demonstrate how to check if the data remains in the database even if the pod running PostgreSQL in the cluster is deleted.</p>
<h3 id="heading-configure-your-nodejs-application">Configure your Node.js application</h3>
<p>Go to the database folder and open the database.js file. Replace the host with your EXTERNAL-IP obtained from the service, and leave the rest unchanged since you've already defined those variables in your config map.</p>
<p>Your database.js file should resemble the CodeSnap below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/code.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>CodeSnap of database.js Configuration</em></p>
<h3 id="heading-run-your-nodejs-application">Run your Node.js application</h3>
<p>In your VS Code terminal, execute this command to start the Node.js application locally:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>Your output should look like this if the connection is established successfully.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-06.27.25.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Server Listening on Port 4000</em></p>
<p>If your output looks the same as mine, it indicates that you've successfully connected your Node.js application to the PostgreSQL database running in your Azure Kubernetes cluster. Congratulations! 🎉</p>
<h3 id="heading-test-the-application">Test the application</h3>
<p>Testing is a fundamental principle in DevOps operations. It helps us understand the state of the application we've built before releasing it to users. Any application that doesn't pass the testing stage will not be deployed. This is a rule in DevOps.</p>
<p>For this tutorial, you'll be using Postman. You can download Postman <a target="_blank" href="https://www.postman.com/downloads/">here</a>. Postman enables you to test API endpoints by receiving status responses.</p>
<p>Check out this <a target="_blank" href="https://qalified.com/blog/postman-for-api-testing/">post</a> on how to use Postman to test APIs. If you want to learn more, <a target="_blank" href="https://www.freecodecamp.org/news/learn-how-to-use-postman-to-test-apis/">here's a full course</a> on the subject.</p>
<h3 id="heading-open-your-postman-application">Open your Postman application</h3>
<p>To begin using Postman, start by creating a new API request in your preferred workspace. Choose POST. POST requests add new data to the database or server. Then, paste the endpoint URL (localhost:4000/api/v1/admin/register) for your Postman test.</p>
<p>The below screenshot illustrates how you will create a POST request.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-15.32.32.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman URL Endpoint Configuration</em></p>
<p>In the body, paste the JSON data shown below inside it as shown below:</p>
<pre><code class="lang-bash">{   
    <span class="hljs-string">"fullName"</span>:<span class="hljs-string">"Azure postgres freecodecamp"</span>,
    <span class="hljs-string">"email"</span>:<span class="hljs-string">"freecodecamp@gmail.com"</span>,
    <span class="hljs-string">"password"</span>:<span class="hljs-string">"freecodecamp"</span>
}
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-15.34.58-1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman Request Body</em></p>
<p>Once you've set up the request, just click the "Send" button to send it. Postman will then show you status codes, and the response payload as shown below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-06.56.39.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman API Response</em></p>
<h3 id="heading-confirm-the-data">Confirm the data</h3>
<p>To confirm that the data you sent into the database exists, make a GET request to this endpoint URL: localhost:4000/api/v1/admin/freecodecamp@gmail.com</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-15.45.41.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman GET Request URL Endpoint</em></p>
<p>When you click send, Postman will then show you status codes and the response payload as shown below. Notice that we didn't put anything in the body because this is a GET request.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-15.48.29.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Postman GET Request by Email Retrieval Response</em></p>
<h3 id="heading-delete-the-pod-to-confirm-data-persistence">Delete the pod to confirm data persistence</h3>
<p>We chose to create our PostgreSQL database using a <code>StatefulSet</code> to ensure that data persists even if the pod is destroyed. Let's test this by deleting the pod and checking if the data remains intact.</p>
<p>In your VS Code terminal, execute the command: <code>kubectl delete pod -n database postgres-0</code>.</p>
<p>This command deletes a pod named "postgres-0" in the "database" namespace from your Kubernetes cluster. Your output should look like this.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-07.09.41.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Deleting Pod in Namespace:</em></p>
<h3 id="heading-pod-recreation">Pod recreation</h3>
<p>Kubernetes has a built-in feature called replication controllers or replica sets that ensure a specified number of pod replicas are running at any given time. If a pod is deleted, Kubernetes will automatically recreate it to maintain the desired number of replicas, ensuring high availability</p>
<p>If you run <code>kubectl get pods -n database</code>, you'll notice that Kubernetes has created a new pod with the same name, "postgres-0", to replace the one that was deleted. This ensures that the application remains available and continues to function as expected.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-07.10.04.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Pod Recreated back in Namespace</em></p>
<h3 id="heading-data-persistence">Data persistence</h3>
<p>Navigate back to Postman and make a GET request to the endpoint URL localhost:4000/api/v1/admin/freecodecamp@gmail.com.</p>
<p>You should get the same response as before. So under the hood, when we delete the pod, the storage disk was not deleted. The storage disk is inside the Azure disk. How do we know that? If you run this command:</p>
<pre><code class="lang-bash">kubectl get pvc -n database
</code></pre>
<p>you should get this output:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/05/Screenshot-2024-05-06-at-16.04.48-3.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p><em>Persistennce Volume Claim details in namespace</em></p>
<p>This shows details about a storage called "azure-managed-disk-pvc" in your Kubernetes. It's currently in use and has 4 gigabytes of space available. It's set up to be read and written to by one system at a time. This storage is provided by a service called "azuredisk-premium-retain" that we configured earlier.</p>
<h3 id="heading-clean-up-resources">Clean up resources</h3>
<p>In this tutorial, you created Azure resources in a resource group. If you won't need these resources later, delete the resource group from the Azure portal or run the following command in your terminal:</p>
<pre><code class="lang-bash">az group delete --name AZURE-POSTGRES-RG --yes
</code></pre>
<p>This command might take a minute to run.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>We've gone on quite a journey here! You've learned how to deploy a Postgres container in Azure Kubernetes Service (AKS) and integrate it with a Node.js application.</p>
<p>In this tutorial, I guided you through the process of configuring Kubernetes using Azure Kubernetes Service (AKS). You learned to customize YAML files utilizing StatefulSet, Persistent Volume, and Services to deploy a PostgreSQL database on Azure Kubernetes. You also acquired PostgreSQL database credentials running within AKS to establish connectivity with a Node.js application. I then provided detailed instructions on connecting your Node.js Express app to the Postgres container within the AKS cluster.</p>
<p>Thank you for reading!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Implement Relationship Based Access Control (ReBAC) ]]>
                </title>
                <description>
                    <![CDATA[ By Imran In today's digital age, managing who can access what resources is more critical than ever. That's where ReBAC comes in. It's a fresh take on authorization, focusing on the relationships between different entities rather than just assigning s... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/implement-relationship-based-access-control/</link>
                <guid isPermaLink="false">66d45f31246e57ac83a2c76f</guid>
                
                    <category>
                        <![CDATA[ authorization ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 08 Mar 2024 14:08:21 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/RELATIONSHIP-BASED-ACCESS-CONTROL.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Imran</p>
<p>In today's digital age, managing who can access what resources is more critical than ever. That's where ReBAC comes in. It's a fresh take on authorization, focusing on the relationships between different entities rather than just assigning static roles or attributes.</p>
<p>Traditional access control methods, like Role-Based Access Control (RBAC), assign specific roles to users. While this works in many cases, it can become difficult, especially in dynamic environments where roles and permissions need to adapt quickly. On the other hand, Attribute-Based Access Control (ABAC) offers flexibility based on user attributes, but it can get complex to manage.</p>
<p>Now, ReBAC is all about understanding the intricate web of relationships between entities. Whether it's within an organization, a social media platform, or a project management tool, ReBAC ensures that access control remains dynamic and context-aware.</p>
<p>By the end of this tutorial, you'll have a clear understanding of ReBAC and be able to model a ReBAC scenario.</p>
<h2 id="heading-key-takeaways">Key Takeaways</h2>
<ul>
<li><strong>ReBAC Principles:</strong> Understand how ReBAC uses relationships between entities for access control, differing from traditional models.</li>
<li><strong>Policy Visualization:</strong> Learn about representing policies as graphs for clearer management.</li>
<li><strong>Real-World Examples:</strong> Explore ReBAC's application in scenarios like social media platforms and project management tools.</li>
<li><strong>Benefits of ReBAC:</strong> Discover the advantages like granular control and dynamic policy adaptation.</li>
<li><strong>Permission Models:</strong> Get familiar with ReBAC's common models such as Ownership and Hierarchical Models.</li>
<li><strong>Permify Implementation:</strong> Step-by-step guide to implement ReBAC in Permify, including entity definition, relationship establishment, and permissions setup.</li>
</ul>
<h2 id="heading-what-is-relationship-based-access-control-rebac">What is Relationship-Based Access Control (ReBAC)?</h2>
<p>Traditional access control methods, like Role-Based Access Control (RBAC), assign specific roles to users, like giving someone a badge that says "manager" or "employee". But what if the roles aren't so clear-cut, and relationships between people and resources matter more?</p>
<p>That's where Relationship-Based Access Control (ReBAC) steps in. Instead of relying solely on predefined roles or attributes, ReBAC considers the intricate web of connections between users, resources, and other entities. It's like saying, "You can access this because you're connected to it in this specific way", rather than just based on a generic label.</p>
<p>But how does ReBAC actually do this? ReBAC examines the relationships between entities, such as users and resources, and uses these connections to determine access.</p>
<p>Let's break it down further. In our everyday lives, we have relationships that matter. Think about social media – you can see certain posts because you're friends with someone or because someone you follow liked it. ReBAC takes this idea and applies it to access control in systems.</p>
<h2 id="heading-policy-as-a-graph">Policy as a Graph</h2>
<p>At the core of ReBAC lies the concept of "Policy as a Graph". This idea shows the importance of visualizing access policies through relationships.</p>
<p>Imagine that you have a detailed map of a bustling city. It doesn't just show buildings but also the connections between them – the roads, bridges, and pathways that link everything together.</p>
<p>Now, picture this map as a representation of your organization. Instead of buildings, it represents team members, departments, and their roles. The connections between them symbolize the relationships that dictate access.</p>
<p>This is what we mean by "Policy as a Graph" in ReBAC.</p>
<p>In simpler terms, access policies are like interconnected dots on a graph. Each dot represents an entity, and the lines between them signify the relationships influencing authorization. It's a visual representation that helps us understand the complex web of connections that govern access.</p>
<h2 id="heading-how-is-rebac-different-from-other-control-models">How is ReBAC Different from Other Control Models?</h2>
<p>Now, let's explore how ReBAC sets itself apart from other access control models, such as Role-Based Access Control (RBAC).</p>
<p>Unlike traditional models, ReBAC doesn't rely solely on rigid roles or attributes. Instead, it works on deriving permissions from existing relationships. Here's how it stands out:</p>
<ul>
<li><strong>Role Derivation:</strong>ReBAC allows the creation of authorization policies based on pre-existing relationships. This means that assigning a user a certain role in one context might automatically extend that role to related entities, saving the need for manual assignment.</li>
<li><strong>Resource Roles:</strong>Unlike global roles in traditional models, ReBAC introduces the concept of resource-specific roles (for example: Folder#Owner). These roles are exclusive to the context of a particular resource, ensuring that permissions are relevant and tailored to that specific entity.</li>
</ul>
<h2 id="heading-real-world-examples">Real-World Examples</h2>
<p>To better understand how Relationship-Based Access Control (ReBAC) functions in the real world, let's explore two scenarios that mimic everyday complexities.</p>
<p>These examples will help illustrate how ReBAC excels in managing intricate access dynamics.</p>
<h3 id="heading-instagram-like-social-platform">Instagram-like Social Platform</h3>
<p>Consider an Instagram-inspired platform where users hold individual accounts. Each account consists of user-generated content, namely pictures (Pic 1 and Pic 2), chat interactions with different users, and project collaboration.</p>
<p>The user account possesses a list of blocked users who are restricted from viewing pictures. Here's a detailed breakdown of the entities and permissions:</p>
<h4 id="heading-1-account-entities">1. Account Entities</h4>
<ul>
<li><strong>User Account:</strong> Represents individual user accounts on the platform.</li>
<li><strong>Pictures (Pic 1 and Pic 2):</strong> Depict user-generated visual content.</li>
<li><strong>Chats:</strong> Captures interaction histories with different users.</li>
<li><strong>Blocked Users List:</strong> Maintains a list of users who are blocked from viewing pictures.</li>
</ul>
<h4 id="heading-2-permissions-dynamics">2. Permissions Dynamics</h4>
<h5 id="heading-account-access-permissions">Account Access Permissions:</h5>
<ul>
<li>"Account#Owner" grants ownership, allowing the user account holder to manage all aspects.</li>
<li>"Account#Viewer" enables others to view the user's account.</li>
</ul>
<h5 id="heading-picture-management-permissions">Picture Management Permissions:</h5>
<ul>
<li>"Picture#Owner" designates ownership at the picture level, allowing the user to edit, delete, and upload pictures.</li>
<li>"Picture#Viewer" permits normal viewers to only view pictures.</li>
<li>"BlockedUser#CannotView" ensures that blocked users cannot view pictures.</li>
</ul>
<h5 id="heading-chat-interaction-permissions">Chat Interaction Permissions:</h5>
<ul>
<li>"Chat#Participant" allows users to participate in chat interactions.</li>
<li>"Chat#BlockedUser" restricts certain users from participating in chats.</li>
</ul>
<h5 id="heading-account-editing-permissions">Account Editing Permissions:</h5>
<ul>
<li>"Account#Edit" grants the ability to update account details and preferences.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Instagram.png" alt="Instagram.png" width="600" height="400" loading="lazy">
<em>Instagram-like Social Platform Entities</em></p>
<p>In this scenario, the "#" symbol represents the relationship between entities when defining permissions. For example, "Account#Owner" signifies ownership of the user account, allowing the account holder to manage all aspects of their account.</p>
<h3 id="heading-project-management-tool">Project Management Tool</h3>
<p>Imagine a project management tool where teams collaborate on various projects. Entities like "Teams", "Projects", and "Tasks" play central roles, showcasing ReBAC's adaptability:</p>
<h4 id="heading-1-team-entities">1. Team Entities:</h4>
<ul>
<li><strong>Teams:</strong> Represent collaborative groups within the project management tool.</li>
<li><strong>Projects:</strong> Encompass various ongoing initiatives.</li>
<li><strong>Tasks:</strong> Break down project activities into manageable tasks.</li>
</ul>
<h4 id="heading-2-permissions-dynamics-1">2. Permissions Dynamics:</h4>
<h5 id="heading-team-leadership-permissions">Team Leadership Permissions:</h5>
<ul>
<li>"Team#Lead" designates team leadership, allowing leaders to manage team-related activities.</li>
</ul>
<h5 id="heading-project-ownership-permissions">Project Ownership Permissions:</h5>
<ul>
<li>"Project#Owner" signifies ownership at the project level, granting comprehensive control over project-related actions.</li>
</ul>
<p><strong>Task Assignment Permissions:</strong></p>
<ul>
<li>"Task#Assignee" designates individuals responsible for specific tasks.</li>
</ul>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/project.png" alt="project.png" width="600" height="400" loading="lazy">
<em>Project Management Tool Entities</em></p>
<p>These real-world scenarios demonstrate ReBAC's versatility and effectiveness in managing access control in different settings.</p>
<h2 id="heading-advantages-of-rebac">Advantages of ReBAC</h2>
<p>Now, let's understand why Relationship-Based Access Control (ReBAC) stands out from traditional methods like Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC). ReBAC brings a host of benefits to the table, enhancing scalability, flexibility, and adaptability in complex organizational setups. Let's take a closer look at its key advantages:</p>
<h3 id="heading-granular-and-contextual-control">Granular and Contextual Control</h3>
<p>ReBAC allows organizations to define granular access controls tailored to the specific relationships between users, resources, and entities. This ensures that permissions are contextually relevant, providing a nuanced level of control.</p>
<h3 id="heading-efficient-management-of-hierarchies">Efficient Management of Hierarchies</h3>
<p>In scenarios with hierarchical structures, ReBAC simplifies the management of access control. By allowing permissions to be inherited based on relationships, it reduces the need for manual role assignments. </p>
<p>This simplifies the creation of natural connections between different business units, resources, and entities, making it easier to navigate complex hierarchies.</p>
<h3 id="heading-scalability-and-adaptability">Scalability and Adaptability</h3>
<p>ReBAC is designed to scale with organizational growth and changes in relationships. It easily accommodates the introduction of new entities or connections. However, it's crucial to address the challenge of role explosion, where the number of roles grows exponentially alongside asset growth. </p>
<p>Without proper management, this can lead to security risks and administrative overhead. Yet, ReBAC's scalability ensures that access controls remain effective, mitigating these challenges and avoiding the need for extensive modifications.</p>
<p>With these advantages in mind, ReBAC offers a robust framework for access control that meets the evolving needs of modern organizations. Now, let's delve into the common Relationship Type Permission Models to further understand how ReBAC operates.</p>
<h2 id="heading-common-relationship-type-permission-models">Common Relationship Type Permission Models</h2>
<p>Let’s look at the some of the permission models:</p>
<h3 id="heading-ownership-model"><strong>Owners</strong>hip Model</h3>
<p>The Ownership Model in ReBAC is a fundamental concept where ownership relationships streamline authorization within hierarchical structures.</p>
<p>In this model, the act of owning a higher-level entity automatically extends ownership over its subordinate entities.</p>
<p>Imagine a scenario in a cloud storage platform where users create folders to organize their files. In the Ownership Model, the user who creates a folder is designated as the owner.</p>
<p>Consequently, this ownership relation automatically grants the user ownership permissions for all files within that folder.</p>
<p>This hierarchical ownership structure simplifies permission management and mirrors real-world ownership dynamics.</p>
<h3 id="heading-parent-child-amp-hierarchical-model"><strong>Parent-Child &amp; Hierarc</strong>hical Model</h3>
<p>The Parent-Child &amp; Hierarchical Model is a powerful tool for managing access control in hierarchical structures such as organizational frameworks or file systems.</p>
<p>In this model, permissions granted at the parent level down to its child entities, ensuring a cohesive and efficient authorization system.</p>
<p>Consider a corporate environment where organizations have multiple departments. Using ReBAC's Parent-Child &amp; Hierarchical Model, permissions granted at the organization level, such as admin privileges, seamlessly extend to the organization's departments and their respective members.</p>
<p>This hierarchical flow of permissions reflects the organizational structure, making it easy to manage access control across different levels.</p>
<h3 id="heading-user-groups-amp-teams-model">User Groups &amp; Teams Model</h3>
<p>The User Groups &amp; Teams Model allows for efficient permission management by grouping users based on shared attributes or project affiliations.</p>
<p>In this model, permissions assigned to a group leader, for instance, can be effortlessly applied to all members of that group.</p>
<p>In a collaborative project management tool, teams serve as user groups. Applying ReBAC's User Groups &amp; Teams Model, the team lead's permissions, like editing or deleting project tasks, can be automatically inherited by all team members.</p>
<p>This streamlined approach simplifies access control in collaborative environments, where team-based permissions are crucial for project efficiency.</p>
<p>These three Relationship-Based Access Control models demonstrate the flexibility and adaptability of ReBAC in handling diverse organizational structures and application domains.</p>
<p>By aligning permissions with inherent relationships among entities, ReBAC provides an intuitive and powerful access control framework.</p>
<h2 id="heading-how-to-implement-rebac-with-permify">How to Implement ReBAC with Permify</h2>
<p>Now, let's practically implement ReBAC using Permify.</p>
<p><a target="_blank" href="https://permify.co/">Permify</a> is an open-source authorization as a service platform that allows developers to model, manage, and enforce access control in applications. It provides tools for defining complex authorization rules and relationships between entities, such as users, organizations, and resources.</p>
<p>Permify uses a domain-specific language for creating authorization models and offers a Playground environment for testing these models.</p>
<p>It also supports the creation of relational tuples and attributes for managing dynamic access control scenarios, streamlining the process of implementing robust and flexible authorization systems in software applications.</p>
<p>We'll create a scenario covering both Ownership and Parent-Child &amp; Hierarchical models.</p>
<p>We'll use the <a target="_blank" href="https://play.permify.co/">Permify Playground</a> for modeling.</p>
<h3 id="heading-modeling">Modeling</h3>
<p>Modeling in Permify involves creating a schema that defines the relationships and permissions between different entities in your system. </p>
<p>Here's a simplified process:</p>
<ol>
<li><strong>Define Entities</strong>: Start by creating entities that represent the resources in your system (for example: users, organizations, teams).</li>
<li><strong>Define Relations</strong>: Establish relationships between these entities. For example, an organization can have members and admins, or a team can be part of an organization.</li>
<li><strong>Define Actions and Permissions</strong>: Specify the actions that can be performed on each entity and the conditions under which they are allowed. For example, only admins can delete an organization.</li>
</ol>
<p>Permify uses its own language for modeling authorization logic, allowing for complex structures using set-algebraic operators. The modeling process includes defining entities, relations, actions, permissions, and, if needed, attributes for more advanced scenarios like ABAC (Attribute-Based Access Control).</p>
<p>Modeling in Permify is about creating a clear blueprint of your organization's structure and defining who gets to do what. </p>
<p>Let's break down how to model a schema in Permify.</p>
<h3 id="heading-step-1-define-entities">Step 1: Define Entities</h3>
<p>Entities are the core objects in your model. In this case, we have <code>user</code>, <code>organization</code>, <code>department</code>, <code>project</code>, <code>file</code>, and <code>task</code>.</p>
<pre><code class="lang-jsx">entity user {}
entity organization {}
entity department {}
entity project {}
entity file {}
entity task {}
</code></pre>
<h3 id="heading-step-2-establish-relationships">Step 2: Establish Relationships</h3>
<p>Next, we specify relationships between these entities. This defines how they are connected.</p>
<p><strong>Organization</strong>:</p>
<ul>
<li>Has <code>admin</code> who are users.</li>
</ul>
<pre><code class="lang-jsx">entity organization {
    relation admin @user
}
</code></pre>
<p><strong>Department</strong>:</p>
<ul>
<li>Belongs to an <code>organization</code> (parent).</li>
<li>Has <code>head</code>, <code>manager</code>, and <code>employee</code> roles, all of which are users.</li>
</ul>
<pre><code class="lang-jsx">entity department {
    relation parent @organization
    relation head @user
    relation manager @user
    relation employee @user
}
</code></pre>
<p><strong>Project</strong>:</p>
<ul>
<li>Belongs to a <code>department</code> (parent).</li>
</ul>
<pre><code class="lang-jsx">entity project {
    relation parent @department
}
</code></pre>
<p><strong>File</strong>:</p>
<ul>
<li>Belongs to a <code>department</code> (parent).</li>
<li>Has an <code>owner</code> who is a user.</li>
</ul>
<pre><code class="lang-jsx">entity file {
    relation parent @department
    relation owner @user
}
</code></pre>
<p><strong>Task</strong>:</p>
<ul>
<li>Belongs to a <code>project</code> (parent).</li>
<li>Has an <code>assignee</code> who is a user.</li>
</ul>
<pre><code class="lang-jsx">entity task {
    relation parent @project
    relation assignee @user
}
</code></pre>
<h3 id="heading-step-3-define-permissions">Step 3: Define Permissions</h3>
<p>Permissions determine what actions specific roles can perform on each entity.</p>
<p><strong>Project</strong>:</p>
<ul>
<li><code>contribute_to_project</code> permission is granted to <code>employee</code> or <code>manager</code> of the parent <code>department</code>.</li>
</ul>
<pre><code class="lang-jsx">entity project {
    <span class="hljs-comment">// ... (existing relations)</span>
    permission contribute_to_project = parent.employee or parent.manager
}
</code></pre>
<p><strong>File</strong>:</p>
<ul>
<li><code>read</code>, <code>edit</code>, and <code>delete</code> permissions are controlled based on the <code>manager</code> of the parent <code>department</code> and the <code>owner</code>.</li>
</ul>
<pre><code class="lang-jsx">entity file {
    <span class="hljs-comment">// ... (existing relations)</span>
    permission read   = parent.manager or owner
    permission edit   = parent.manager or owner
    permission <span class="hljs-keyword">delete</span> = owner
}
</code></pre>
<p><strong>Task</strong>:</p>
<ul>
<li><code>view_task</code> permission is given to the <code>assignee</code>.</li>
</ul>
<pre><code class="lang-jsx">entity task {
    <span class="hljs-comment">// ... (existing relations)</span>
    permission view_task = assignee
}
</code></pre>
<p><strong>Full Schema:</strong></p>
<pre><code><span class="hljs-comment">// Define entities</span>
entity user {}

entity organization {
    <span class="hljs-comment">// Organizational roles</span>
    relation admin @user
}

entity department {
    <span class="hljs-comment">// Department roles</span>
    relation parent @organization
    relation head @user
    relation manager @user
    relation employee @user
}

entity project {
    <span class="hljs-comment">// Project roles</span>
    relation parent @department

    <span class="hljs-comment">// Permissions</span>
    permission contribute_to_project = parent.employee or parent.manager
}

entity file {
    <span class="hljs-comment">// Represents files' parent entity (department)</span>
    relation parent @department

    <span class="hljs-comment">// Represents the owner of the file</span>
    relation owner @user

    <span class="hljs-comment">// Permissions</span>
    permission read   = parent.manager or owner
    permission edit   = parent.manager or owner
    permission <span class="hljs-keyword">delete</span> = owner
}

entity task {
    <span class="hljs-comment">// Represents tasks' parent entity (project)</span>
    relation parent @project

    <span class="hljs-comment">// Represents the assignee of the task</span>
    relation assignee @user

    <span class="hljs-comment">// Permissions</span>
    permission view_task   = assignee
}
</code></pre><h3 id="heading-relation-tuples">Relation Tuples</h3>
<p>The creation of relation tuples for the organization schema can be accomplished through the Permify Playground and API. </p>
<p>Here's how the relationship tuples would be structured according to the schema:</p>
<h4 id="heading-user-and-organization-relationships">User and Organization Relationships:</h4>
<ul>
<li>For assigning a user as an admin in an organization, the tuple would be: <code>organization:ID#admin@user:ID</code>.</li>
<li>To denote a user as a member of an organization: <code>organization:ID#member@user:ID</code>.</li>
</ul>
<h4 id="heading-user-and-department-relationships">User and Department Relationships:</h4>
<ul>
<li>Assigning a head to a department: <code>department:ID#head@user:ID</code>.</li>
<li>Assigning a manager to a department: <code>department:ID#manager@user:ID</code>.</li>
<li>Associating an employee with a department: <code>department:ID#employee@user:ID</code>.</li>
<li>To set a department's parent organization: <code>department:ID#parent@organization:ID</code>.</li>
</ul>
<h4 id="heading-project-and-department-relationships">Project and Department Relationships:</h4>
<ul>
<li>To define the parent department of a project: <code>project:ID#parent@department:ID</code>.</li>
</ul>
<h4 id="heading-file-management">File Management:</h4>
<ul>
<li>Associating a file with its parent department: <code>file:ID#parent@department:ID</code>.</li>
<li>Defining the owner of a file: <code>file:ID#owner@user:ID</code>.</li>
</ul>
<h4 id="heading-task-management">Task Management:</h4>
<ul>
<li>Linking a task to its parent project: <code>task:ID#parent@project:ID</code>.</li>
<li>Assigning a user as the assignee of a task: <code>task:ID#assignee@user:ID</code>.</li>
</ul>
<p>In each of these tuples, <code>ID</code> is a placeholder that should be replaced with the actual identifier of the entity or user in your system. </p>
<p>For instance, if you have an organization with an ID of 1 and a user with an ID of 3, and you want to assign this user as an admin of that organization, the tuple would be <code>organization:1#admin@user:3</code>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Untitled-2.png" alt="Untitled" width="600" height="400" loading="lazy">
<em>Relationships dashboard</em></p>
<p>These tuples are created and managed using the Permify API. The API allows for creating, updating, and deleting these tuples as needed, reflecting the dynamic nature of relationships and permissions in an organization. This flexibility ensures that your authorization data is always up-to-date and consistent with the current state of your system's entities and their relationships.</p>
<h3 id="heading-enforcement">Enforcement</h3>
<p>To enforce access control in your schema using Permify, you can create scenarios in the Permify Playground's Enforcement section. This is done using YAML to define various test scenarios. </p>
<p>Here's an example based on your schema and the created relation tuples:</p>
<h4 id="heading-check-if-a-user-for-example-user2-can-contribute-to-a-project">Check if a user (for example: user:2) can contribute to a project:</h4>
<ul>
<li>Entity: <code>project:1</code></li>
<li>Subject: <code>user:2</code></li>
<li>Assertion: <code>contribute_to_project: true or false</code> (depending on whether user:2 is an employee or manager in the parent department of project:1).</li>
</ul>
<pre><code>- name: user_access_test
  <span class="hljs-attr">checks</span>:
    - entity: project:<span class="hljs-number">1</span>
      <span class="hljs-attr">subject</span>: user:<span class="hljs-number">2</span>
      <span class="hljs-attr">context</span>: <span class="hljs-literal">null</span>
      <span class="hljs-attr">assertions</span>:
        contribute_to_project: <span class="hljs-literal">false</span>
  <span class="hljs-attr">entity_filters</span>: []
  <span class="hljs-attr">subject_filters</span>: []
</code></pre><p><strong>Check if a user (for example: user:4) can view a task</strong>:</p>
<ul>
<li>Entity: <code>task:1</code></li>
<li>Subject: <code>user:4</code></li>
<li>Assertion: <code>view_task: true or false</code> (true if user:4 is the assignee of the task).</li>
</ul>
<pre><code>- name: user_access_test
  <span class="hljs-attr">checks</span>:
    - entity: task:<span class="hljs-number">1</span>
      <span class="hljs-attr">subject</span>: user:<span class="hljs-number">4</span>
      <span class="hljs-attr">context</span>: <span class="hljs-literal">null</span>
      <span class="hljs-attr">assertions</span>:
        view_task: <span class="hljs-literal">false</span>
  <span class="hljs-attr">entity_filters</span>: []
  <span class="hljs-attr">subject_filters</span>: []
</code></pre><p>These scenarios will help you validate the permissions as per your schema in a controlled environment. </p>
<p>Each assertion in the YAML scenario will define the expected outcome (true or false) for a particular action or permission based on your schema and data tuples.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/03/Untitled-1.png" alt="Untitled" width="600" height="400" loading="lazy">
<em>YAML representation</em></p>
<p>For detailed steps and examples, refer to the <a target="_blank" href="https://docs.permify.co/docs/getting-started/modeling/">Permify Modeling Documentation</a>.</p>
<h3 id="heading-conclusion">Conclusion</h3>
<p>By following these steps, you can effectively implement a sophisticated ReBAC system using Permify. </p>
<p>This implementation will provide a robust, flexible, and secure access control framework tailored to the unique needs and relationships within your organization.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How Databases Guarantee Isolation – Pessimistic vs Optimistic Concurrency Control Explained ]]>
                </title>
                <description>
                    <![CDATA[ ACID (Atomicity, Consistency, Isolation, and Durability) is a set of guarantees when working with a DBMS. Pessimistic and optimistic concurrency control explains how databases achieve the “I” in ACID. Isolation is a guarantee that concurrently runnin... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-databases-guarantee-isolation/</link>
                <guid isPermaLink="false">66d45e12f855545810e93431</guid>
                
                    <category>
                        <![CDATA[ concurrency ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Mon, 05 Feb 2024 22:41:18 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/02/cover--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>ACID (Atomicity, Consistency, Isolation, and Durability) is a set of guarantees when working with a DBMS. Pessimistic and optimistic concurrency control explains how databases achieve the “I” in ACID.</p>
<p>Isolation is a guarantee that concurrently running transactions should not interfere with each other. This is arguably the most important ACID property, because different DBMS can often have different default isolation levels. And you may need to change this based on what is needed for your application.</p>
<p>In a <a target="_blank" href="https://lightcloud.substack.com/p/acid-databases-explained">previous article</a>, I explained the two main isolation levels used by most DBMS. These are the <a target="_blank" href="https://lightcloud.substack.com/i/140524854/read-committed">read committed</a> and <a target="_blank" href="https://lightcloud.substack.com/i/140524854/repeatable-read">repeatable read</a> isolation levels.</p>
<p>Pessimistic and optimistic concurrency controls essentially explain some of the ways a database is able to achieve these two isolation guarantees.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><p><a class="post-section-overview" href="#heading-pessimistic-concurrency-control">Pessimistic Concurrency Control</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-library-analogy-for-pessimistic-concurrency-control">Pessimistic Concurrency Control Analogy</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-simple-real-world-example-of-pessimistic-concurrency-control-in-action">Real-World Example of Pessimistic Concurrency Control</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-and-challenges-of-pessimistic-concurrency-control">Pros and Cons of Pessimistic Concurrency Control</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-pessimistic-concurrency-controls-guarantee-the-read-committed-isolation-level">How it Guarantees the Read Committed Isolation Level</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-optimistic-concurrency-control">Optimistic Concurrency Control</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-a-simple-real-world-example-of-optimistic-concurrency-control-in-action">Real-World Example of Optimistic Concurrency Control</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-benefits-and-challenges-of-optimistic-concurrency-control">Pros and Cons of Optimistic Concurrency Control</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-optimistic-concurrency-controls-guarantee-the-repeatable-read-isolation-level">How it Guarantees the Repeatable Read Isolation Level</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-bringing-it-together">Bringing it Together</a></p>
</li>
</ol>
<h2 id="heading-pessimistic-concurrency-control">Pessimistic Concurrency Control</h2>
<p>With pessimistic concurrency control, the DBMS assumes that conflicts between transactions are likely to occur. It is pessimistic – that is, it assumes that if something can go wrong, it will go wrong. This pessimism prevents conflicts from occurring by blocking them before they get a chance to start.</p>
<p>To prevent these conflicts, it <em>locks</em> the data that a transaction is using until the transaction is completed. This approach is 'pessimistic' because it assumes the worst-case scenario – that every transaction might lead to a <em>conflict</em>. The data is therefore locked in order to prevent conflicts from happening.</p>
<p>I've mentioned two technical terms here that need clarification: <em>locks</em> and <em>conflict</em>.</p>
<h3 id="heading-what-are-locks">What are locks?</h3>
<p>A lock is a mechanism used to control access to a database item, like a row or table. Locks ensure data integrity, if multiple transactions are occurring at the same time.</p>
<p>In very simple terms, a lock is analogous to a reservation on the database item. A reservation, be it a restaurant, hotel, or a train, prevents other people from using the resource you reserved for a fixed duration of time. Locks work in a similar way.</p>
<p>There are two types of locks: a read lock and a write lock.</p>
<p>A read lock can be shared by multiple transactions trying to read the same database item. But it blocks other transactions from updating that database item.</p>
<p>A write lock is exclusive – that is, it can only be held by a single transaction. A transaction with a write lock on a database item blocks every other transaction from reading or updating that database item.</p>
<h3 id="heading-what-are-conflicts">What are conflicts?</h3>
<p>A conflict refers to a situation where multiple transactions are attempting to access and modify the same data concurrently, in a way that could lead to inconsistencies or errors in the database.</p>
<h3 id="heading-a-library-analogy-for-pessimistic-concurrency-control">A Library Analogy for Pessimistic Concurrency Control</h3>
<p>First, let us describe an analogy for a write lock.</p>
<p>Imagine you're at a library, and you want to borrow a hard copy of a popular book, say, The Great Gatsby by F. Scott Fitzgerald.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd683922-af28-4886-a6dc-6c121ac7e915_1858x1054.png" alt="Image" width="1456" height="826" loading="lazy"></p>
<p><em>Write locks are analogous to borrowing a physical book from the library</em></p>
<p>With a write lock, the librarian assumes that there can be conflicts over who gets to borrow the book. So, they implement a strict rule to avoid conflicts: only one person can hold the reservation for a physical book at a time.</p>
<p>When you reserve the book, no one else can borrow it. The book is available to be reserved again only once it is returned. This is similar to how a write lock works.</p>
<p>Write locks are exclusive. This means that they can only he held by a single transaction at any time. Similarly, reserving a physical book from the library means no one else has access to it. Only the person with the reservation can read the book, or write in it (although writing in a library book is bad form).</p>
<p>Read locks work a bit differently.</p>
<p>A read lock is analogous to someone making a reservation to borrow an e-book. Borrowing an e-book is not a very popular thing to do, but some libraries do have such a service.</p>
<p>Many people can make the same reservation for the same e-book without any conflict. One person borrowing an e-book version of The Great Gatsby does not stop others from doing the same. But no one who borrows an e-book can update it, by scribbling notes in it that can be seen by others, for example.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbb2c7acc-c32e-47f6-a412-2000ae14da2b_2156x1344.png" alt="Image" width="1456" height="908" loading="lazy"></p>
<p><em>Read locks are analogous to borrowing an e-book from the library</em></p>
<p>Pessimistic concurrency control is very safe because it prevents conflicts from occurring by blocking them before they get a chance to start. A write lock on a database item prevents other transactions from reading or updating that item while that lock is held, similar to to how a library stops more than one person from trying to borrow the same physical book at the same time.</p>
<p>A read lock on a database item allows other transactions to also obtain a read lock for that item, but prevents transactions from updating that item. This is analogous to borrowing an e-book, where multiple people can borrow the same e-book at the same time, but can’t make any updates to it.</p>
<h3 id="heading-a-simple-real-world-example-of-pessimistic-concurrency-control-in-action">A Simple Real-World Example of Pessimistic Concurrency Control in Action</h3>
<p>Let's illustrate how pessimistic concurrency control works using a simple example involving a bank balance database table. Assume we have a table named Accounts with the following columns: AccountID and Balance.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1d9e2dd5-5abe-46ea-bbb6-033f1d5bc663_1096x534.png" alt="Image" width="1096" height="534" loading="lazy"></p>
<p><em>Database columns for AccountID and Balance</em></p>
<p>Two transactions, T1 and T2, intend to update the balance of account 12345. T1 wants to withdraw $300, and T2 wants to deposit $400. At the end of these two transactions, the account balance should read $1600</p>
<p>Here are the steps of how this will work using write locks:</p>
<ol>
<li><p>Start of T1 (Withdrawal): T1 requests to update the balance of AccountID 12345. The database system places an exclusive write lock on the row for AccountID 12345, preventing other transactions from reading or writing to this row until T1 is completed. T1 reads the balance ($1500).</p>
</li>
<li><p>T1 Processing: T1 calculates the new balance as $1200 ($1500 - $300).</p>
</li>
<li><p>Commit T1: T1 writes the new balance ($1200) back to the database. Upon successful commit, T1 releases the exclusive lock on AccountID 12345.</p>
</li>
<li><p>Start of T2 (Deposit) After T1 Completes: Now that T1 has completed and the lock is released, T2 can start. T2 attempts to read and update the balance for AccountID 12345. The database system places an exclusive lock on the row for AccountID 12345 for T2, ensuring no other transactions can interfere. T2 reads the updated balance ($1200).</p>
</li>
<li><p>T2 Processing: T2 calculates the new balance as $1600 ($1200 + $400).</p>
</li>
<li><p>Commit T2: T2 writes the new balance ($1600) back to the database. Upon successful commit, T2 releases the exclusive lock on AccountID 12345.</p>
</li>
<li><p>Result: The Accounts table is updated using locks After T1: $1200 After T2: $1600</p>
</li>
</ol>
<p>Without a write lock in this example, T1 and T2 could read the original balance of $1500 at the same time. So, instead of a balance of $1200 after T1 has committed, T2 still reads the original balance of $1500 and adds $400. This would cause the final balance to be $1500 + $400 = $1900 (instead of $1600).</p>
<p>Absence of locking has created free money, which is never a bad thing for a customer. But, if money can be conjured out of thin air because of these conflicts, it can also vanish, and accidentally shrinking bank balances are a quick way to make customers unhappy.</p>
<h3 id="heading-benefits-and-challenges-of-pessimistic-concurrency-control">Benefits and Challenges of Pessimistic Concurrency Control</h3>
<p>Just like reserving a book ensures that it's set aside for one person, pessimistic concurrency control locks data for a single transaction. Other transactions cannot access or modify this data until the lock is released.</p>
<p>This method prevents two people from trying to take out the same popular book at the same time, thereby avoiding disputes. Similarly, in databases, it stops conflicts due to concurrent transactions before they get a chance to start.</p>
<p>But this approach can be inefficient. The reserved book might sit on the reserved shelf for a while, stopping other people from reading it.</p>
<p>In databases, this locking mechanism can lead to underutilisation of resources and a slowdown in the speed transactions take to complete, since a subset of the data is locked and inaccessible to other transactions.</p>
<h3 id="heading-how-pessimistic-concurrency-controls-guarantee-the-read-committed-isolation-level">How Pessimistic Concurrency Controls Guarantee the Read Committed Isolation Level</h3>
<p>So, how exactly does pessimistic concurrency control work in ensuring the isolation guarantee, that is the “I” in ACID? The implementation details can vary across different DBMS. But the explanation here shows the general approach.</p>
<p>Recall that <a target="_blank" href="https://lightcloud.substack.com/i/140524854/read-committed">the read committed isolation</a> level prevents dirty writes and dirty reads.</p>
<h4 id="heading-preventing-dirty-writes">Preventing Dirty Writes</h4>
<p>Overwriting data that has already been written by another transaction but not yet committed is called a dirty write. A common approach to preventing dirty writes is to use pessimistic concurrency control. For example, by using a write lock at the row level.</p>
<p>When a transaction wants to modify a row, it acquires a lock on that row and holds it until the transaction is complete. Recall that write locks can only be held by a single transaction. This prevents another transaction from acquiring a lock to modify that row.</p>
<h4 id="heading-preventing-dirty-reads">Preventing Dirty Reads</h4>
<p>Reading data from another transaction that has not yet been committed is called a dirty read. Dirty reads are prevented using either a read or write lock. Once a transaction acquires a read lock on a database item, it will prevent updates to that item.</p>
<p>But what happens if you are trying to read something that is already being updated but the transaction has not yet committed? In this instance, the write lock saves the day again.</p>
<p>Since write locks are exclusive (can’t be shared with other transactions), any transaction wanting to read the same database item will have to wait until the transaction with the write lock is committed (or aborted, if it fails). This prevents other transactions from reading uncommitted changes.</p>
<h2 id="heading-optimistic-concurrency-control">Optimistic Concurrency Control</h2>
<p>With optimistic concurrency control, transactions do not obtain locks on data when they read or write. The "Optimistic" in the name comes from assuming that conflicts are unlikely to occur, so locks are not needed. If something does go wrong though, conflicts will still be prevented and everything will be OK.</p>
<p>Unlike pessimistic concurrency control – which prevents conflicts from occurring by blocking them before they get a chance to start – optimistic concurrency control checks for conflicts at the end of a transaction.</p>
<p>With optimistic concurrency control, multiple transactions can read or update the same database item without acquiring locks. How exactly does this work?</p>
<p>Every time a transaction wants to update a database item, say a row, it will also read two additional columns added to every table by the DBMS – the timestamp and the version number. Before that transaction is committed, it checks if another transaction has made any change(s) to that row by confirming if the version number and timestamp are the same.</p>
<p>If they have changed, that means another transaction has updated that row, so the initial transaction will have to be retried.</p>
<h3 id="heading-a-simple-real-world-example-of-optimistic-concurrency-control-in-action">A Simple Real-World Example of Optimistic Concurrency Control in Action</h3>
<p>Let's illustrate how optimistic concurrency control works using a simple example involving a bank balance database table. Assume we have a table named Accounts with the following columns: AccountID, Balance, VersionNumber, and Timestamp.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc46a7d00-c1b9-46c6-80c0-0bc1a7aba9ae_2180x542.png" alt="Image" width="1456" height="362" loading="lazy"></p>
<p><em>Table showing AccountID, Balance, VersionNumber, and Timestamp columns</em></p>
<p>Two transactions, T1 and T2, intend to update the balance of account 12345 at the same time. T1 wants to withdraw $200, and T2 wants to deposit $300. At the end of these two transactions, the account balance should read $1100</p>
<p>Here are the steps of how this will work:</p>
<ol>
<li><p>Start of Transactions: T1 reads the balance, version number, and timestamp for AccountID 12345. Simultaneously, T2 reads the same row with the same balance, version number, and timestamp.</p>
</li>
<li><p>Processing: T1 calculates the new balance as $800 ($1000 - $200) but does not write it back immediately. T2 calculates the new balance as $1300 ($1000 + $300) but also waits to commit.</p>
</li>
<li><p>Attempt to Commit T1: Before committing, T1 checks the current VersionNumber and Timestamp of AccountID 12345 in the database. Since no other transaction has modified the row, T1 updates the balance to $800, increments the VersionNumber to 2, updates the Timestamp, and commits successfully.</p>
</li>
<li><p>Attempt to Commit T2: T2 attempts to commit by first verifying the VersionNumber and Timestamp. T2 finds that the VersionNumber and Timestamp have changed (now VersionNumber is 2, and Timestamp is updated), indicating another transaction (T1) has updated the row. Since the version number and timestamp have changed, T2 realises there was a conflict.</p>
</li>
<li><p>Resolution for T2: T2 must restart its transaction. It re-reads the updated balance of $800, the new VersionNumber 2, and the updated Timestamp. T2 recalculates the new balance as $1100 ($800 + $300), updates the VersionNumber to 3, updates the Timestamp, and commits successfully.</p>
</li>
</ol>
<p>Result: The Accounts table is updated sequentially and safely without any locks: After T1: $800, VersionNumber: 2. After T2: $1100, VersionNumber: 3.</p>
<h3 id="heading-benefits-and-challenges-of-optimistic-concurrency-control">Benefits and Challenges of Optimistic Concurrency Control</h3>
<p>On the positive side, avoiding locks allows for high levels of concurrency. This is particularly beneficial in read-heavy workloads where transactions are less likely to conflict, allowing the system to handle more transactions in a given period. For example, database backups and analytical queries typically used in a data warehouse.</p>
<p>But in scenarios where conflicts are frequent, the cost of repeatedly rolling back and retrying transactions can outweigh the benefits of avoiding locks, making optimistic concurrency control less efficient</p>
<h3 id="heading-how-optimistic-concurrency-controls-guarantee-the-repeatable-read-isolation-level">How Optimistic Concurrency Controls Guarantee the Repeatable Read Isolation level</h3>
<p>The repeatable read is more strict isolation level in that it has the same guarantees as read committed isolation, plus it guarantees that reads are repeatable.</p>
<p>A repeatable read guarantees that if a transaction reads a row of data, any subsequent reads of that same row of data within the same transaction will yield the same result, regardless of changes made by other transactions. This consistency is maintained throughout the duration of the transaction.</p>
<p>How can a repeatable read be achieved? Pessimistic control using a read lock can help with this, since a transaction with a read lock on a database item will prevent that item from being updated. But this can be inefficient, since a long running read transaction can block updates from happening to that database item.</p>
<p>Multi-Version Concurrency Control (MVCC) is a concurrency control method used by some DBMS to allow multiple transactions to access the same data simultaneously without locking the data. This makes it a popular choice for reducing lock contention and improving the scalability of databases.</p>
<p>MVCC achieves this by keeping multiple versions of data objects, which helps to manage different visibility levels for transactions depending on their timestamps or version numbers.</p>
<h2 id="heading-bringing-it-together">Bringing it Together</h2>
<p>A lock is a mechanism used to control access to a database item, like a row or table. In very simple terms, it is analogous to a reservation on a database item.</p>
<p>Pessimistic concurrency control assumes the worst. It assumes that conflicts are likely to happen, so locks are used to block transactions that can cause conflicts before they even get a chance to start.</p>
<p>In situations where conflicts are common, such as a write heavy application, this approach can prevent the overhead associated with frequent rollbacks and retries (which happens in optimistic concurrency control) by ensuring exclusive access to database items during transactions.</p>
<p>Optimistic concurrency control assumes the best. It assumes that conflicts are unlikely to occur, so locks are not needed to stop transactions before they start. Instead, potential conflicts are checked at the end of a transaction and if any are found, the transaction is aborted or retried.</p>
<p>Optimistic concurrency control is useful for read heavy transactions with infrequent writes, as it allows multiple transactions to proceed without the need to use a lock, which can be inefficient.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Back Up and Restore Azure SQL Databases ]]>
                </title>
                <description>
                    <![CDATA[ Microsoft's Azure provides many services via a single cloud, which lets them offer one solution for multiple corporate infrastructures. Development teams often use Azure because they value the opportunity to run SQL databases in the cloud and complet... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-back-up-and-restore-sql-azure-database/</link>
                <guid isPermaLink="false">66ba2f06d8f1b6513f67389b</guid>
                
                    <category>
                        <![CDATA[ Azure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Backup ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Alex Tray ]]>
                </dc:creator>
                <pubDate>Wed, 24 Jan 2024 22:34:09 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/12/maxresdefault.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Microsoft's Azure provides many services via a single cloud, which lets them offer one solution for multiple corporate infrastructures. Development teams often use Azure because they value the opportunity to run SQL databases in the cloud and complete simple operations via the Azure portal. </p>
<p>But you'll need to have a way to back up your data, as it's crucial to ensuring the functionality of the production site and the stability of everyday workflows. So creating Azure SQL backups can help you and your team avoid data loss emergencies and have the shortest possible downtime while maintaining control over the <a target="_blank" href="https://www.hostpapa.com/blog/technology/what-is-an-it-infrastructure/">infrastructure</a>.</p>
<p>Another reason to have a current Azure database backup is Microsoft’s policy. Microsoft uses the shared responsibility model, which makes the user responsible for data integrity and recovery while Microsoft only ensures the availability of its services. Microsoft directly recommends using third-party solutions to create database backups. </p>
<p>In case you run a local SQL Server, you'll need to prepare for the possibility of hardware failures that may result in data loss and downtime. An SQL database on Azure helps mitigate that risk, although it's still prone to human errors or cloud-specific threats like malware. </p>
<p>These and other threats make enabling Azure SQL database backups necessary for any organization using Microsoft’s service to manage and process data. </p>
<p>In this tutorial, you'll learn about backing up Azure databases and restoring your data on demand with native instruments provided by Microsoft, including methods like: </p>
<ul>
<li>Built-in Azure database backup functionality</li>
<li>Cloud archiving</li>
<li>Secondary database and table management</li>
<li>Linked server</li>
<li>Stretch Database</li>
</ul>
<h2 id="heading-why-backup-your-sql-azure-database">Why Backup Your SQL Azure Database?</h2>
<p>Although I covered this briefly in the intro, there are many reasons to back up your SQL Azure database data. </p>
<h3 id="heading-disaster-recovery">Disaster Recovery</h3>
<p>Data centers can be damaged or destroyed by planned cyberattacks, random malware infiltration (<a target="_blank" href="https://www.nakivo.com/blog/how-to-protect-against-ransomware-attacks/">check out this article</a> to discover more on ransomware protection), and natural disasters like floods or hurricanes, among others. Backups can be used to swiftly recover data and restore operations after various disaster cases.</p>
<h3 id="heading-data-loss-prevention">Data Loss Prevention</h3>
<p>Data corruption, hardware failure, and accidental or malicious deletion lead to data loss and can threaten an organization. Backup workflows set up to run regularly mean you can quickly recover the data that was lost or corrupted.</p>
<h3 id="heading-compliance-and-regulations">Compliance and Regulations</h3>
<p>Compliance requirements and legislative regulations can be severe regardless of your organization’s industry. Mostly, laws require you to keep up with security and perform regular backups for compliance.</p>
<h3 id="heading-testing-and-development">Testing and Development</h3>
<p>You can use backups to create Azure database copies for development, troubleshooting, or testing. Thus, you can fix, develop, or improve your organization’s workflows without involving the production environment.</p>
<h2 id="heading-how-to-back-up-your-azure-sql-database">How to Back Up Your Azure SQL Database</h2>
<p>Backing up your Azure SQL database can be challenging if you go through the process without preparation. So that's why I wrote this guide – to help you be prepared. Here's what we'll cover in the following sections:</p>
<ul>
<li>Requirements for SQL Azure database backup</li>
<li>How to configure database backups in Azure with native tools</li>
<li>Cloud archiving</li>
<li>Backup verification and data restoration</li>
</ul>
<h3 id="heading-sql-azure-database-backup-requirements">SQL Azure Database Backup Requirements</h3>
<p>Before backing up your SQL Azure databases, you need to create and configure Azure storage. Before you do that, you'll need to go through the following steps:</p>
<p>First, open the Azure management portal and find <strong>Create a Resource</strong>.</p>
<p>Then, go to <strong>Storage</strong> &gt; <strong>Storage account</strong>. Provide the information, including the location and names of a storage account and resource group according to your preferences. After you enter the information, hit <strong>Next</strong>.</p>
<p><img src="https://lh7-us.googleusercontent.com/TbuVyIRHXKkKd1__mMSo8RJTktZVnJjK2r8ijtY1h5gvlY5KqkRE8NPsej18m-A1-p3UwF-YO0W0p9AzJa8AW7TwR1yXp531y7qXrm84hJQIuTIKUMwhbnU7WAiUoGRPIdL_SrQCv0nxav4RnCu389o" alt="Image" width="800" height="500" loading="lazy">
<em>Storage account config</em></p>
<p>Then go to the advanced section for additional settings. The optimal choice is to set _"_Secure transfer required" as <strong>Enabled</strong> and "Allow access" from <strong>All</strong> networks. For more resilience in case of human error, you can set "Blob soft delete" as <strong>Enabled</strong>. With that setting, you can quickly correct accidental deletions in the storage account.</p>
<p>After that, specify the tags you need to simplify navigating through your infrastructure.</p>
<p><img src="https://lh7-us.googleusercontent.com/BTXJpa8dz9wKa7p9pQm84fmtqtwD5WEuRB9yh2_Htpa-pF86Zf70CuKP7j32uPR56igplljn6fehCuJEgnMkiCiAcZPZVU_FNEL2JZcrtVjunthzLKQOWp9wbtXLLLKMgYTerYNJpsiQxZjJcMlr05I" alt="Image" width="1000" height="563" loading="lazy">
<em>Azure backup storage tags</em></p>
<p>Check the settings once more. If everything is configured correctly, hit <strong>Create</strong>. Your new storage account is now created.</p>
<p>Once the storage volume is created, it's time to configure a backup data storage container. </p>
<p>Go to the storage account, find <strong>Containers</strong>, then hit the <strong>+ Container</strong> tab there. After that, specify a name for the new container and switch Public access level to <strong>Private (no anonymous access)</strong>.</p>
<p><img src="https://lh7-us.googleusercontent.com/eGTyBc9-uiRO52QsQ0pGzlWPAZlvyMR0miExCMX-Pck9yPQvUlwKqa0_N-zWc908TzHONdzLC2Kv8ACU5UHJjuJ8G6kBOmgxONkLN5LE33ItBsKOx5XdIKtMg8oYDY6eKrdFrZ0bhuOD535QALtqxMU" alt="Image" width="1600" height="955" loading="lazy">
<em>Container Azure storage account</em></p>
<p>You can then use the container as a backup storage (.bak files will be stored there in that case).</p>
<h2 id="heading-azure-database-backup-configuration">Azure Database Backup Configuration</h2>
<p>Now, everything is set up for you to back up your SQL Azure database. Do the following to create a database backup:</p>
<p>First, go to <strong>SQL Management Studio</strong>, and establish a connection with the SQL server. After that, right-click the database that should be backed up. The context menu appears, so go to <strong>Tasks</strong> there. Then hit <strong>Back Up…</strong>. </p>
<p><img src="https://lh7-us.googleusercontent.com/l5g6ajoZ6ZuBObluCiG7mra9pz9BPgP-iOCAoGh36SY5zfg1yv300oQQ1cvgrVFNL75Nu7roFIVp2BfPze3ag5nTzL1NQYiO_fhUokWSd9fVms1SDcoP5pJ7a4wWdB3fQJWeKbrNIK_-vo2-hiXTDl0" alt="Image" width="1348" height="720" loading="lazy">
<em>SQL server tasks backup</em></p>
<p>Then find the Destination tab, and set <strong>Back up to line to URL</strong> there. After that, hit <strong>New container</strong>.</p>
<p>Next, sign in to Azure. Pick the container you created before. Provide your credentials, then hit <strong>OK</strong>.</p>
<p>You’ll see a message asking you to sign in to Azure subscription. Then, choose the container and hit <strong>OK</strong>.</p>
<p>Now, you'll see the configured backup destination URL listed. To start the workflow to back up your Azure data, hit <strong>OK</strong> once again.</p>
<p>When your SQL Azure database backup is completed, the message shows up: "<em>The backup of database ‘your database name’ completed successfully</em>."</p>
<p>The backup file in the target container should now be visible from the Azure portal.</p>
<p>Keep in mind that, when uploading backups to any cloud storage, you may face issues if your network connection is not fast enough. </p>
<p>In case that’s true for you, you can reorganize your backup workflows: send backup data to a physical storage drive first, and then send another copy to the cloud. Thus, you can prevent operational challenges that might appear due to network bandwidth deficiency.</p>
<h2 id="heading-cloud-archiving-for-azure-database-backups">Cloud Archiving for Azure Database Backups</h2>
<p>Databases tend to grow in volume as the organization grows. This means that the storage space required to fit the data and that data's backup increases significantly. Also, the original data volume prolongs the duration of full backup workflows, posing another challenge. </p>
<p>Of course, the first way to get more storage space is to revise your data regularly and erase records that are irrelevant, outdated, or unnecessary otherwise. Still, it's sometimes difficult to determine if data will be or become unnecessary or irrelevant, especially when dealing with issues of compliance. </p>
<p>To keep your organization compliant in any case, data archiving can help you solve two problems at once: you can ensure data accessibility on one hand, and save storage space on the other hand.</p>
<p>To archive your SQL database in the cloud, you should first save that database copy to an Azure blob container. Then, to move a newly created blob to the archive tier in the Azure portal, do the following: </p>
<ol>
<li>Go to the required container where the SQL database is stored.</li>
<li>Choose the blob that you need to move.</li>
<li>Hit <strong>Change tier</strong>.</li>
</ol>
<p><img src="https://lh7-us.googleusercontent.com/p41GC9ys42mQBQGWW1jqcR2xCfACpCYF1MpGG7Qx6EdqzjDSK6xnuqlPRCtDuhEmH_-8E6Lz2gY8H3h1CoZ4_jpScQWUxB-21GXOnuDEBSHVJiGa1zBiHu4JJP2Xntq1fpbPLjbb1-APOTJMO2sdBMk" alt="Image" width="1281" height="629" loading="lazy">
<em>Azure blob container change tier</em></p>
<ol start="4">
<li>In the <strong>Access tier</strong> dropdown menu, choose <strong>Archive</strong>.</li>
</ol>
<p><img src="https://lh7-us.googleusercontent.com/qlyKGop3uj6kfi71fSpVsqKkhf8vc1TiQRyoeHEiwjKdg1i3Dsz_LXLHcD5q-qR77utIPUbkLyWU7Xzn7ehl3Z1IWUQZjy0LndXLEcDA1PRZj4ufO8QGR0GCmEDGqoWQ6paFwo8pn0VbUH8RWjlRzLU" alt="Image" width="432" height="283" loading="lazy">
<em>Azure blob change tier</em></p>
<ol start="5">
<li>Hit <strong>Save</strong>.</li>
</ol>
<p>Additionally, the Archive storage tier is the most affordable one in Azure, meaning that you can reduce your database data TCO with it.</p>
<h3 id="heading-secondary-database-and-table-management">Secondary Database and Table Management</h3>
<p>There exist several workflows that can help you set up Azure database backup archiving for your organization. When you need the data to stay in the initial database, for instance, creating a separate table and moving that data there can be your choice. However, the filegroup of that table should stay apart from the main database and be moved to a separate disk whenever possible. </p>
<p>Most probably, you’ll want to let users access the data you send to a separate table. To make that happen, you can create a view merging the relevant tables and redirect the requests to that view, not to the original table. Doing things that way, you can keep the data accessible while dealing with maintenance faster.</p>
<h3 id="heading-sql-server-linking">SQL Server Linking</h3>
<p>If you can’t move the data to another database for internal reasons such as special Azure backup policies, you can consider maintaining your primary database accordingly. </p>
<p>Here, the outcome is likely to be that of the previous case, but you need to link the SQL servers or configure apps so they can send direct requests to your second server. </p>
<p>The downside here is that your SQL database, which was supposed to be a backup one, becomes a production database and gains appropriate importance for an organization.</p>
<p>There are two ways to create linked servers via SQL Server Management Studio (SSMS): </p>
<ul>
<li><strong>sp_addlinkedserver</strong> (Transact-SQL) system stored procedure that creates a linked server</li>
<li><strong>SSMS GUI</strong></li>
</ul>
<p>After you've ensured that you have appropriate access rights on both server instances you need to link, the network is configured appropriately to access them, and SSMS is installed, you'll need to go through the following steps:</p>
<p>First, open SSMS.</p>
<p><img src="https://lh7-us.googleusercontent.com/6NS8wE2UmtV5Bs3-loE7kIASfehk4-hSaPP5y7Wm1oEVIUFDCPyxD_f1rLQzxsJVdCGaFJwcRHqKVrnypgETOSohLP5hQK50m4tj4pBZBIx6oTUj8WOJbcttfhy0IybUyC_CrJCyK8saEPnchKInp7g" alt="Image" width="1499" height="1087" loading="lazy">
<em>Microsoft SSMS</em></p>
<p>Connect to the instance where you need to establish a linked server. Then find <strong>Object Explorer &gt; Server Objects</strong>, then right-click <strong>Linked Servers</strong>.</p>
<p>Pick <strong>New Linked Server</strong> from the dropdown:</p>
<p><img src="https://lh7-us.googleusercontent.com/tD5YO2e1RtfLUmtBdRFNfiHQSyaxnQml9lBGnRPPzuNrW4Fcu-3alTg4N3-mdR-oQxcaUyMpyqp36l7r3aTfg29RzT6Jgx0Nb1eT2T-y-zotl1RujRUIC4gSwE25aslpfMJJUvNW4MMivP4BstyQu4o" alt="Image" width="400" height="340" loading="lazy">
<em>New linked server SSMS</em></p>
<p>Then configure the server properties, including name, server type, provider and product name:</p>
<p><img src="https://lh7-us.googleusercontent.com/y-WSzJni8uyKBAcJywPqk-iufIeJ_4TTs1rf3e_9RYhj1Kt8nUsZfad9Vekec4yL6eFCX8doLR4Qr7iA6X3p78jnRfIs3AYlHMn1GOhR8Ya29CW5X9DIU-nbj_jDaTwAvwEJXNjr7npd5THmnD7Iv3A" alt="Image" width="400" height="378" loading="lazy">
<em>Linked server configuration SSMS</em></p>
<p>Then you'll just need to complete the security configuration, set up the server options, and complete connection testing.</p>
<h3 id="heading-original-data-deletion">Original Data Deletion</h3>
<p>When you don’t need 24/7 data availability but need the data stored due to internal policies or compliance requirements, you can choose what's probably the simplest solution to increase storage space efficiency. Just back up the data that can stay unavailable and then delete the originals from the main database. Accessing any records you may need will still be possible via the backup.</p>
<h3 id="heading-stretch-database">Stretch Database</h3>
<p>Aiming to make data management of organizations’ databases simpler, Microsoft implemented a Stretch Database feature in SQL Server 2016. With this feature, you can get an SQL backup to Azure after you send the data from the hosted database to an Azure SQL database. The method enables you to increase overall infrastructure cost-efficiency by simplifying backup workflows.</p>
<p>To enable this workflow in your environment, develop the policy specifying the data on a hosted server to send to Azure. You don’t need to introduce any changes in applications that use the production database: SQL Server can independently get the records from the Azure SQL Database.</p>
<h3 id="heading-azure-database-backups-verification-and-restoration">Azure Database Backups Verification and Restoration</h3>
<p>During an SQL Azure database backup, you can choose to create such backups <strong>WITH CHECKSUMS</strong> or without them. When the workflow is complete, I recommend you use the following command: <strong><code>RESTORE VERIFYONLY</code></strong>. This command enables you to check the recoverability of backup files.  </p>
<p>To access the data, you can restore records from a backup to a different database. With Azure Automation scripts on backups, you can accelerate the restoration process, thus minimizing downtime and increasing the overall resilience of your Azure infrastructure. </p>
<p>You need to follow only a few steps to restore an Azure SQL database to a required recovery point from a backup. Still, keep in mind that your subscription can define the available retention period which can vary from 7 to 35 days. A native tool for backup restoration to SQL servers is Server Management Studio.</p>
<h2 id="heading-to-conclude">To Conclude</h2>
<p>The critical nature of Azure SQL database data makes Azure SQL backups obligatory for any organization that uses this Microsoft solution. In this guide, we reviewed the process of creating SQL Azure database backup using native Microsoft tools. </p>
<p>These tools provide data backup, backup verification, and recovery functionality along with some automation. </p>
<p>You can also implement a specialized all-in-one data protection solution, such as <a target="_blank" href="https://www.nakivo.com/backup-to-azure-blob/">NAKIVO</a>, the company where I work. It can help you make your data backup workflows more efficient.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ ACID Databases – Atomicity, Consistency, Isolation & Durability Explained ]]>
                </title>
                <description>
                    <![CDATA[ ACID stands for Atomicity, Consistency, Isolation and Durability. These are four key properties that most database management systems (DBMS) offer as guarantees when handling transactions. Most popular DBMS like MySQL, PostgresSQL and Oracle have ACI... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/acid-databases-explained/</link>
                <guid isPermaLink="false">66d45e01246e57ac83a2c725</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Daniel Adetunji ]]>
                </dc:creator>
                <pubDate>Wed, 17 Jan 2024 17:45:53 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/cover-fcc.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>ACID stands for Atomicity, Consistency, Isolation and Durability. These are four key properties that most database management systems (DBMS) offer as guarantees when handling transactions.</p>
<p>Most popular DBMS like <a target="_blank" href="https://dev.mysql.com/doc/refman/8.0/en/mysql-acid.html">MySQL</a>, <a target="_blank" href="https://www.postgresql.org/about/">PostgresSQL</a> and <a target="_blank" href="https://docs.oracle.com/cd/F51125_01/docs.85/SDS%20PI/acid-compliant-transactions.html#GUID-ECB79D66-46DE-4F48-93DC-8677E7BB44EF">Oracle</a> have ACID guarantees out of the box. Others have partial ACID guarantees like <a target="_blank" href="https://www.oreilly.com/library/view/redis-cookbook/9781449311353/ch01.html#:~:text=Redis%20provides%20partial%20ACID%20compliance,also%20be%20a%20key%20factor.">Redis</a>, DynamoDB, and Cassandra. The trend, however, seems to be that more and more DBMS are offering ACID compliance.</p>
<p>It is important to note that while a lot of DBMS may say they are ACID compliant, the implementation of this compliance can vary.</p>
<p>So, for example, if isolation is a key property that you need for an application you are building, you need to understand how exactly your chosen DBMS implements isolation.</p>
<p>This article will explain what transactions are, and go through, in detail, what atomicity, consistency, isolation and durability mean, using analogies and real world examples.</p>
<h3 id="heading-table-of-contents">Table of Contents:</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-what-are-transactions">What are Transactions?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-does-atomicity-mean">What Does Atomicity Mean?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-does-consistency-mean">What Does Consistency Mean?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-does-isolation-mean">What Does Isolation Mean?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-does-durability-mean">What Does Durability Mean?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-bringing-it-together">Bringing it Together</a></p>
</li>
</ol>
<h2 id="heading-what-are-transactions">What are Transactions?</h2>
<p>Lots of things can go wrong when using a database:</p>
<ul>
<li><p>the database hardware or software can fail</p>
</li>
<li><p>the application calling the database can fail mid-operation</p>
</li>
<li><p>the network can be flooded with more traffic can it can handle (rendering it inoperable)</p>
</li>
<li><p>several clients can make writes at the same time that overwrite the other’s changes</p>
</li>
<li><p>clients can read phantom data that should not be in the database</p>
</li>
</ul>
<p>And so on – this is in no way an exhaustive list of things that can go wrong.</p>
<p>Since things can fail in more ways than we can possibly anticipate, trying to prevent every possible failure can become unnecessarily expensive and complicated. Instead, it is better to design a system that can continue to operate in spite of a failure. Transactions allow us to do this.</p>
<p>Transactions serve a single purpose: they make sure a system is <a target="_blank" href="https://lightcloud.substack.com/i/59017006/fault-tolerance"><strong>fault tolerant</strong></a><strong>.</strong> If a failure in a system occurs, can the system continue to operate without complete catastrophe? Phrased differently, can the system tolerate faults? An answer of ‘yes’ to this question means that such a system is fault tolerant.</p>
<p>So, what exactly is a transaction?</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F718d52b8-c016-4746-a1eb-73b6aac6d5fa_636x960.png" alt="Image" width="636" height="960" loading="lazy"></p>
<p><em>Not this kind of transaction</em></p>
<p>A transaction is an <a target="_blank" href="https://lightcloud.substack.com/p/cloud-computing-abstractions-explained">abstraction</a>. It is a collection of operations (reads and writes) that are treated as a single logical operation.</p>
<p>Imagine you want to buy a single book from an online store, say amazon.com. The steps below show a simplified view of what needs to happen:</p>
<ol>
<li><p>First, you select the book, which adds the item to your basket.</p>
</li>
<li><p>The inventory quantity of the book is checked to ensure it is valid (that is, the inventory value for the title you are buying needs to be greater than 0).</p>
</li>
<li><p>You click ‘buy’, which updates Amazon’s inventory for the book and decreases it by 1 (since you are buying a single book).</p>
</li>
<li><p>Also, your bank account balance is updated to account for the cost of the book.</p>
</li>
</ol>
<p>A transaction ensures that all operations related to the purchase are treated as a single operation. If any part of the transaction fails, the entire transaction is rolled back, leaving the database in a state as if the customer had never attempted the purchase, thus maintaining the integrity of the data.</p>
<p>The transaction is committed when all the operations within the transaction are successfully completed and their results are permanently recorded. This permanence is typically achieved by writing the changes to the database's storage, which could be on disk for traditional databases or in memory for in-memory databases like Redis.</p>
<p>By treating all of these different operations as a single logical operation, the database is able to offer some guarantees as to how it can be fault tolerant. These guarantees are <strong>atomicity</strong>, <strong>consistency</strong>, <strong>isolation</strong> and <strong>durability</strong>.</p>
<h2 id="heading-what-does-atomicity-mean">What Does Atomicity Mean?</h2>
<p>Atomicity simply means that all queries in a transaction must succeed for the transaction to succeed. If one query fails, the entire transaction fails.</p>
<h3 id="heading-an-atomic-restaurant">An Atomic Restaurant</h3>
<p>Imagine using a self-service machine at a fast-food restaurant. The transaction in this case is ordering food, and consists of two separate operations:</p>
<ol>
<li><p>Select food</p>
</li>
<li><p>Make payment</p>
</li>
</ol>
<p>Both of these must succeed for the transaction to succeed. If either fails, the transaction fails.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb52901d6-a9e0-43a7-a198-d56ca2a82219_544x886.png" alt="Image" width="544" height="886" loading="lazy"></p>
<p><em>Customer making an order in an "atomic" restaurant</em></p>
<p>You select your burger, fries, and a drink from the touchscreen menu. The machine prompts you to pay, and only after your payment is processed successfully, it sends your order to the kitchen. Moments later, your entire order is ready, and you pick it up from the counter.</p>
<p>This is an atomic operation: the transaction (ordering food) is either entirely completed (if you select your food item and make a payment) or not completed at all.</p>
<p>Either part of the transaction failing means the entire transaction will fail. If your payment fails, the machine won't process any part of the order, so the transaction fails. If you make a payment without selecting a food item, the transaction also fails, as there is nothing for the kitchen to prepare.</p>
<h3 id="heading-a-non-atomic-restaurant">A Non-Atomic Restaurant</h3>
<p>Now consider the alternative, a traditional sit-down restaurant where you order several dishes. As each dish is prepared, it is brought to your table.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F045c9ec7-dbb9-45b7-ad4d-357f7b7cc37c_888x1026.png" alt="Image" width="888" height="1026" loading="lazy"></p>
<p><em>customer making an order in a "non-atomic" restaurant</em></p>
<p>Again, the transaction is ordering food, and consists of two separate operations:</p>
<ol>
<li><p>Select food</p>
</li>
<li><p>Make payment</p>
</li>
</ol>
<p>In this non-atomic restaurant, failure to make a payment does not stop the transaction from completing, since you pay after you have finished your meal. Partial failures do not cause a transaction to fail.</p>
<p>This creates a risk for the restaurant. Customers that choose to dine and dash can order food to their heart’s delight and then simply leave without paying, causing a financial loss for the restaurant.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4136ceee-82b9-4cf0-b0de-5a0af3f1aa71_2218x1278.png" alt="Image" width="1456" height="839" loading="lazy"></p>
<p><em>non-atomic restaurants are at risk of customers doing a dine and dash</em></p>
<h3 id="heading-atomic-transactions">Atomic Transactions</h3>
<p>If several SQL queries are grouped together in a transaction, atomicity is a guarantee that, should any of the queries fail for any reason (hardware, application or networking problems) then the transaction is aborted and the database returns to its previous state, as if nothing had happened.</p>
<p>Without atomicity, if a failure occurs while some queries are running, it is difficult to know which queries have been committed (that is, completed) and which have not. Running the queries again after a failure can compound the problem, since you risk introducing incorrect data to the database by re-running queries that previously succeeded.</p>
<p>Atomic transactions prevent such uncertainty, since you know that if the previous transaction failed, it failed in its entirety, and you can simply retry without worrying about introducing inconsistent data.</p>
<h2 id="heading-what-does-consistency-mean">What Does Consistency Mean?</h2>
<p>Consistency can mean different things in cloud/software engineering, depending on the context. In the case of ACID, the “C” was most likely added to make the acronym work.</p>
<p>Consistency in the context of ACID means <em>consistency in data</em>, which is defined by the creator of the database. The technical term for consistency in data is called referential integrity. Referential integrity is a method of ensuring that relationships between tables remain consistent. It's usually enforced through the use of foreign keys.</p>
<p>To understand referential integrity, consider the following.</p>
<p>Imagine a library system with two types of cards: a book card and a borrower's card.</p>
<ul>
<li><p>The book card lists all the books available in the library.</p>
</li>
<li><p>The borrower's card tracks which books are borrowed by which members.</p>
</li>
</ul>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82ea7c95-f55f-4cb6-86ea-47c11399b5c7_2324x1316.png" alt="Image" width="1456" height="824" loading="lazy"></p>
<p><em>A book card and borrower card for a library</em></p>
<p>The rule of the library is that a book can only be listed on a borrower's card if it exists on a book card. This is referential integrity. If someone tries to list a book on a borrower's card that isn't on the book card (that is a book that doesn’t exist in the library), the system will not allow it.</p>
<p>While atomicity, isolation and durability are properties intrinsic to the database itself, consistency in data, or referential integrity, is not a property intrinsic to the database.</p>
<p>Consistency is defined by the creator of the database. The application calling the database relies on the atomicity and isolation properties of the database to maintain that consistency.</p>
<h2 id="heading-what-does-isolation-mean">What Does Isolation Mean?</h2>
<p>Isolation is a guarantee that concurrently running transactions should not interfere with each other. Concurrency here refers to two or more transactions trying to modify or read the same database record(s) at the same time.</p>
<p>There are three levels of transaction isolation. I'll just explain the two main ones below, arranged in order from the least strict to most strict.</p>
<h3 id="heading-read-committed">Read Committed</h3>
<p>This gives two guarantees. It prevents dirty reads and dirty writes.</p>
<p><strong>No Dirty Reads</strong>: Reading data from another transaction that has not yet been committed is called a dirty read. With the read committed isolation level, you will only see data that has been committed by another transaction.</p>
<p><strong>No Dirty Writes</strong>: Overwriting data that has already been written by another transaction but not yet committed is called a dirty write.</p>
<p>To understand how read committed isolation works, consider the following example.</p>
<p>Imagine a fast-food restaurant with only one last special burger available, and two hungry customers, Marie and Marko, are trying to buy it simultaneously.</p>
<p><img src="https://substackcdn.com/image/fetch/w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fec0fd185-63c9-4821-bcac-b0140f2f4183_2360x1322.png" alt="Image" width="1456" height="816" loading="lazy"></p>
<p><em>Two customers ordering a burger at the same time</em></p>
<ol>
<li><p>Marie checks the availability of burgers and sees the last one available. Unknown to her, Marko’s order is being processed but hasn't been finalised in the system, as he has not paid. Since his order has not yet been finalised, Marie is not aware that his order conflicts with her own. This is similar to a transaction reading the most recently committed data, where it does not see uncommitted changes (like Marko's pending order).</p>
</li>
<li><p>Marie places an order based on this incomplete information, thinking a burger is available.</p>
</li>
<li><p>Once Marko pays, the system updates to show that there are no burgers left. This is similar to a transaction being committed</p>
</li>
<li><p>Marie’s order will have to be aborted since there are no burgers left.</p>
</li>
</ol>
<p>The key point here is step #3. What if Marko’s payment failed at this stage? Then the transaction will not be committed and there would still be a burger available for Marie.</p>
<p>In this example, read committed isolation ensures that Marie is not prematurely excluded from buying the burger just because someone else said they wanted it. Only committed transactions can be read. Therefore, the burger is available to be ordered as long as no one has paid for it.</p>
<h3 id="heading-repeatable-read">Repeatable Read</h3>
<p>The repeatable read is a more strict isolation level, in that it has the same guarantees as read committed isolation – plus it guarantees that reads are repeatable.</p>
<p>A repeatable read guarantees that if a transaction reads a row of data, any subsequent reads of that same row of data within the same transaction will yield the same result, regardless of changes made by other transactions. This consistency is maintained throughout the duration of the transaction.</p>
<p>When a transaction reads the same data twice, but sees a different value in each read because a committed transaction has updated the value between the two reads, this is called a fuzzy read. The repeatable read isolation level prevents fuzzy reads.</p>
<p>Fuzzy reads are neither inherently good nor bad. It all depends on what you are trying to achieve.</p>
<p>Fuzzy reads are bad for long-running, read-only transactions, since new writes are likely to occur during the transaction and this can cause inconsistencies in the data. Examples of long running, read-only transactions are a database backup and analytical queries typically used in a data warehouse.</p>
<p>Repeatable reads are usually implemented by the DBMS by reading from a snapshot of the database which remains unchanged for the duration of the transaction, thereby ignoring any new committed writes in that period.</p>
<h2 id="heading-what-does-durability-mean">What Does Durability Mean?</h2>
<p>Durability is a guarantee that changes made by a committed transaction must not be lost. All committed transactions must be persisted on durable, non-volatile storage, that is on disk. This ensures that any committed transactions are protected even if the database crashes.</p>
<p>Naturally, durability cannot protect against destruction of the disk which stores the data. Additional redundancy can be added by having backups of your database stored separately from the original.</p>
<h2 id="heading-bringing-it-together">Bringing it Together</h2>
<p>ACID (Atomicity, Consistency, Isolation, and Durability) provides a set of guarantees when working with a DBMS. While most relational DBMS are ACID compliant, the implementation of this compliance can vary.</p>
<p>Atomicity ensures that all parts of a transaction are completed or none at all. Partial failures are not allowed.</p>
<p>Consistency, or referential integrity, ensures that data remains accurate and reliable, adhering to predefined rules. Unlike the other priorities, consistency is not intrinsic to the DBMS itself. Instead, the application calling the database relies on the atomicity and isolation properties of the database to maintain consistency.</p>
<p>Isolation is a guarantee that concurrently running transactions should not interfere with each other. This is arguably the most important property because a DBMS can often have different default isolation levels, which may need to be changed based on what is needed for your application.</p>
<p>Finally, durability is a guarantee that changes made by a committed transaction must not be lost.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ The SQL Handbook – A Free Course for Web Developers ]]>
                </title>
                <description>
                    <![CDATA[ SQL is everywhere these days. Whether you're learning backend development, data engineering, DevOps, or data science, SQL is a skill you'll want in your toolbelt. This a free and open text-based handbook. If you want to get started, just scroll down ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/a-beginners-guide-to-sql/</link>
                <guid isPermaLink="false">66b9e9e18ff373c48b152943</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Lane Wagner ]]>
                </dc:creator>
                <pubDate>Tue, 05 Sep 2023 13:57:37 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/The-SQL-Handbook-Cover.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>SQL is everywhere these days. Whether you're <a target="_blank" href="https://www.boot.dev/">learning backend development</a>, data engineering, DevOps, or data science, SQL is a skill you'll want in your toolbelt.</p>
<p>This a free and open text-based handbook. If you want to get started, just scroll down and start reading. That said, there are two other options for following along:</p>
<ol>
<li>Try the interactive version of this <a target="_blank" href="https://boot.dev/learn/learn-gsql">SQL course</a> on <a target="_blank" href="https://boot.dev/">Boot.dev</a>, complete with coding challenges and projects</li>
<li>Watch the video walkthrough of this course on FreeCodeCamp's YouTube channel (embedded below):</li>
</ol>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/KBDSJU3cGkc" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-chapter-1-introduction">Chapter 1: Introduction</a></li>
<li><a class="post-section-overview" href="#heading-chapter-2-sql-tables">Chapter 2: SQL Tables</a></li>
<li><a class="post-section-overview" href="#heading-chapter-3-constraints">Chapter 3: Constraints</a></li>
<li><a class="post-section-overview" href="#heading-chapter-4-crud-operations-in-sql">Chapter 4: CRUD Operations</a></li>
<li><a class="post-section-overview" href="#heading-chapter-5-basic-sql-queries">Chapter 5: Basic SQL Queries</a></li>
<li><a class="post-section-overview" href="#heading-chapter-6-how-to-structure-return-data-in-sql">Chapter 6: How to Structure Return Data in SQL</a></li>
<li><a class="post-section-overview" href="#heading-chapter-7-how-to-perform-aggregations-in-sql">Chapter 7: How to Perform Aggregations in SQL</a></li>
<li><a class="post-section-overview" href="#heading-chapter-8-sql-subqueries">Chapter 8: SQL Subqueries</a></li>
<li><a class="post-section-overview" href="#heading-chapter-9-database-normalization">Chapter 9: Database Normalization</a></li>
<li><a class="post-section-overview" href="#heading-chapter-10-how-to-join-tables-in-sql">Chapter 10: How to Join Tables in SQL</a></li>
<li><a class="post-section-overview" href="#heading-chapter-11-database-performance">Chapter 11: Database Performance</a></li>
</ul>
<h2 id="heading-chapter-1-introduction">Chapter 1: Introduction</h2>
<p>Structured Query Language, or <a target="_blank" href="https://www.freecodecamp.org/news/what-is-sql-database-definition-for-beginners/">SQL</a>, is the primary programming language used to manage and interact with <a target="_blank" href="https://cloud.google.com/learn/what-is-a-relational-database">relational databases</a>. SQL can perform various operations such as creating, updating, reading, and deleting records within a database.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/pYKirBUnr-8" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h3 id="heading-what-is-a-sql-select-statement">What is a SQL Select Statement?</h3>
<p>Let's write our own SQL statement from scratch. A <code>SELECT</code> statement is the most common operation in SQL – often called a "query". <code>SELECT</code> retrieves data from one or more tables. Standard <code>SELECT</code> statements do <em>not</em> alter the state of the database.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">from</span> <span class="hljs-keyword">users</span>;
</code></pre>
<h4 id="heading-how-to-select-a-single-field">How to select a single field</h4>
<p>A <code>SELECT</code> statement begins with the keyword <code>SELECT</code> followed by the fields you want to retrieve.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">id</span> <span class="hljs-keyword">from</span> <span class="hljs-keyword">users</span>;
</code></pre>
<h4 id="heading-how-to-select-multiple-fields">How to select multiple fields</h4>
<p>If you want to select more than one field, you can specify multiple fields separated by commas like this:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">id</span>, <span class="hljs-keyword">name</span> <span class="hljs-keyword">from</span> <span class="hljs-keyword">users</span>;
</code></pre>
<h4 id="heading-how-to-select-all-fields">How to select all fields</h4>
<p>If you want to select <em>every</em> field in a record, you can use the shorthand <code>*</code> syntax.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">from</span> <span class="hljs-keyword">users</span>;
</code></pre>
<p>After specifying fields, you need to indicate which table you want to pull the records from using the <code>from</code> statement followed by the name of the table. </p>
<p>We'll talk more about tables later, but for now, you can think about them like structs or objects. For example, the <code>users</code> table might have 3 fields:</p>
<ul>
<li><code>id</code></li>
<li><code>name</code></li>
<li><code>balance</code></li>
</ul>
<p>And finally, <em>all</em> statements end with a semi-colon <code>;</code>.</p>
<h3 id="heading-which-databases-use-sql">Which Databases Use SQL?</h3>
<p>SQL is just a query language. You typically use it to interact with a specific database technology. For example: </p>
<ul>
<li><a target="_blank" href="https://www.sqlite.org/index.html">SQLite</a></li>
<li><a target="_blank" href="https://www.postgresql.org/">PostgreSQL</a></li>
<li><a target="_blank" href="https://www.mysql.com/">MySQL</a></li>
<li><a target="_blank" href="https://www.cockroachlabs.com/">CockroachDB</a></li>
<li><a target="_blank" href="https://www.oracle.com/database/">Oracle</a></li>
</ul>
<p>And others.</p>
<p>Although many different databases use the SQL <em>language</em>, most of them will have their own <em>dialect</em>. It's critical to understand that not all databases are created equal. Just because one SQL-compatible database does things a certain way, doesn't mean every SQL-compatible database will follow those exact same patterns.</p>
<h4 id="heading-were-using-sqlite">We're using SQLite</h4>
<p>In this course, we'll be using <a target="_blank" href="https://www.sqlite.org/index.html">SQLite</a> specifically. SQLite is great for embedded projects, web browsers, and toy projects. It's lightweight, but has limited functionality compared to the likes of PostgreSQL or MySQL – two of the more common production SQL technologies.</p>
<p>And I'll make sure to point out to you whenever some functionality we're working with is unique to SQLite.</p>
<h2 id="heading-nosql-vs-sql">NoSQL vs SQL</h2>
<p>When talking about SQL databases, we also have to mention the elephant in the room: <a target="_blank" href="https://en.wikipedia.org/wiki/NoSQL">NoSQL</a>.</p>
<p>To put it simply, a NoSQL database is a database that does not use SQL (Structured Query Language). Each NoSQL typically has its own way of writing and executing queries. For example, <a target="_blank" href="https://www.mongodb.com/">MongoDB</a> uses MQL (MongoDB Query Language) and <a target="_blank" href="https://www.elastic.co/">ElasticSearch</a> simply has a JSON API.</p>
<p>While most relational databases are fairly similar, NoSQL databases tend to be fairly unique and are used for more niche purposes. Some of the main differences between a SQL and NoSQL database are:</p>
<ol>
<li>NoSQL databases are usually non-relational, SQL databases are usually <a target="_blank" href="https://cloud.google.com/learn/what-is-a-relational-database">relational</a> (we'll talk more about what this means later).</li>
<li>SQL databases usually have a defined schema, NoSQL databases usually have dynamic schema.</li>
<li>SQL databases are table-based, NoSQL databases have a variety of different storage methods, such as document, key-value, graph, wide-column, and more.</li>
</ol>
<h3 id="heading-types-of-nosql-databases">Types of NoSQL databases</h3>
<ul>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Document-oriented_database">Document Database</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Key%E2%80%93value_database">Key-Value Store</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Wide-column_store">Wide-Column</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Graph_database">Graph</a></li>
</ul>
<p>A few of the most popular NoSQL databases are:</p>
<ul>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/MongoDB">MongoDB</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Apache_Cassandra">Cassandra</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Apache_CouchDB">CouchDB</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/Amazon_DynamoDB">DynamoDB</a></li>
<li><a target="_blank" href="https://www.elastic.co/">ElasticSearch</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/NovjCrDFlXk" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h3 id="heading-comparing-sql-databases">Comparing SQL Databases</h3>
<p>Let's dive deeper and talk about some of the popular SQL Databases and what makes them different from one another. Some of the most popular SQL Databases right now are:</p>
<ul>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/PostgreSQL">PostgreSQL</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/MySQL">MySQL</a></li>
<li><a target="_blank" href="https://db-engines.com/en/system/Microsoft+SQL+Server">Microsoft SQL Server</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/SQLite">SQLite</a></li>
<li><a target="_blank" href="https://en.wikipedia.org/wiki/List_of_relational_database_management_systems">And many others</a></li>
</ul>
<p>Source: <a target="_blank" href="https://db-engines.com/en/ranking">db-engines.com</a></p>
<p>While all of these Databases use SQL, each database defines specific rules, practices, and strategies that separate them from their competitors. </p>
<h4 id="heading-sqlite-vs-postgresql">SQLite vs PostgreSQL</h4>
<p>Personally, SQLite and PostgreSQL are my favorites from the list above. Postgres is a very powerful, open-source, production-ready SQL database. SQLite is a lightweight, embeddable, open-source database. I usually choose one of these technologies if I'm doing SQL work.</p>
<p>SQLite is a serverless database management system (DBMS) that has the ability to run within applications, whereas PostgreSQL uses a Client-Server model and requires a server to be installed and listening on a network, similar to an HTTP server.</p>
<p>See a full <a target="_blank" href="https://db-engines.com/en/system/PostgreSQL%3BSQLite">comparison here</a>.</p>
<p>Again, in this course we will be working with SQLite, a lightweight and simple database. For most <a target="_blank" href="https://blog.boot.dev/backend/do-backend-devs-need-sql/">backend</a> web servers, PostgreSQL is a more production-ready option, but SQLite is great for learning and for small systems.</p>
<h2 id="heading-chapter-2-sql-tables">Chapter 2: SQL Tables</h2>
<p>The <code>CREATE TABLE</code> statement is used to create a new table in a database.</p>
<h3 id="heading-how-to-use-the-create-table-statement">How to use the <code>CREATE TABLE</code> statement</h3>
<p>To create a table, use the <code>CREATE TABLE</code> statement followed by the name of the table and the fields you want in the table.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> employees (<span class="hljs-keyword">id</span> <span class="hljs-built_in">INTEGER</span>, <span class="hljs-keyword">name</span> <span class="hljs-built_in">TEXT</span>, age <span class="hljs-built_in">INTEGER</span>, is_manager <span class="hljs-built_in">BOOLEAN</span>, salary <span class="hljs-built_in">INTEGER</span>);
</code></pre>
<p>Each field name is followed by its datatype. We'll get to data types in a minute.</p>
<p>It's also acceptable and common to break up the <code>CREATE TABLE</code> statement with some whitespace like this:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> employees(
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">INTEGER</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">TEXT</span>,
    age <span class="hljs-built_in">INTEGER</span>,
    is_manager <span class="hljs-built_in">BOOLEAN</span>,
    salary <span class="hljs-built_in">INTEGER</span>
);
</code></pre>
<h3 id="heading-how-to-alter-tables">How to Alter Tables</h3>
<p>We often need to alter our database schema without deleting it and re-creating it. Imagine if Twitter deleted its database each time it needed to add a feature, that would be a disaster! Your account and all your tweets would be wiped out on a daily basis.</p>
<p>Instead, we can use use the <code>ALTER TABLE</code> statement to make changes in place without deleting any data.</p>
<h4 id="heading-how-to-use-alter-table">How to use <code>ALTER TABLE</code></h4>
<p>With SQLite an <code>ALTER TABLE</code> statement allows you to:</p>
<ol>
<li>Rename a table or column, which you can do like this:</li>
</ol>
<pre><code class="lang-SQL"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> employees
<span class="hljs-keyword">RENAME</span> <span class="hljs-keyword">TO</span> contractors;

<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> contractors
<span class="hljs-keyword">RENAME</span> <span class="hljs-keyword">COLUMN</span> salary <span class="hljs-keyword">TO</span> invoice;
</code></pre>
<ol start="2">
<li>ADD or DROP a column, which you can do like this:</li>
</ol>
<pre><code class="lang-SQL"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> contractors
<span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> job_title <span class="hljs-built_in">TEXT</span>;

<span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> contractors
<span class="hljs-keyword">DROP</span> <span class="hljs-keyword">COLUMN</span> is_manager;
</code></pre>
<h3 id="heading-intro-to-migrations">Intro to Migrations</h3>
<p>A database <a target="_blank" href="https://en.wikipedia.org/wiki/Schema_migration">migration</a> is a set of changes to a relational database. In fact, the <code>ALTER TABLE</code> statements we did in the last exercise were examples of migrations.</p>
<p>Migrations are helpful when transitioning from one state to another, fixing mistakes, or adapting a database to changes. </p>
<p>Good migrations are small, incremental and ideally reversible changes to a database. As you can imagine, when working with large databases, making changes can be scary. We have to be careful when writing database migrations so that we don't break any systems that depend on the old database schema.</p>
<h4 id="heading-example-of-a-bad-migration">Example of a bad migration</h4>
<p>If a backend server periodically runs a query like <code>SELECT * FROM people</code>, and we execute a database migration that alters the table name from <code>people</code> to <code>users</code> <em>without updating the code</em>, the application will break. It will try to grab data from a table that no longer exists.</p>
<p>A simple solution to this problem would be to deploy new code that uses a new query:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span>;
</code></pre>
<p>And we would deploy that code to production immediately following the migration.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/iHIGUpEVN6Y" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h3 id="heading-sql-data-types">SQL Data Types</h3>
<p>SQL as a language can support many different data types. But the datatypes that your database management system (<a target="_blank" href="https://en.wikipedia.org/wiki/Database#:~:text=A%20database%20management%20system%20(DBMS)">DBMS</a>) supports will vary depending on the specific database you're using.</p>
<p>SQLite only supports the most basic types, and we're using SQLite in this course.</p>
<h4 id="heading-sqlite-data-types">SQLite Data Types</h4>
<p>Let's go over the <a target="_blank" href="https://www.sqlite.org/datatype3.html">data types supported by SQLite:</a> and how they are stored.</p>
<ol>
<li><code>NULL</code> - Null value.</li>
<li><code>INTEGER</code> - A signed integer stored in 0,1,2,3,4,6, or 8 bytes.</li>
<li><code>REAL</code> - Floating point value stored as an 64-bit <a target="_blank" href="https://en.wikipedia.org/wiki/IEEE_754">IEEE floating point number</a>.</li>
<li><code>TEXT</code> - Text string stored using database encoding such as <a target="_blank" href="https://en.wikipedia.org/wiki/UTF-8">UTF-8</a></li>
<li><code>BLOB</code> - Short for <a target="_blank" href="https://en.wikipedia.org/wiki/Binary_large_object">Binary large object</a> and typically used for images, audio or other multimedia.</li>
</ol>
<p>For example:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> employees (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">INTEGER</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">TEXT</span>,
    age <span class="hljs-built_in">INTEGER</span>,
    is_manager <span class="hljs-built_in">BOOLEAN</span>,
    salary <span class="hljs-built_in">INTEGER</span>
);
</code></pre>
<h4 id="heading-boolean-values">Boolean values</h4>
<p>It's important to note that SQLite does not have a separate <code>BOOLEAN</code> storage class. Instead, boolean values are stored as integers:</p>
<ul>
<li><code>0</code> = <code>false</code></li>
<li><code>1</code> = <code>true</code></li>
</ul>
<p>It's not actually all that weird – boolean values are just binary bits after all!</p>
<p>SQLite will still let you write your queries using <code>boolean</code> expressions and <code>true</code>/<code>false</code> keywords, but it will convert the booleans to integers under-the-hood.</p>
<h2 id="heading-chapter-3-constraints">Chapter 3: Constraints</h2>
<p>A <code>constraint</code> is a rule we create on a database that enforces some specific behavior. For example, setting a <code>NOT NULL</code> constraint on a column ensures that the column will not accept <code>NULL</code> values.</p>
<p>If we try to insert a <code>NULL</code> value into a column with the <code>NOT NULL</code> constraint, the insert will fail with an error message. Constraints are extremely useful when we need to ensure that certain kinds of data exist within our database. </p>
<h4 id="heading-not-null-constraint">NOT NULL constraint</h4>
<p>The <code>NOT NULL</code> constraint can be added directly to the <code>CREATE TABLE</code> statement.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> employees(
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">INTEGER</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">UNIQUE</span>,
    title <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>
);
</code></pre>
<h4 id="heading-sqlite-limitation">SQLite limitation</h4>
<p>In other dialects of SQL you can <code>ADD CONSTRAINT</code> within an <code>ALTER TABLE</code> statement. SQLite does not support this feature, so when we create our tables we need to make sure we specify all the constraints we want. </p>
<p>Here's a <a target="_blank" href="https://www.sqlite.org/omitted.html">list of SQL Features</a> SQLite does not implement in case you're curious.</p>
<h3 id="heading-primary-key-constraints">Primary Key Constraints</h3>
<p>A <em>key</em> defines and protects relationships between tables. A <a target="_blank" href="https://en.wikipedia.org/wiki/Primary_key"><code>primary key</code></a> is a special column that uniquely identifies records within a table. Each table can have one, and only one primary key.</p>
<h4 id="heading-your-primary-key-will-almost-always-be-the-id-column">Your primary key will almost always be the "id" column</h4>
<p>It's very common to have a column named <code>id</code> on each table in a database, and that <code>id</code> is the primary key for that table. No two rows in that table can share an <code>id</code>.</p>
<p>A <code>PRIMARY KEY</code> constraint can be explicitly specified on a column to ensure uniqueness, rejecting any inserts where you attempt to create a duplicate ID.</p>
<h3 id="heading-foreign-key-constraints">Foreign Key Constraints</h3>
<p>Foreign keys are what makes relational databases relational! Foreign keys define the relationships <em>between</em> tables. Simply put, a <code>FOREIGN KEY</code> is a field in one table that references another table's <code>PRIMARY KEY</code>.</p>
<h4 id="heading-creating-a-foreign-key-in-sqlite">Creating a Foreign Key in SQLite</h4>
<p>Creating a <code>FOREIGN KEY</code> in SQLite happens at table creation! After we define the table fields and constraints we add an additional <code>CONSTRAINT</code> where we define the <code>FOREIGN KEY</code> and its <code>REFERENCES</code>.</p>
<p>Here's an example:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> departments (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">INTEGER</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    department_name <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>
);

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> employees (
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">INTEGER</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    <span class="hljs-keyword">name</span> <span class="hljs-built_in">TEXT</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>,
    department_id <span class="hljs-built_in">INTEGER</span>,
    <span class="hljs-keyword">CONSTRAINT</span> fk_departments
    <span class="hljs-keyword">FOREIGN</span> <span class="hljs-keyword">KEY</span> (department_id)
    <span class="hljs-keyword">REFERENCES</span> departments(<span class="hljs-keyword">id</span>)
);
</code></pre>
<p>In this example, an <code>employee</code> has a <code>department_id</code>. The <code>department_id</code> must be the same as the <code>id</code> field of a record from the <code>departments</code> table.</p>
<h3 id="heading-schema">Schema</h3>
<p>We've used the word <em>schema</em> a few times now, let's talk about what that word means. A database's <a target="_blank" href="https://www.ibm.com/cloud/learn/database-schema">schema</a> describes how data is organized within it.</p>
<p>Data types, table names, field names, constraints, and the relationships between all of those entities are part of a database's <em>schema</em>.</p>
<h4 id="heading-there-is-no-perfect-way-to-architect-a-database-schema">There is no perfect way to architect a database schema</h4>
<p>When designing a database schema there typically isn't a "correct" solution. We do our best to choose a sane set of tables, fields, constraints, etc that will accomplish our project's goals. Like many things in programming, different schema designs come with different tradeoffs.</p>
<h4 id="heading-how-do-we-decide-on-a-sane-schema-architecture">How do we decide on a sane schema architecture?</h4>
<p>One very important decision that needs to be made is to decide which table will store a user's balance! As you can imagine, ensuring our data is accurate when dealing with money is <em>super</em> important. We want to be able to:</p>
<ul>
<li>Keep track of a user's current balance</li>
<li>See the historical balance at any point in the past</li>
<li>See a log of which transactions changed the balance over time</li>
</ul>
<p>There are many ways to approach this problem. For our first attempt, let's try the simplest schema that fulfills our project's needs. </p>
<h2 id="heading-chapter-4-crud-operations-in-sql">Chapter 4: CRUD Operations in SQL</h2>
<h3 id="heading-what-is-crud">What is CRUD?</h3>
<p>CRUD is an acronym that stands for <code>CREATE</code>, <code>READ</code>, <code>UPDATE</code>, and <code>DELETE</code>. These four operations are the bread and butter of nearly every database you will create. </p>
<h4 id="heading-http-and-crud">HTTP and CRUD</h4>
<p>The CRUD operations correlate nicely with the HTTP methods you may have already learned:</p>
<ul>
<li><code>HTTP POST</code> - <code>CREATE</code></li>
<li><code>HTTP GET</code> - <code>READ</code></li>
<li><code>HTTP PUT</code> - <code>UPDATE</code></li>
<li><code>HTTP DELETE</code> - <code>DELETE</code></li>
</ul>
<h3 id="heading-sql-insert-statement">SQL Insert Statement</h3>
<p>Tables are pretty useless without data in them. In SQL we can add records to a table using an <code>INSERT INTO</code> statement. When using an <code>INSERT</code> statement we must first specify the <code>table</code> we are inserting the record into, followed by the <code>fields</code> within that table we want to add <code>VALUES</code> to.</p>
<p>Here's an example of an <code>INSERT INTO</code> statement:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> employees(<span class="hljs-keyword">id</span>, <span class="hljs-keyword">name</span>, title)
<span class="hljs-keyword">VALUES</span> (<span class="hljs-number">1</span>, <span class="hljs-string">'Allan'</span>, <span class="hljs-string">'Engineer'</span>);
</code></pre>
<h3 id="heading-http-crud-database-lifecycle">HTTP CRUD Database lifecycle</h3>
<p>It's important to understand how data <em>flows</em> through a typical web application.</p>
<p><img src="https://i.imgur.com/hli3crD.png" alt="database flow" width="799" height="386" loading="lazy"></p>
<ol>
<li>The front-end processes some data from user input - maybe a form is submitted.</li>
<li>The front-end sends that data to the server through an HTTP request - maybe a <code>POST</code>.</li>
<li>The server makes a SQL query to it's database to create an associated record - Probably using an <code>INSERT</code> statement.</li>
<li>Once the server has processed that the database query was successful, it responds to the front-end with a status code! Hopefully a 200-level code (success)!</li>
</ol>
<h3 id="heading-manual-entry">Manual Entry</h3>
<p>Manually <code>INSERT</code>ing every single record in a database would be an <em>extremely</em> time-consuming task! Working with raw SQL as we are now is not super common when designing <a target="_blank" href="https://blog.boot.dev/backend/do-backend-devs-need-sql/">backend systems</a>.</p>
<p>When working with SQL within a software system, like a backend web application, you'll typically have access to a programming language such as <a target="_blank" href="https://boot.dev/learn/learn-golang">Go</a> or <a target="_blank" href="https://boot.dev/learn/learn-python">Python</a>. </p>
<p>For example, a backend server written in Go can use string concatenation to dynamically create SQL statements, and that's usually how it's done.</p>
<pre><code class="lang-go">sqlQuery := fmt.Sprintf(<span class="hljs-string">`
INSERT INTO users(name, age, country_code)
VALUES ('%s', %v, %s);
`</span>, user.Name, user.Age, user.CountryCode)
</code></pre>
<h4 id="heading-sql-injection">SQL Injection</h4>
<p>The example above is an oversimplification of what <em>really</em> happens when you access a database using Go code. In essence, it's correct. String interpolation is how production systems access databases. That said, it must be done <em>carefully</em> to not be a <a target="_blank" href="https://en.wikipedia.org/wiki/SQL_injection">security vulnerability</a>. We'll talk more about that later!</p>
<h3 id="heading-count">Count</h3>
<p>We can use a <code>SELECT</code> statement to get a count of the records within a table. This can be very useful when we need to know how many records there are, but we don't particularly care what's in them.</p>
<p>Here's an example in SQLite:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">count</span>(*) <span class="hljs-keyword">from</span> employees;
</code></pre>
<p>The <code>*</code> in this case refers to a column name. We don't care about the count of a specific column - we want to know the number of total records so we can use the wildcard (*).</p>
<h3 id="heading-http-crud-database-lifecycle-1">HTTP CRUD database lifecycle</h3>
<p>We talked about how a "create" operation flows through a web application. Let's talk about a "read".</p>
<p><img src="https://i.imgur.com/KTDQGy1.png" alt="read lifecycle" width="787" height="352" loading="lazy"></p>
<p>Let's talk through an example. Our product manager wants to show profile data on a user's settings page. Here's how we could engineer that feature request:</p>
<ol>
<li>First, the front-end webpage loads.</li>
<li>The front-end sends an HTTP <code>GET</code> request to a <code>/users</code> endpoint on the back-end server.</li>
<li>The server receives the request.</li>
<li>The server uses a <code>SELECT</code> statement to retrieve the user's record from the <code>users</code> table in the database.</li>
<li>The server converts the row of SQL data into a <code>JSON</code> object and sends it back to the front-end.</li>
</ol>
<h3 id="heading-where-clause">WHERE clause</h3>
<p>In order to keep learning about CRUD operations in SQL, we need to learn how to make the instructions we send to the database more specific. SQL accepts a <code>WHERE</code> statement within a query that allows us to be very specific with our instructions.</p>
<p>If we were unable to specify the specific record we wanted to <code>READ</code>, <code>UPDATE</code>, or <code>DELETE</code> making queries to a database would be very frustrating, and very inefficient.</p>
<h4 id="heading-using-a-where-clause">Using a WHERE clause</h4>
<p>Say we had over 9000 records in our <code>users</code> table. We often want to look at specific user data within that table without retrieving <em>all</em> the other records in the table. We can use a <code>SELECT</code> statement followed by a <code>WHERE</code> clause to specify which records to retrieve. The <code>SELECT</code> statement stays the same, we just add the <code>WHERE</code> clause to the end of the <code>SELECT</code>. </p>
<p>Here's an example:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">name</span> <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span> <span class="hljs-keyword">WHERE</span> power_level &gt;= <span class="hljs-number">9000</span>;
</code></pre>
<p>This will select only the <code>name</code> field of any user within the <code>users</code> table <code>WHERE</code> the <code>power_level</code> field is greater than or equal to <code>9000</code>.</p>
<h3 id="heading-finding-null-values">Finding NULL values</h3>
<p>You can use a <code>WHERE</code> clause to filter values by whether or not they're <code>NULL</code>.</p>
<h4 id="heading-is-null">IS NULL</h4>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">name</span> <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span> <span class="hljs-keyword">WHERE</span> first_name <span class="hljs-keyword">IS</span> <span class="hljs-literal">NULL</span>;
</code></pre>
<h4 id="heading-is-not-null">IS NOT NULL</h4>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">name</span> <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span> <span class="hljs-keyword">WHERE</span> first_name <span class="hljs-keyword">IS</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>;
</code></pre>
<h3 id="heading-delete">DELETE</h3>
<p>When a user deletes their account on Twitter, or deletes a comment on a YouTube video, that data needs to be removed from its respective database.</p>
<h4 id="heading-delete-statement">DELETE statement</h4>
<p>A <code>DELETE</code> statement removes a record from a table that match the <code>WHERE</code> clause. As an example:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">DELETE</span> <span class="hljs-keyword">from</span> employees
    <span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">id</span> = <span class="hljs-number">251</span>;
</code></pre>
<p>This <code>DELETE</code> statement removes all records from the <code>employees</code> table that have an id of <code>251</code>!</p>
<h3 id="heading-the-danger-of-deleting-data">The danger of deleting data</h3>
<p>Deleting data can be a dangerous operation. Once removed, data can be really hard if not impossible to restore! Let's talk about a couple of common ways back-end engineers protect against losing valuable customer data.</p>
<h4 id="heading-strategy-1-backups">Strategy 1 - Backups</h4>
<p>If you're using a cloud-service like GCP's <a target="_blank" href="https://cloud.google.com/sql">Cloud SQL</a> or AWS's <a target="_blank" href="https://aws.amazon.com/rds/">RDS</a> you should <em>always</em> turn on automated backups. They take an automatic snapshot of your entire database on some interval, and keep it around for some length of time.</p>
<p>For example, the Boot.dev database has a backup snapshot taken daily and we retain those backups for 30 days. If I ever accidentally run a query that deletes valuable data, I can restore it from the backup.</p>
<p><strong>You should have a backup strategy for production databases.</strong></p>
<h4 id="heading-strategy-2-soft-deletes">Strategy 2 - Soft deletes</h4>
<p>A "soft delete" is when you don't actually delete data from your database, but instead just "mark" the data as deleted. </p>
<p>For example, you might set a <code>deleted_at</code> date on the row you want to delete. Then, in your queries you ignore anything that has a <code>deleted_at</code> date set. The idea is that this allows your application to behave as if it's deleting data, but you can always go back and restore any data that's been removed.</p>
<p>You should probably only soft-delete if you have a specific reason to do so. Automated backups should be "good enough" for most applications that are just interested in protecting against developer mistakes.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/kCWHniEnQDM" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h3 id="heading-update-query-in-sql">Update query in SQL</h3>
<p>Whenever you update your profile picture or change your password online, you are changing the data in a field on a table in a database. Imagine if every time you accidentally messed up a Tweet on Twitter you had to delete the entire tweet and post a new one instead of just editing it...</p>
<p>...Well, that's a bad example.</p>
<h4 id="heading-update-statement">Update statement</h4>
<p>The <code>UPDATE</code> statement in SQL allows us to update the fields of a record. We can even update many records depending on how we write the statement.</p>
<p>An <code>UPDATE</code> statement specifies the table that needs to be updated, followed by the fields and their new values by using the <code>SET</code> keyword. Lastly a <code>WHERE</code> clause indicates the record(s) to update.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">UPDATE</span> employees
<span class="hljs-keyword">SET</span> job_title = <span class="hljs-string">'Backend Engineer'</span>, salary = <span class="hljs-number">150000</span>
<span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">id</span> = <span class="hljs-number">251</span>;
</code></pre>
<h3 id="heading-object-relational-mapping-orms">Object-Relational Mapping (ORMs)</h3>
<p>An <a target="_blank" href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping">Object-Relational Mapping</a> or an <em>ORM</em> for short, is a tool that allows you to perform CRUD operations on a database using a traditional programming language. These typically come in the form of a library or framework that you would use in your backend code.</p>
<p>The primary benefit an ORM provides is that it maps your database records to in-memory objects. For example, in Go we might have a struct that we use in our code:</p>
<pre><code class="lang-go"><span class="hljs-keyword">type</span> User <span class="hljs-keyword">struct</span> {
    ID <span class="hljs-keyword">int</span>
    Name <span class="hljs-keyword">string</span>
    IsAdmin <span class="hljs-keyword">bool</span>
}
</code></pre>
<p>This struct definition conveniently represents a database table called <code>users</code>, and an instance of the struct represents a row in the table.</p>
<h4 id="heading-example-using-an-orm">Example: Using an ORM</h4>
<p>Using an ORM we might be able to write simple code like this:</p>
<pre><code class="lang-go">user := User{
    ID: <span class="hljs-number">10</span>,
    Name: <span class="hljs-string">"Lane"</span>,
    IsAdmin: <span class="hljs-literal">false</span>,
}

<span class="hljs-comment">// generates a SQL statement and runs it,</span>
<span class="hljs-comment">// creating a new record in the users table</span>
db.Create(user)
</code></pre>
<h4 id="heading-example-using-straight-sql">Example: Using straight SQL</h4>
<p>Using straight SQL we might have to do something a bit more manual:</p>
<pre><code class="lang-go">user := User{
    ID: <span class="hljs-number">10</span>,
    Name: <span class="hljs-string">"Lane"</span>,
    IsAdmin: <span class="hljs-literal">false</span>,
}

db.Exec(<span class="hljs-string">"INSERT INTO users (id, name, is_admin) VALUES (?, ?, ?);"</span>,
    user.ID, user.Name, user.IsAdmin)
</code></pre>
<h4 id="heading-should-you-use-an-orm">Should you use an ORM?</h4>
<p>That depends – an ORM typically trades simplicity for control.</p>
<p>Using straight SQL you can take full advantage of the power of the SQL language. Using an ORM, you're limited by whatever functionality the ORM has. </p>
<p>If you run into issues with a specific query, it can be harder to debug with an ORM because you have to dig through the framework's code and documentation to figure out how the underlying queries are being generated.</p>
<p>I recommend doing projects both ways so that you can learn about the tradeoffs. At the end of the day, when you're working on a team of developers, it will be a team decision.</p>
<h2 id="heading-chapter-5-basic-sql-queries">Chapter 5: Basic SQL Queries</h2>
<h3 id="heading-how-to-use-the-as-clause-in-sql">How to use the <code>AS</code> Clause in SQL</h3>
<p>Sometimes we need to structure the data we return from our queries in a specific way. An <code>AS</code> clause allows us to "alias" a piece of data in our query. The alias only exists for the duration of the query. </p>
<h4 id="heading-as-keyword"><code>AS</code> keyword</h4>
<p>The following queries return the same data:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> employee_id <span class="hljs-keyword">AS</span> <span class="hljs-keyword">id</span>, employee_name <span class="hljs-keyword">AS</span> <span class="hljs-keyword">name</span>
<span class="hljs-keyword">FROM</span> employees;
</code></pre>
<p>and:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> employee_id, employee_name
<span class="hljs-keyword">FROM</span> employees;
</code></pre>
<p>The difference is that the results from the aliased query would have column names <code>id</code> and <code>name</code> instead of <code>employee_id</code> and <code>employee_name</code>.</p>
<h3 id="heading-sql-functions">SQL Functions</h3>
<p>At the end of the day, SQL is a programming language, and it's one that supports functions. We can use functions and aliases to <em>calculate</em> new columns in a query. This is similar to how you might use formulas in Excel.</p>
<h4 id="heading-iif-function">IIF function</h4>
<p>In SQLite, the <code>IIF</code> function works like a <a target="_blank" href="https://book.pythontips.com/en/latest/ternary_operators.html">ternary</a>. For example:</p>
<pre><code class="lang-SQL">IIF(carA &gt; carB, "Car a is bigger", "Car b is bigger")
</code></pre>
<p>If <code>a</code> is greater than <code>b</code>, this statement evaluates to the string <code>"Car a is bigger"</code>. Otherwise, it evaluates to <code>"Car b is bigger"</code>.</p>
<p>Here's how we can use <code>IIF()</code> and a <code>directive</code> alias to add a new calculated column to our result set:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> quantity,
    <span class="hljs-keyword">IIF</span>(quantity &lt; <span class="hljs-number">10</span>, <span class="hljs-string">"Order more"</span>, <span class="hljs-string">"In Stock"</span>) <span class="hljs-keyword">AS</span> directive
    <span class="hljs-keyword">from</span> products
</code></pre>
<h3 id="heading-how-to-use-between-with-where">How to Use <code>BETWEEN</code> with <code>WHERE</code></h3>
<p>We can check if certain values are <code>between</code> two numbers using the <code>WHERE</code> clause in an intuitive way. The <code>WHERE</code> clause doesn't always have to be used to specify specific id's or values. We can also use it to help narrow down our result set. Here's an example:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> employee_name, salary
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">WHERE</span> salary <span class="hljs-keyword">BETWEEN</span> <span class="hljs-number">30000</span> <span class="hljs-keyword">and</span> <span class="hljs-number">60000</span>;
</code></pre>
<p>This query returns all the employees <code>name</code> and <code>salary</code> fields for any rows where the <code>salary</code> is <code>BETWEEN</code> 30,000 and 60,000. We can also query results that are <code>NOT BETWEEN</code> two specified values. </p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> product_name, quantity
<span class="hljs-keyword">FROM</span> products
<span class="hljs-keyword">WHERE</span> quantity <span class="hljs-keyword">NOT</span> <span class="hljs-keyword">BETWEEN</span> <span class="hljs-number">20</span> <span class="hljs-keyword">and</span> <span class="hljs-number">100</span>;
</code></pre>
<p>This query returns all the product names where the quantity was not between <code>20</code> and <code>100</code>. We can use conditionals to make the results of our query as specific as we need them to be.</p>
<h3 id="heading-how-to-return-distinct-values">How to return distinct values</h3>
<p>Sometimes we want to retrieve records from a table without getting back any duplicates.</p>
<p>For example, we may want to know all the different companies our employees have worked at previously, but we don't want to see the same company multiple times in the report.</p>
<h4 id="heading-select-distinct"><code>SELECT DISTINCT</code></h4>
<p>SQL offers us the <code>DISTINCT</code> keyword that removes duplicate records from the resulting query.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">DISTINCT</span> previous_company
    <span class="hljs-keyword">FROM</span> employees;
</code></pre>
<p>This only returns one row for each unique <code>previous_company</code> value.</p>
<h3 id="heading-logical-operators">Logical Operators</h3>
<p>We often need to use multiple conditions to retrieve the exact information we want. We can begin to structure much more complex queries by using multiple conditions together to narrow down the search results of our query.</p>
<p>The logical <code>AND</code> operator can be used to narrow down our result sets even more.</p>
<h4 id="heading-and-operator"><code>AND</code> operator</h4>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> product_name, quantity, shipment_status
    <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">WHERE</span> shipment_status = <span class="hljs-string">'pending'</span>
    <span class="hljs-keyword">AND</span> quantity <span class="hljs-keyword">BETWEEN</span> <span class="hljs-number">0</span> <span class="hljs-keyword">and</span> <span class="hljs-number">10</span>;
</code></pre>
<p>This only retrieves records where both the <code>shipment_status</code> is "pending" AND the <code>quantity</code> is between <code>0</code> and <code>10</code>.</p>
<h4 id="heading-equality-operators">Equality operators</h4>
<p>All of the following operators are supported in SQL. The <code>=</code> is the main one to watch out for, it's not <code>==</code> like in many other languages.</p>
<ul>
<li><code>=</code></li>
<li><code>&lt;</code></li>
<li><code>&gt;</code></li>
<li><code>&lt;=</code></li>
<li><code>&gt;=</code></li>
</ul>
<p>For example, in Python you might compare two values like this:</p>
<pre><code class="lang-py"><span class="hljs-keyword">if</span> name == <span class="hljs-string">"age"</span>
</code></pre>
<p>Whereas in SQL you would do:</p>
<pre><code class="lang-sql">WHERE name = "age"
</code></pre>
<h4 id="heading-or-operator"><code>OR</code> operator</h4>
<p>As you've probably guessed, if the logical <code>AND</code> operator is supported, the <code>OR</code> operator is probably supported as well.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> product_name, quantity, shipment_status
    <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">WHERE</span> shipment_status = <span class="hljs-string">'out of stock'</span>
    <span class="hljs-keyword">OR</span> quantity <span class="hljs-keyword">BETWEEN</span> <span class="hljs-number">10</span> <span class="hljs-keyword">and</span> <span class="hljs-number">100</span>;
</code></pre>
<p>This query retrieves records where either the shipment_status <code>condition</code> OR the <code>quantity</code> condition are met.</p>
<p>Order of operations matter when using these operators.</p>
<p>You can group logical operations with parentheses to specify the <a target="_blank" href="https://www.mathsisfun.com/operation-order-pemdas.html">order of operations</a>.</p>
<pre><code class="lang-sql">(this AND that) OR the_other
</code></pre>
<h4 id="heading-the-in-operator">The <code>IN</code> operator</h4>
<p>Another variation to the <code>WHERE</code> clause we can utilize is the <code>IN</code> operator. <code>IN</code> returns <code>true</code> or <code>false</code> if the first operand matches any of the values in the second operand. The <code>IN</code> operator is a shorthand for multiple <code>OR</code> conditions.</p>
<p>These two queries are equivalent:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> product_name, shipment_status
    <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">WHERE</span> shipment_status <span class="hljs-keyword">IN</span> (<span class="hljs-string">'shipped'</span>, <span class="hljs-string">'preparing'</span>, <span class="hljs-string">'out of stock'</span>);
</code></pre>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> product_name, shipment_status
    <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">WHERE</span> shipment_status = <span class="hljs-string">'shipped'</span>
        <span class="hljs-keyword">OR</span> shipment_status = <span class="hljs-string">'preparing'</span>
        <span class="hljs-keyword">OR</span> shipment_status = <span class="hljs-string">'out of stock'</span>;
</code></pre>
<p>Hopefully, you're starting to see how querying specific data using fine-tuned SQL clauses helps reveal important insights. The larger a table becomes the harder it becomes to analyze without proper queries.</p>
<h4 id="heading-the-like-keyword">The <code>LIKE</code> keyword</h4>
<p>Sometimes we don't have the luxury of knowing exactly what it is we need to query. Have you ever wanted to look up a song or a video but you only remember part of the name? SQL provides us an option for when we're in situations <code>LIKE</code> this.</p>
<p>The <code>LIKE</code> keyword allows for the use of the <code>%</code> and <code>_</code> wildcard operators. Let's focus on <code>%</code> first.</p>
<h4 id="heading-operator"><code>%</code> Operator</h4>
<p>The <code>%</code> operator will match zero or more characters. We can use this operator within our query string to find more than just exact matches depending on where we place it.</p>
<p>Here are some examples that show how these work:</p>
<p>Product starts with "banana":</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> products
<span class="hljs-keyword">WHERE</span> product_name <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'banana%'</span>;
</code></pre>
<p>Product ends with "banana":</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">from</span> products
<span class="hljs-keyword">WHERE</span> product_name <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%banana'</span>;
</code></pre>
<p>Product contains "banana":</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">from</span> products
<span class="hljs-keyword">WHERE</span> product_name <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%banana%'</span>;
</code></pre>
<h3 id="heading-underscore-operator">Underscore Operator</h3>
<p>As discussed, the <code>%</code> wildcard operator matches zero or more characters. Meanwhile, the <code>_</code> wildcard operator only matches a single character.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">WHERE</span> product_name <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'_oot'</span>;
</code></pre>
<p>The query above matches products like:</p>
<ul>
<li>boot</li>
<li>root</li>
<li>foot</li>
</ul>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">WHERE</span> product_name <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'__oot'</span>;
</code></pre>
<p>The query above matches products like:</p>
<ul>
<li>shoot</li>
<li>groot</li>
</ul>
<h2 id="heading-chapter-6-how-to-structure-return-data-in-sql">Chapter 6: How to Structure Return Data in SQL</h2>
<h3 id="heading-the-limit-keyword">The <code>LIMIT</code> keyword</h3>
<p>Sometimes we don't want to retrieve every record from a table. For example, it's common for a production database table to have millions of rows, and <code>SELECT</code>ing all of them might crash your system. This is where the <code>LIMIT</code> keyword enters the chat.</p>
<p>The <code>LIMIT</code> keyword can be used at the end of a select statement to reduce the number of records returned.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">WHERE</span> product_name <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'%berry%'</span>
    <span class="hljs-keyword">LIMIT</span> <span class="hljs-number">50</span>;
</code></pre>
<p>The query above retrieves all the records from the <code>products</code> table where the name contains the word berry. If we ran this query on the Facebook database, it would almost certainly return a lot of records. </p>
<p>The <code>LIMIT</code> statement only allows the database to return up to 50 records matching the query. This means that if there aren't that many records matching the query, the <code>LIMIT</code> statement will not have an effect.</p>
<h3 id="heading-the-sql-order-by-keyword">The SQL <code>ORDER BY</code> keyword</h3>
<p>SQL also offers us the ability to sort the results of a query using <code>ORDER BY</code>. By default, the <code>ORDER BY</code> keyword sorts records by the given field in ascending order, or <code>ASC</code> for short. However, <code>ORDER BY</code> does support descending order as well with the keyword <code>DESC</code>.</p>
<h4 id="heading-examples">Examples</h4>
<p>This query returns the <code>name</code>, <code>price</code>, and <code>quantity</code> fields from the <code>products</code> table sorted by <code>price</code> in ascending order:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">name</span>, price, quantity <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> price;
</code></pre>
<p>This query returns the <code>name</code>, <code>price</code>, and <code>quantity</code> of the products ordered by the quantity in descending order:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">name</span>, price, quantity <span class="hljs-keyword">FROM</span> products
    <span class="hljs-keyword">ORDER</span> <span class="hljs-keyword">BY</span> quantity <span class="hljs-keyword">desc</span>;
</code></pre>
<h3 id="heading-order-by-and-limit">Order By and Limit</h3>
<p>When using both <code>ORDER BY</code> and <code>LIMIT</code>, the <code>ORDER BY</code> clause must come first.</p>
<h2 id="heading-chapter-7-how-to-perform-aggregations-in-sql">Chapter 7: How to Perform Aggregations in SQL</h2>
<p>An "aggregation" is a single value that's derived by combining several other values. We performed an aggregation earlier when we used the <code>count</code> statement to count the number of records in a table.</p>
<h3 id="heading-why-use-aggregations">Why use aggregations?</h3>
<p>Data stored in a database should generally be stored <a target="_blank" href="https://wagslane.dev/posts/keep-your-data-raw-at-rest/">raw</a>. When we need to calculate some additional data from the raw data, we can use an aggregation.</p>
<p>Take the following <code>count</code> aggregation as an example:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">COUNT</span>(*)
<span class="hljs-keyword">FROM</span> products
<span class="hljs-keyword">WHERE</span> quantity = <span class="hljs-number">0</span>;
</code></pre>
<p>This query returns the number of products that have a <code>quantity</code> of <code>0</code>. We could store a count of the products in a separate database table, and increment/decrement it whenever we make changes to the <code>products</code> table - but that would be redundant. </p>
<p>It's much simpler to store the products in a single place (we call this a <a target="_blank" href="https://en.wikipedia.org/wiki/Single_source_of_truth">single source of truth</a>) and run an aggregation when we need to derive additional information from the raw data.</p>
<h3 id="heading-the-sum-function">The <code>SUM</code> function</h3>
<p>The <code>sum</code> aggregation function returns the sum of a set of values.</p>
<p>For example, the query below returns a single record containing a single field. The returned value is equal to the total salary being collected by all of the <code>employees</code> in the <code>employees</code> table.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">sum</span>(salary)
<span class="hljs-keyword">FROM</span> employees;
</code></pre>
<p>Which returns:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>SUM(SALARY)</td></tr>
</thead>
<tbody>
<tr>
<td>2483</td></tr>
</tbody>
</table>
</div><h3 id="heading-the-max-function">The <code>MAX</code> function</h3>
<p>As you may expect, the <code>max</code> function retrieves the <em>largest</em> value from a set of values. For example:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">max</span>(price)
<span class="hljs-keyword">FROM</span> products
</code></pre>
<p>This query looks through all of the prices in the <code>products</code> table and returns the price with the largest price value. Remember it only returns the <code>price</code>, not the rest of the record. You always need to specify each field you want a query to return.</p>
<h4 id="heading-a-note-on-schema">A note on schema</h4>
<ul>
<li>The <code>sender_id</code> will be present for any transactions where the user in question (<code>user_id</code>) is receiving money (from the sender).</li>
<li>The <code>recipient_id</code> will be present for any transactions where the user in question (<code>user_id</code>) is sending money (to the recipient).</li>
</ul>
<p>In other words, a transaction can only have a <code>sender_id</code> or a <code>recipient_id</code> - not both. The presence of one or the other indicates whether money is going into or out of the user's account.</p>
<p>This <code>user_id</code>, <code>recipient_id</code>, <code>sender_id</code> schema we've designed is only one way to design a transactions database - there are other valid ways to do it. It's the one we're using, and later we'll talk more about the tradeoffs in different database design options.</p>
<h3 id="heading-the-min-function">The <code>MIN</code> function</h3>
<p>The <code>min</code> function works the same as the <code>max</code> function but finds the lowest value instead of the highest value.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> product_name, <span class="hljs-keyword">min</span>(price)
<span class="hljs-keyword">from</span> products;
</code></pre>
<p>This query returns the <code>product_name</code> and the <code>price</code> fields of the record with the lowest <code>price</code>.</p>
<h3 id="heading-the-group-by-clause">The <code>GROUP BY</code> clause</h3>
<p>There are times we need to group data based on specific values.</p>
<p>SQL offers the <code>GROUP BY</code> clause which can group rows that have similar values into "summary" rows. It returns one row for each group. The interesting part is that each group can have an aggregate function applied to it that operates only on the grouped data.</p>
<h4 id="heading-example-of-group-by">Example of <code>GROUP BY</code></h4>
<p>Imagine that we have a database with songs and albums, and we want to see how many songs are on each album. We can use a query like this:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> album_id, <span class="hljs-keyword">count</span>(song_id)
<span class="hljs-keyword">FROM</span> songs
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> album_id;
</code></pre>
<p>This query retrieves a count of all the songs on each album. One record is returned per album, and they each have their own <code>count</code>.</p>
<h3 id="heading-the-avg-function">The <code>AVG()</code> function</h3>
<p>Just like we may want to find the minimum or maximum values within a dataset, sometimes we need to know the <a target="_blank" href="https://en.wikipedia.org/wiki/Arithmetic_mean">average</a>!</p>
<p>SQL offers us the <code>AVG()</code> function. Similar to <code>MAX()</code>, <code>AVG()</code> calculates the average of all non-NULL values. </p>
<pre><code class="lang-SQL"><span class="hljs-keyword">select</span> song_name, <span class="hljs-keyword">avg</span>(song_length)
<span class="hljs-keyword">from</span> songs
</code></pre>
<p>This query returns the average <code>song_length</code> in the <code>songs</code> table.</p>
<h3 id="heading-the-having-clause">The <code>HAVING</code> clause</h3>
<p>When we need to filter the results of a <code>GROUP BY</code> query even further, we can use the <code>HAVING</code> clause. The <code>HAVING</code> clause specifies a search condition for a group.</p>
<p>The <code>HAVING</code> clause is similar to the <code>WHERE</code> clause, but it operates on groups after they've been grouped, rather than rows before they've been grouped.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> album_id, <span class="hljs-keyword">count</span>(<span class="hljs-keyword">id</span>) <span class="hljs-keyword">as</span> <span class="hljs-keyword">count</span>
<span class="hljs-keyword">FROM</span> songs
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> album_id
<span class="hljs-keyword">HAVING</span> <span class="hljs-keyword">count</span> &gt; <span class="hljs-number">5</span>;
</code></pre>
<p>This query returns the <code>album_id</code> and count of its songs, but only for albums with more than <code>5</code> songs.</p>
<h3 id="heading-having-vs-where-in-sql"><code>HAVING</code> vs <code>WHERE</code> in SQL</h3>
<p>It's fairly common for developers to get confused about the difference between the <code>HAVING</code> and the <code>WHERE</code> clauses - they're pretty similar after all.</p>
<p>The difference is fairly simple in actuality:</p>
<ul>
<li>A <code>WHERE</code> condition is applied to all the data in a query before it's grouped by a <code>GROUP BY</code> clause.</li>
<li>A <code>HAVING</code> condition is only applied to the grouped rows that are returned after a <code>GROUP BY</code> is applied.</li>
</ul>
<p>This means that if you want to filter on the result of an aggregation, you need to use <code>HAVING</code>. If you want to filter on a value that's present in the raw data, you should use a simple <code>WHERE</code> clause.</p>
<h3 id="heading-the-round-function">The <code>ROUND</code> function</h3>
<p>Sometimes we need to <a target="_blank" href="https://en.wikipedia.org/wiki/Rounding">round</a> some numbers, particularly when working with the results of an aggregation. We can use the <code>ROUND()</code> function to get the job done.</p>
<p>The SQL <code>round()</code> function allows you to specify both the value you wish to round and the precision to which you wish to round it:</p>
<pre><code class="lang-SQL">round(value, precision)
</code></pre>
<p>If no precision is given, SQL will round the value to the nearest whole value:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">select</span> song_name, <span class="hljs-keyword">round</span>(<span class="hljs-keyword">avg</span>(song_length), <span class="hljs-number">1</span>)
<span class="hljs-keyword">from</span> songs
</code></pre>
<p>This query returns the average <code>song_length</code> from the <code>songs</code> table, rounded to a single decimal point.</p>
<h2 id="heading-chapter-8-sql-subqueries">Chapter 8: SQL Subqueries</h2>
<h3 id="heading-subqueries">Subqueries</h3>
<p>Sometimes a single query is not enough to retrieve the specific records we need.</p>
<p>It is possible to run a query on the result set of another query - a query within a query! This is called "query-ception"... erm... I mean a "subquery".</p>
<p>Subqueries can be very useful in a number of situations when trying to retrieve specific data that wouldn't be accessible by simply querying a single table.</p>
<h4 id="heading-how-to-retreive-data-from-multiple-tables">How to retreive data from multiple tables</h4>
<p>Here is an example of a subquery:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">id</span>, song_name, artist_id
<span class="hljs-keyword">FROM</span> songs
<span class="hljs-keyword">WHERE</span> artist_id <span class="hljs-keyword">IN</span> (
    <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">id</span>
    <span class="hljs-keyword">FROM</span> artists
    <span class="hljs-keyword">WHERE</span> artist_name <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'Rick%'</span>
);
</code></pre>
<p>In this hypothetical database, the query above selects all of the <code>song_id</code>s, <code>song_name</code>s, and <code>artist_id</code>s from the <code>songs</code> table that are written by artists whose name starts with "Rick". Notice that the subquery allows us to use information from a different table - in this case the <code>artists</code> table.</p>
<h4 id="heading-subquery-syntax">Subquery syntax</h4>
<p>The only syntax unique to a subquery is the parentheses surrounding the nested query. The <code>IN</code> operator could be different, for example, we could use the <code>=</code> operator if we expect a single value to be returned.</p>
<p>Here's an example:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">id</span>, song_name, artist_id
<span class="hljs-keyword">FROM</span> songs
<span class="hljs-keyword">WHERE</span> artist_id <span class="hljs-keyword">IN</span> (
    <span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">id</span>
    <span class="hljs-keyword">FROM</span> artists
    <span class="hljs-keyword">WHERE</span> artist_name <span class="hljs-keyword">LIKE</span> <span class="hljs-string">'Rick%'</span>
);
</code></pre>
<h3 id="heading-no-tables-necessary">No tables necessary</h3>
<p>When working on a back-end application, this doesn't come up often, but it's important to remember that <strong>SQL is a full programming language</strong>. We usually use it to interact with data stored in tables, but it's quite flexible and powerful.</p>
<p>For example, you can <code>SELECT</code> information that's simply calculated, with no tables necessary.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> <span class="hljs-number">5</span> + <span class="hljs-number">10</span> <span class="hljs-keyword">as</span> <span class="hljs-keyword">sum</span>;
</code></pre>
<h2 id="heading-chapter-9-database-normalization">Chapter 9: Database Normalization</h2>
<h3 id="heading-table-relationships">Table Relationships</h3>
<p>Relational databases are powerful because of the relationships between the tables. These relationships help us to keep our databases clean and efficient. </p>
<p>A relationship between tables assumes that one of these tables has a <code>foreign key</code> that references the <code>primary key</code> of another table.</p>
<p>@<a target="_blank" href="https://www.youtube.com/watch?v=WJTdg1AsSz0">youtube</a></p>
<h4 id="heading-types-of-relationships">Types of Relationships</h4>
<p>There are 3 primary types of relationships in a relational database:</p>
<ol>
<li>One-to-one</li>
<li>One-to-many</li>
<li>Many-to-many</li>
</ol>
<p><img src="https://i.imgur.com/u4i6XdL.png" alt="relationships" width="763" height="340" loading="lazy"></p>
<h3 id="heading-one-to-one">One-to-one</h3>
<p>A <code>one-to-one</code> relationship most often manifests as a field or set of fields on a row in a table. For example, a <code>user</code> will have exactly one <code>password</code>.</p>
<p>Settings fields might be another example of a one-to-one relationship. A user will have exactly one <code>email_preference</code> and exactly one <code>birthday</code>.</p>
<h3 id="heading-one-to-many">One to many</h3>
<p>When talking about the relationships between tables, a one-to-many relationship is probably the most commonly used relationship. </p>
<p>A one-to-many relationship occurs when a single record in one table is related to potentially many records in another table. </p>
<p>Note that the one-&gt;many relation only goes one way, a record in the second table can not be related to multiple records in the first table!</p>
<h4 id="heading-examples-of-one-to-many-relationships">Examples of one-to-many relationships</h4>
<ul>
<li>A <code>customers</code> table and a <code>orders</code> table. Each customer has <code>0</code>, <code>1</code>, or many orders that they've placed.</li>
<li>A <code>users</code> table and a <code>transactions</code> table. Each <code>user</code> has <code>0</code>, <code>1</code>, or many transactions that taken part in.</li>
</ul>
<h3 id="heading-many-to-many">Many to many</h3>
<p>A many-to-many relationship occurs when multiple records in one table can be related to multiple records in another table.</p>
<h4 id="heading-examples-of-many-to-many-relationships">Examples of many-to-many relationships</h4>
<ul>
<li>A <code>products</code> table and a <code>suppliers</code> table - Products may have <code>0</code> to many suppliers, and suppliers can supply <code>0</code> to many products.</li>
<li>A <code>classes</code> table and a <code>students</code> table - Students can take potentially many classes and classes can have many students enrolled.</li>
</ul>
<h4 id="heading-joining-tables">Joining tables</h4>
<p>Joining tables helps define many-to-many relationships between data in a database. As an example, when defining the relationship above between products and suppliers, we would define a joining table called <code>products_suppliers</code> that contains the primary keys from the tables to be joined.</p>
<p>Then, when we want to see if a supplier supplies a specific product, we can look in the joining table to see if the ids share a row.</p>
<h4 id="heading-unique-constraints-across-2-fields">Unique constraints across 2 fields</h4>
<p>When enforcing specific schema constraints we may need to enforce the <code>UNIQUE</code> constraint across two different fields.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> product_suppliers (
  product_id <span class="hljs-built_in">INTEGER</span>,
  supplier_id <span class="hljs-built_in">INTEGER</span>,
  <span class="hljs-keyword">UNIQUE</span>(product_id, supplier_id)
);
</code></pre>
<p>This ensures that we can have multiple rows with the same <code>product_id</code> or <code>supplier_id</code>, but we can't have two rows where both the <code>product_id</code> and <code>supplier_id</code> are the same.</p>
<h3 id="heading-database-normalization">Database normalization</h3>
<p>Database normalization is a method for structuring your database schema in a way that helps:</p>
<ul>
<li>Improve data integrity</li>
<li>Reduce data redundancy</li>
</ul>
<h4 id="heading-what-is-data-integrity">What is data integrity?</h4>
<p>"Data integrity" refers to the accuracy and consistency of data. For example, if a user's age is stored in a database, rather than their birthday, that data becomes incorrect automatically with the passage of time.</p>
<p>It would be better to store a birthday and calculate the age as needed.</p>
<h4 id="heading-what-is-data-redundancy">What is data redundancy?</h4>
<p>"Data redundancy" occurs when the same piece of data is stored in multiple places. For example: saving the same file multiple times to different hard drives.</p>
<p>Data redundancy can be problematic, especially when data in one place is changed such that the data is no longer consistent across all copies of that data.</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/U3L4NYNwb6k" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h3 id="heading-normal-forms">Normal Forms</h3>
<p>The creator of "database normalization", <a target="_blank" href="https://en.wikipedia.org/wiki/Edgar_F._Codd">Edgar F. Codd</a>, described different "normal forms" a database can adhere to. We'll talk about the most common ones.</p>
<ul>
<li>First normal form (1NF)</li>
<li>Second normal form (2NF)</li>
<li>Third normal form (3NF)</li>
<li>Boyce-Codd normal form (BCNF)</li>
</ul>
<p><img src="https://i.imgur.com/CpDOeej.png" alt="normal forms" width="300" height="275" loading="lazy"></p>
<p>In short, 1st normal form is the least "normalized" form, and Boyce-Codd is the most "normalized" form.</p>
<p>The more normalized a database, the better its data integrity, and the less duplicate data you'll have.</p>
<h4 id="heading-in-the-context-of-normal-forms-primary-key-means-something-a-bit-different">In the context of normal forms, "primary key" means something a bit different</h4>
<p>In the context of database normalization, we're going to use the term "primary key" slightly differently. When we're talking about SQLite, a "primary key" is a single column that uniquely identifies a row.</p>
<p>When we're talking more generally about data normalization, the term "primary key" means the collection of columns that uniquely identify a row. That can be a single column, but it can actually be any number of columns. A primary key is the minimum number of columns needed to uniquely identify a row in a table. </p>
<p>If you think back to the many-to-many joining table <code>product_suppliers</code>, that table's "primary key" was actually a combination of the 2 ids, <code>product_id</code> and <code>supplier_id</code>:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> product_suppliers (
    product_id <span class="hljs-built_in">INTEGER</span>,
    supplier_id <span class="hljs-built_in">INTEGER</span>,
    <span class="hljs-keyword">UNIQUE</span>(product_id, supplier_id)
);
</code></pre>
<h3 id="heading-1st-normal-form-1nf">1st Normal Form (1NF)</h3>
<p>To be compliant with <a target="_blank" href="https://en.wikipedia.org/wiki/First_normal_form">first normal form</a>, a database table simply needs to follow 2 rules:</p>
<ul>
<li>It must have a unique primary key.</li>
<li>A cell can't have a nested table as its value (depending on the database you're using, this may not even be possible)</li>
</ul>
<h4 id="heading-example-of-not-1st-normal-form">Example of NOT 1st normal form</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>name</td><td>age</td><td>email</td></tr>
</thead>
<tbody>
<tr>
<td>Lane</td><td>27</td><td>lane@boot.dev</td></tr>
<tr>
<td>Lane</td><td>27</td><td>lane@boot.dev</td></tr>
<tr>
<td>Allan</td><td>27</td><td>allan@boot.dev</td></tr>
</tbody>
</table>
</div><p>This table does not adhere to 1NF. It has two identical rows, so there isn't a unique primary key for each row.</p>
<h4 id="heading-example-of-1st-normal-form">Example of 1st normal form</h4>
<p>The simplest way (but not the only way) to get into first normal form is to add a unique <code>id</code> column.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>id</td><td>name</td><td>age</td><td>email</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>Lane</td><td>27</td><td>lane@boot.dev</td></tr>
<tr>
<td>2</td><td>Lane</td><td>27</td><td>lane@boot.dev</td></tr>
<tr>
<td>3</td><td>Allan</td><td>27</td><td>allan@boot.dev</td></tr>
</tbody>
</table>
</div><p>It's worth noting that if you create a "primary key" by ensuring that two columns are always "unique together" that works too.</p>
<h4 id="heading-you-should-almost-never-design-a-table-that-doesnt-adhere-to-1nf">You should <em>almost</em> never design a table that doesn't adhere to 1NF</h4>
<p>First normal form is simply a good idea. I've never built a database schema where each table isn't at least in first normal form.</p>
<h3 id="heading-2nd-normal-form-2nf">2nd Normal Form (2NF)</h3>
<p>A table in <a target="_blank" href="https://en.wikipedia.org/wiki/Second_normal_form">second normal form</a> follows all the rules of 1st normal form, and one additional rule:</p>
<ul>
<li>All columns that are not part of the primary key are dependent on the entire primary key, and not just one of the columns in the primary key.</li>
</ul>
<h4 id="heading-example-of-1st-nf-but-not-2nd-nf">Example of 1st NF, but not 2nd NF</h4>
<p>In this table, the primary key is a combination of <code>first_name</code> + <code>last_name</code>.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>first_name</td><td>last_name</td><td>first_initial</td></tr>
</thead>
<tbody>
<tr>
<td>Lane</td><td>Wagner</td><td>l</td></tr>
<tr>
<td>Lane</td><td>Small</td><td>l</td></tr>
<tr>
<td>Allan</td><td>Wagner</td><td>a</td></tr>
</tbody>
</table>
</div><p>This table does not adhere to 2NF. The <code>first_initial</code> column is entirely dependent on the <code>first_name</code> column, rendering it redundant.</p>
<h4 id="heading-example-of-2nd-normal-form">Example of 2nd normal form</h4>
<p>One way to convert the table above to 2NF is to add a new table that maps a <code>first_name</code> directly to its <code>first_initial</code>. This removes any duplicates:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>first_name</td><td>last_name</td></tr>
</thead>
<tbody>
<tr>
<td>Lane</td><td>Wagner</td></tr>
<tr>
<td>Lane</td><td>Small</td></tr>
<tr>
<td>Allan</td><td>Wagner</td></tr>
</tbody>
</table>
</div><div class="hn-table">
<table>
<thead>
<tr>
<td>first_name</td><td>first_initial</td></tr>
</thead>
<tbody>
<tr>
<td>Lane</td><td>l</td></tr>
<tr>
<td>Allan</td><td>a</td></tr>
</tbody>
</table>
</div><h4 id="heading-2nf-is-usually-a-good-idea">2NF is <em>usually</em> a good idea</h4>
<p>You should probably default to keeping your tables in second normal form. That said, there are good reasons to deviate from it, particularly for performance reasons. The reason being that when you have query a second table to get additional data it can take a bit longer.</p>
<p>My rule of thumb is:</p>
<blockquote>
<p>Optimize for data integrity and data de-duplication first. If you have speed issues, de-normalize accordingly.</p>
</blockquote>
<h3 id="heading-3rd-normal-form-3nf">3rd Normal Form (3NF)</h3>
<p>A table in <a target="_blank" href="https://en.wikipedia.org/wiki/Third_normal_form">3rd normal form</a> follows all the rules of 2nd normal form, and one additional rule:</p>
<ul>
<li>All columns that aren't part of the primary are dependent solely on the primary key.</li>
</ul>
<p>Notice that this is only slightly different from second normal form. In second normal form we can't have a column completely dependent on a part of the primary key, and in third normal form we can't have a column that is entirely dependent on anything that isn't the entire primary key.</p>
<h4 id="heading-example-of-2nd-nf-but-not-3rd-nf">Example of 2nd NF, but not 3rd NF</h4>
<p>In this table, the primary key is simply the <code>id</code> column.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>id</td><td>name</td><td>first_initial</td><td>email</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>Lane</td><td>l</td><td>lane.works@example.com</td></tr>
<tr>
<td>2</td><td>Breanna</td><td>b</td><td>breanna@example.com</td></tr>
<tr>
<td>3</td><td>Lane</td><td>l</td><td>lane.right@example.com</td></tr>
</tbody>
</table>
</div><p>This table is in 2nd normal form because <code>first_initial</code> is not dependent on a part of the primary key. However, because it is dependent on the <code>name</code> column it doesn't adhere to 3rd normal form.</p>
<h4 id="heading-example-of-3rd-normal-form">Example of 3rd normal form</h4>
<p>The way to convert the table above to 3NF is to add a new table that maps a <code>name</code> directly to its <code>first_initial</code>. Notice how similar this solution is to 2NF.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>id</td><td>name</td><td>email</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>Lane</td><td>lane.works@example.com</td></tr>
<tr>
<td>2</td><td>Breanna</td><td>breanna@example.com</td></tr>
<tr>
<td>3</td><td>Lane</td><td>lane.right@example.com</td></tr>
</tbody>
</table>
</div><div class="hn-table">
<table>
<thead>
<tr>
<td>name</td><td>first_initial</td></tr>
</thead>
<tbody>
<tr>
<td>Lane</td><td>l</td></tr>
<tr>
<td>Breanna</td><td>b</td></tr>
</tbody>
</table>
</div><h4 id="heading-3nf-is-usually-a-good-idea">3NF is <em>usually</em> a good idea</h4>
<p>The same exact rule of thumb applies to the second and third normal forms.</p>
<blockquote>
<p>Optimize for data integrity and data de-duplication first by adhering to 3NF. If you have speed issues, de-normalize accordingly.</p>
</blockquote>
<p>Remember the <a target="_blank" href="https://www.sqlitetutorial.net/sqlite-functions/sqlite-iif/">IIF function</a> and the <code>AS</code> clause.</p>
<h3 id="heading-boyce-codd-normal-form-bcnf">Boyce-Codd Normal Form (BCNF)</h3>
<p>A table in <a target="_blank" href="https://en.wikipedia.org/wiki/Boyce%E2%80%93Codd_normal_form">Boyce-Codd normal form</a> (created by <a target="_blank" href="https://en.wikipedia.org/wiki/Raymond_F._Boyce">Raymond F Boyce</a> and <a target="_blank" href="https://en.wikipedia.org/wiki/Edgar_F._Codd">Edgar F Codd</a>) follows all the rules of 3rd normal form, plus one additional rule:</p>
<ul>
<li>A column that's part of a primary key can not be entirely dependent on a column that's not part of that primary key.</li>
</ul>
<p>This only comes into play when there are multiple possible primary key combinations that overlap. Another name for this is "overlapping candidate keys".</p>
<p>Only in rare cases does a table in third normal form not meet the requirements of Boyce-Codd normal form.</p>
<h4 id="heading-example-of-3rd-nf-but-not-boyce-codd-nf">Example of 3rd NF, but not Boyce-Codd NF</h4>
<div class="hn-table">
<table>
<thead>
<tr>
<td>release_year</td><td>release_date</td><td>sales</td><td>name</td></tr>
</thead>
<tbody>
<tr>
<td>2001</td><td>2001-01-02</td><td>100</td><td>Kiss me tender</td></tr>
<tr>
<td>2001</td><td>2001-02-04</td><td>200</td><td>Bloody Mary</td></tr>
<tr>
<td>2002</td><td>2002-04-14</td><td>100</td><td>I wanna be them</td></tr>
<tr>
<td>2002</td><td>2002-06-24</td><td>200</td><td>He got me</td></tr>
</tbody>
</table>
</div><p>The interesting thing here is that there are 3 possible primary keys:</p>
<ul>
<li><code>release_year</code> + <code>sales</code></li>
<li><code>release_date</code> + <code>sales</code></li>
<li><code>name</code></li>
</ul>
<p>This means that by definition this table is in 2nd and 3rd normal form because those forms only restrict how dependent a column that is not part of a primary key can be.</p>
<p>This table is not in Boyce-Codd's normal form because <code>release_year</code> is entirely dependent on <code>release_date</code>.</p>
<h4 id="heading-example-of-boyce-codd-normal-form">Example of Boyce-Codd normal form</h4>
<p>The easiest way to fix the table in our example is to simply remove the duplicate data from <code>release_date</code>. Let's make that column <code>release_day_and_month</code>.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>release_year</td><td>release_day_and_month</td><td>sales</td><td>name</td></tr>
</thead>
<tbody>
<tr>
<td>2001</td><td>01-02</td><td>100</td><td>Kiss me tender</td></tr>
<tr>
<td>2001</td><td>02-04</td><td>200</td><td>Bloody Mary</td></tr>
<tr>
<td>2002</td><td>04-14</td><td>100</td><td>I wanna be them</td></tr>
<tr>
<td>2002</td><td>06-24</td><td>200</td><td>He got me</td></tr>
</tbody>
</table>
</div><h4 id="heading-bcnf-is-usually-a-good-idea">BCNF is <em>usually</em> a good idea</h4>
<p>The same exact rule of thumb applies to the 2nd, 3rd and Boyce-Codd normal forms. That said, it's unlikely you'll see BCNF-specific issues in practice.</p>
<blockquote>
<p>Optimize for data integrity and data de-duplication first by adhering to Boyce-Codd normal form. If you have speed issues, de-normalize accordingly.</p>
</blockquote>
<h3 id="heading-normalization-review">Normalization Review</h3>
<p>In my opinion, the exact definitions of 1st, 2nd, 3rd and Boyce-Codd normal forms simply are not all that important in your work as a back-end developer.</p>
<p>However, what is important is to understand the basic principles of data integrity and data redundancy that the normal forms teach us. </p>
<p>Let's go over some rules of thumb that you should commit to memory - they'll serve you well when you design databases and even just in coding interviews.</p>
<h4 id="heading-rules-of-thumb-for-database-design">Rules of thumb for database design</h4>
<ol>
<li>Every table should always have a unique identifier (primary key)</li>
<li>90% of the time, that unique identifier will be a single column named <code>id</code></li>
<li>Avoid duplicate data</li>
<li>Avoid storing data that is completely dependent on other data. Instead, compute it on the fly when you need it.</li>
<li>Keep your schema as simple as you can. Optimize for a normalized database first. Only denormalize for speed's sake when you start to run into performance problems.</li>
</ol>
<p>We'll talk more about speed optimization in a later chapter.</p>
<h2 id="heading-chapter-10-how-to-join-tables-in-sql">Chapter 10: How to Join Tables in SQL</h2>
<p>Joins are one of the most important features that SQL offers. Joins allow us to make use of the relationships we have set up between our tables. In short, joins allow us to query multiple tables at the same time.</p>
<h3 id="heading-inner-join"><code>INNER JOIN</code></h3>
<p>The simplest and most common type of join in SQL is the <code>INNER JOIN</code>. By default, a <code>JOIN</code> command is an <code>INNER JOIN</code>. </p>
<p>An <code>INNER JOIN</code> returns all of the records in <code>table_a</code> that have matching records in <code>table_b</code>, as demonstrated by the following Venn diagram.</p>
<p><img src="https://i.imgur.com/wgxAmhA.png" alt="inner join" width="421" height="293" loading="lazy"></p>
<h4 id="heading-the-on-clause">The <code>ON</code> clause</h4>
<p>In order to perform a join, we need to tell the database which fields should be "matched up". The  <code>ON</code> clause is used to specify these columns to join.</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> *
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">INNER</span> <span class="hljs-keyword">JOIN</span> departments 
<span class="hljs-keyword">ON</span> employees.department_id = departments.id;
</code></pre>
<p>The query above returns all the fields from both tables. The <code>INNER</code> keyword doesn't have anything to do with the number of columns returned - it only affects the number of rows returned.</p>
<h3 id="heading-namespacing-on-tables">Namespacing on Tables</h3>
<p>When working with multiple tables, you can specify which table a field exists on using a <code>.</code>. For example:</p>
<p><code>table_name.column_name</code></p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> students.name, classes.name
<span class="hljs-keyword">FROM</span> students
<span class="hljs-keyword">INNER</span> <span class="hljs-keyword">JOIN</span> classes <span class="hljs-keyword">on</span> classes.class_id = students.class_id;
</code></pre>
<p>The above query returns the <code>name</code> field from the <code>students</code> table and the <code>name</code> field from the <code>classes</code> table. </p>
<h3 id="heading-left-join"><code>LEFT JOIN</code></h3>
<p>A <code>LEFT JOIN</code> will return every record from <code>table_a</code> regardless of whether or not any of those records have a match in <code>table_b</code>. A left join will also return any matching records from <code>table_b</code>. </p>
<p>Here is a Venn diagram to help visualize the effect of a <code>LEFT JOIN</code>.</p>
<p><img src="https://i.imgur.com/mNbhWfM.png" alt="left-join" width="292" height="178" loading="lazy"></p>
<p>A small trick you can do to make writing the SQL query easier is define an <a target="_blank" href="https://en.wikipedia.org/wiki/Alias_(SQL)">alias</a> for each table. Here's an example:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">SELECT</span> e.name, d.name
<span class="hljs-keyword">FROM</span> employees e
<span class="hljs-keyword">LEFT</span> <span class="hljs-keyword">JOIN</span> departments d
<span class="hljs-keyword">ON</span> e.department_id = d.id;
</code></pre>
<p>Notice the simple alias declarations <code>e</code> and <code>d</code> for <code>employees</code> and <code>departments</code> respectively.</p>
<p>Some developers do this to make their queries less verbose. That said, I personally hate it because single-letter variables are harder to understand the meaning of.</p>
<h3 id="heading-right-join"><code>RIGHT JOIN</code></h3>
<p>A <code>RIGHT JOIN</code> is, as you may expect, the opposite of a <code>LEFT JOIN</code>. It returns all records from <code>table_b</code> regardless of matches, and all matching records between the two tables.</p>
<p><img src="https://i.imgur.com/LG6Y43j.png" alt="right-join" width="652" height="352" loading="lazy"></p>
<h4 id="heading-sqlite-restriction">SQLite Restriction</h4>
<p>SQLite does not support right joins, but many dialects of SQL do. If you think about it, a <code>RIGHT JOIN</code> is just a <code>LEFT JOIN</code> with the order of the tables switched, so it's not a big deal that SQLite doesn't support the syntax.</p>
<h3 id="heading-full-join"><code>FULL JOIN</code></h3>
<p>A <code>FULL JOIN</code> combines the result set of the <code>LEFT JOIN</code> and <code>RIGHT JOIN</code> commands. It returns all records from both from <code>table_a</code> and <code>table_b</code> regardless of whether or not they have matches.</p>
<p><img src="https://i.imgur.com/Kk3k1Ub.png" alt="Full-join" width="606" height="344" loading="lazy"></p>
<h4 id="heading-sqlite">SQLite</h4>
<p>Like <code>RIGHT JOIN</code>s, SQLite doesn't support <code>FULL JOIN</code>s but they are still important to know.</p>
<h2 id="heading-chapter-11-database-performance">Chapter 11: Database Performance</h2>
<h3 id="heading-sql-indexes">SQL Indexes</h3>
<p>An index is an in-memory structure that ensures that queries we run on a database are performant, that is to say, they run quickly. </p>
<p>If you've learned about data structures, most database indexes are just <a target="_blank" href="https://en.wikipedia.org/wiki/Binary_tree">binary trees</a>. The binary tree can be stored in <a target="_blank" href="https://en.wikipedia.org/wiki/Random-access_memory">ram</a> as well as on <a target="_blank" href="https://en.wikipedia.org/wiki/Computer_data_storage">disk</a>, and it makes it easy to lookup the location of an entire row.</p>
<p><code>PRIMARY KEY</code> columns are indexed by default, ensuring you can look up a row by its <code>id</code> very quickly. But if you have other columns that you want to be able to do quick lookups on, you'll need to index them.</p>
<h4 id="heading-create-index"><code>CREATE INDEX</code></h4>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> index_name <span class="hljs-keyword">on</span> table_name (column_name);
</code></pre>
<p>It's fairly common to name an index after the column it's created on with a suffix of <code>_idx</code>.</p>
<h3 id="heading-index-review">Index Review</h3>
<p>As we discussed, an index is a data structure that can perform quick lookups. By indexing a column, we create a new in-memory structure, usually a binary-tree, where the values in the indexed column are sorted into the tree to keep lookups fast. </p>
<p>In terms of Big-O complexity, a binary tree index ensures that lookups are <a target="_blank" href="https://en.wikipedia.org/wiki/Big_O_notation">O(log(n))</a>.</p>
<h4 id="heading-shouldnt-we-index-everything-we-can-make-the-database-ultra-fast">Shouldn't we index everything? We can make the database ultra-fast!</h4>
<p>While indexes make specific kinds of lookups much faster, they also add performance overhead - they can slow down a database in other ways. </p>
<p>Think about it: if you index every column, you could have hundreds of binary trees in memory. That needlessly bloats the memory usage of your database. It also means that each time you insert a record, that record needs to be added to many trees - slowing down your insert speed.</p>
<p>The rule of thumb is simple:</p>
<blockquote>
<p>Add an index to columns you know you'll be doing frequent lookups on. Leave everything else un-indexed. You can always add indexes later.</p>
</blockquote>
<h3 id="heading-multi-column-indexes">Multi-column indexes</h3>
<p>Multi-column indexes are useful for the exact reason you might think - they speed up lookups that depend on multiple columns. </p>
<h4 id="heading-create-index-1"><code>CREATE INDEX</code></h4>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> first_name_last_name_age_idx
<span class="hljs-keyword">ON</span> <span class="hljs-keyword">users</span> (first_name, last_name, age);
</code></pre>
<p>A multi-column index is sorted by the first column first, the second column next, and so forth. A lookup on only the first column in a multi-column index gets almost all of the performance improvements that it would get from its own single-column index. But lookups on only the second or third column will have very degraded performance.</p>
<h4 id="heading-rule-of-thumb">Rule of thumb</h4>
<p>Unless you have specific reasons to do something special, only add multi-column indexes if you're doing frequent lookups on a specific combination of columns.</p>
<h3 id="heading-denormalizing-for-speed">Denormalizing for speed</h3>
<p>I left you with a cliffhanger in the "normalization" chapter. As it turns out, data integrity and deduplication come at a cost, and that cost is usually speed.</p>
<p>Joining tables together, using subqueries, performing aggregations, and running post-hoc calculations all take time. At very large scales these advanced techniques can actually take a huge performance toll on an application - sometimes grinding the database server to a halt.</p>
<p>Storing duplicate information can drastically speed up an application that needs to look it up in different ways. For example, if you store a user's country information right on their user record, no expensive join is required to load their profile page.</p>
<p>That said, denormalize at your own risk. Denormalizing a database incurs a large risk of inaccurate and buggy data.</p>
<p>In my opinion, it should be used as a kind of "last resort" in the name of speed.</p>
<h3 id="heading-sql-injection-1">SQL Injection</h3>
<p>SQL is a very common way hackers attempt to cause damage or breach a database. One of my favorite <a target="_blank" href="https://xkcd.com/327/">XKCD</a> comics of all time demonstrates the problem:</p>
<p><img src="https://bobby-tables.com/img/xkcd.png" alt="bobby tables" width="666" height="205" loading="lazy"></p>
<p>The joke here is that if someone was using this query:</p>
<pre><code class="lang-SQL"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> students(<span class="hljs-keyword">name</span>) <span class="hljs-keyword">VALUES</span> (?);
</code></pre>
<p>And the "name" of a student was <code>'Robert'); DROP TABLE students;--</code> then the resulting SQL query would look like this:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> students(<span class="hljs-keyword">name</span>) <span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'Robert'</span>); <span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> students;<span class="hljs-comment">--)</span>
</code></pre>
<p>As you can see, this is actually 2 queries! The first one inserts "Robert" into the database, and the second one deletes the students table!</p>
<h4 id="heading-how-do-we-protect-against-sql-injection">How do we protect against SQL injection?</h4>
<p>You need to be aware of SQL injection attacks, but to be honest the solution these days is to simply use a modern SQL library that sanitizes SQL inputs. We don't often need to sanitize inputs by hand at the application level anymore.</p>
<p>For example, the Go standard library's SQL packages automatically protects your inputs against SQL attacks if you <a target="_blank" href="https://go.dev/doc/database/sql-injection">use it properly</a>. In short, don't interpolate user input into raw strings yourself - make sure your database library has a way to sanitize inputs, and pass it those raw values.</p>
<h2 id="heading-congratulations-on-making-it-to-the-end">Congratulations on making it to the end!</h2>
<p>If you're interested in doing the interactive coding assignments and quizzes for this course, you can check out the <a target="_blank" href="https://www.boot.dev/learn/learn-sql">Learn SQL Course</a> course over on <a target="_blank" href="https://www.boot.dev/">Boot.dev</a></p>
<p>This course is a part of my full back-end developer career path, made up of other courses and projects if you're interested in checking those out.</p>
<p>If you want to see the other content I'm creating related to web development, check out some of my links below:</p>
<p><a target="_blank" href="https://www.backendbanter.fm/">Lane's Podcast: Backend Banter</a>
<a target="_blank" href="https://twitter.com/wagslane">Lane on Twitter</a>
<a target="_blank" href="https://www.youtube.com/@bootdotdev">Lane on YouTube</a></p>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
