<?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[ postgres - 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[ postgres - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sat, 16 May 2026 19:39:28 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/postgres/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Develop a CRUD App with Spring Boot, Neon Postgres, and Azure App Service ]]>
                </title>
                <description>
                    <![CDATA[ In this article, we'll explore how to develop a CRUD (Create, Read, Update, Delete) application using Spring Boot and Neon Postgres. We'll also deploy the application on Azure App Service and make it production-ready by setting up features like autos... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-a-crud-app-spring-boot-neon-postgres/</link>
                <guid isPermaLink="false">66c3762340438b5931fe0a0b</guid>
                
                    <category>
                        <![CDATA[ Azure ]]>
                    </category>
                
                    <category>
                        <![CDATA[ crud ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ spring-boot ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Abhinav Pandey ]]>
                </dc:creator>
                <pubDate>Fri, 26 Jul 2024 19:14:36 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/neon-banner.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this article, we'll explore how to develop a CRUD (Create, Read, Update, Delete) application using Spring Boot and <a target="_blank" href="https://neon.tech/">Neon Postgres</a>.</p>
<p>We'll also deploy the application on <a target="_blank" href="https://azure.microsoft.com/en-us/products/app-service">Azure App Service</a> and make it production-ready by setting up features like autoscaling and multiple environments.</p>
<p>You'll learn how Neon Postgres can make your development and deployment processes easier along the way.</p>
<h2 id="heading-heres-what-well-cover">Here's what we'll cover:</h2>
<ul>
<li>Setting up a Neon Postgres database and exploring its features</li>
<li>Building a CRUD application using Spring Boot and deploying the application on Azure App Service</li>
<li>Why Neon is a good fit for infrastructure that auto-scales</li>
<li>Database branching in Neon Postgres and how it can ease the development workflow</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>Working knowledge of Java, Maven, and Spring Boot</li>
<li>Basics of SQL databases</li>
<li>Understanding of serverless and cloud services</li>
<li>Familiarity with testing and deployment processes</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-neon-postgres">What is Neon Postgres?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-the-database">How to Set Up the Database</a><ul>
<li><a class="post-section-overview" href="#heading-create-the-database">Create the Database</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-spring-boot-crud-app">How to Build the Spring Boot CRUD App</a><ul>
<li><a class="post-section-overview" href="#heading-create-an-entity-class">Create an Entity Class</a></li>
<li><a class="post-section-overview" href="#heading-create-a-repository">Create a Repository</a></li>
<li><a class="post-section-overview" href="#heading-create-a-rest-controller">Create a REST Controller</a></li>
<li><a class="post-section-overview" href="#heading-configure-the-database">Configure the Database</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-deploy-on-azure-app-service">How to Deploy on Azure App Service</a><ul>
<li><a class="post-section-overview" href="#heading-create-a-new-web-app">Create a New Web App</a></li>
<li><a class="post-section-overview" href="#heading-deploy-the-application">Deploy the Application</a></li>
<li><a class="post-section-overview" href="#heading-access-the-application">Access the Application</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-autoscaling">How to Set Up Autoscaling</a><ul>
<li><a class="post-section-overview" href="#heading-autoscaling-in-azure">Autoscaling in Azure</a></li>
<li><a class="post-section-overview" href="#heading-autoscaling-in-neon">Autoscaling in Neon</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-configure-database-branches-in-neon">How to Configure Database Branches in Neon</a></li>
<li><a class="post-section-overview" href="#heading-summary">Summary</a></li>
</ul>
<h2 id="heading-what-is-neon-postgres">What is Neon Postgres?</h2>
<p>Neon is a fully managed serverless Postgres database platform. It offers features such as high availability, automatic backups, and scaling options to handle varying traffic levels.</p>
<p>Neon is designed to be cost-efficient and developer-friendly, and it focuses on providing a seamless experience for developers.</p>
<p>In addition to the standard Postgres features, it provides capabilities like database branching, allowing you to create Git-like branches of the database for different purposes.</p>
<h2 id="heading-how-to-set-up-the-database">How to Set Up the Database</h2>
<p>To begin with, let's explore how you can set up a Neon database for your application.</p>
<p>Firstly, you'll need to <a target="_blank" href="https://console.neon.tech/signup">create an account</a> on the Neon website. It doesn't require a credit card to sign up, and you're automatically set up with the free tier to get started.</p>
<p>Here's a <a target="_blank" href="https://neon.tech/pricing">pricing and features comparison</a> of Neon plans:</p>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finxumg46sf92ffre6l2q.png" alt="A screenshot of pricing plans in Neon listing down free and paid features" width="600" height="400" loading="lazy">
<em>Neon pricing plans</em></p>
<p>In the free tier, we get 0.5 GB of storage with basic computing which is enough for playing around with the database and building small applications.</p>
<h3 id="heading-create-the-database">Create the Database</h3>
<p>Once you've signed up, you can access the dashboard and create a new project.</p>
<p>Star by filling in the project name, region, and Postgres version options. In addition to this, we can choose two additional options:</p>
<ul>
<li><strong>compute size</strong> – You can choose a min and max compute size for the database. This is useful for autoscaling the database based on the load.</li>
<li><strong>suspend time</strong> – You can set a time after which the database will be suspended if not being used. This is useful for saving costs when the database is not being used.</li>
</ul>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggwuvqtb8ydl3mxd1dak.png" alt="Form with specifications required when creating a database" width="600" height="400" loading="lazy">
<em>Creating a database project in Neon</em></p>
<p>Once you submit the form, Neon will create the database and provide the connection details.</p>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwe2x5d81euphg2owgxhd.png" alt="Neon Dashboard showing the project is ready. Also shows connection details." width="600" height="400" loading="lazy">
<em>Neon Dashboard</em></p>
<p>As you can see, the database was set up in 3.3 seconds (compared to hours of installing and setting up your own infrastructure). You can choose multiple ways to connect to the database. For this tutorial, select Java as your programming language and get the JDBC connection string.</p>
<h2 id="heading-how-to-build-the-spring-boot-crud-app">How to Build the Spring Boot CRUD App</h2>
<p>Next, let's set up our CRUD application. We'll use Spring Boot, as it provides easy bootstrapping and configuration for building web applications.</p>
<p>We can use the <a target="_blank" href="https://start.spring.io/">Spring Initializr</a> to generate a new Spring Boot project with the necessary dependencies:</p>
<ul>
<li>Spring Web – for building web applications</li>
<li>Spring Data JPA – for working with databases using JPA</li>
<li>PostGres Driver – for connecting to the Postgres database</li>
</ul>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffifv17tc5d3swothe3zf.png" alt="Spring Initializer website form to select spring boot project specifications and dependencies" width="600" height="400" loading="lazy">
<em>Creating a Spring Boot project using Spring Initializer</em></p>
<p>You can generate, download, and import the project into your favorite IDE.</p>
<h3 id="heading-create-an-entity-class">Create an Entity Class</h3>
<p>Let's create an entity class to represent the data in the application. First, create a <code>User</code> class:</p>
<pre><code class="lang-java"><span class="hljs-meta">@Entity(name = "users")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> </span>{
    <span class="hljs-meta">@Id</span>
    <span class="hljs-meta">@GeneratedValue(strategy = GenerationType.IDENTITY)</span>
    <span class="hljs-keyword">private</span> Long id;
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> String email;

    <span class="hljs-comment">// Constructors, Getters and Setters</span>
}
</code></pre>
<p>The entity name <code>users</code> is the name of the table you want to use in your database.</p>
<h3 id="heading-create-a-repository">Create a Repository</h3>
<p>Next, create a repository interface to interact with the database. You'll extend the <code>JpaRepository</code> interface provided by Spring Data JPA:</p>
<pre><code class="lang-java"><span class="hljs-meta">@Repository</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">UserRepository</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">JpaRepository</span>&lt;<span class="hljs-title">User</span>, <span class="hljs-title">Long</span>&gt; </span>{
}
</code></pre>
<p>You need to annotate the interface with <code>@Repository</code> to mark it as a Spring bean. The <code>JpaRepository</code> interface provides methods for CRUD operations like <code>save</code>, <code>findAll</code>, <code>findById</code>, <code>delete</code>, and so on, so you don't need to write the queries manually.</p>
<p>You'll provide your entity class <code>User</code> and the type of the primary key <code>Long</code> as type arguments to the <code>JpaRepository</code> interface.</p>
<h3 id="heading-create-a-rest-controller">Create a REST Controller</h3>
<p>Finally, create a REST controller to handle the CRUD operations. You'll inject the <code>UserRepository</code> into the controller and implement the necessary endpoints:</p>
<pre><code class="lang-java"><span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping("/users")</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UserController</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> UserRepository userRepository;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">UserController</span><span class="hljs-params">(UserRepository userRepository)</span> </span>{
        <span class="hljs-keyword">this</span>.userRepository = userRepository;
    }

    <span class="hljs-meta">@GetMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> List&lt;User&gt; <span class="hljs-title">getUsers</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> userRepository.findAll();
    }

    <span class="hljs-meta">@PostMapping</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">createUser</span><span class="hljs-params">(<span class="hljs-meta">@RequestBody</span> User user)</span> </span>{
        <span class="hljs-keyword">return</span> userRepository.save(user);
    }

    <span class="hljs-meta">@PutMapping("/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> User <span class="hljs-title">updateUser</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> Long id, <span class="hljs-meta">@RequestBody</span> User user)</span> </span>{
        user.setId(id);
        <span class="hljs-keyword">return</span> userRepository.save(user);
    }

    <span class="hljs-meta">@DeleteMapping("/{id}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">deleteUser</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable</span> Long id)</span> </span>{
        userRepository.deleteById(id);
    }
}
</code></pre>
<p>Here are a few things to note:</p>
<ul>
<li>You're using the <code>@RestController</code> annotation to mark the class as a controller that handles REST requests.</li>
<li>The <code>@RequestMapping</code> annotation specifies the base URL for the endpoints.</li>
<li>You're injecting the <code>UserRepository</code> into the controller using constructor injection.</li>
<li>Finally, you're implementing your API endpoints for CRUD operations using the <code>@GetMapping</code>, <code>@PostMapping</code>, <code>@PutMapping</code>, and <code>@DeleteMapping</code> annotations.</li>
</ul>
<h3 id="heading-configure-the-database">Configure the Database</h3>
<p>To connect your Spring Boot application to the Neon Postgres database, you need to configure the database URL, username, and password in the <code>application.properties</code> file:</p>
<pre><code>spring.datasource.url=jdbc:postgresql:<span class="hljs-comment">//&lt;db-url&gt;/&lt;db-name&gt;?sslmode=require</span>
spring.datasource.username=&lt;username&gt;
spring.datasource.password=&lt;password&gt;
spring.jpa.hibernate.ddl-auto=update
</code></pre><p>Here, you configured the database URL, username, and password provided by Neon when you created the database. The <code>spring.jpa.hibernate.ddl-auto=update</code> property tells Spring Boot to automatically create the necessary tables or columns based on the entity classes when the application starts.</p>
<h2 id="heading-how-to-deploy-on-azure-app-service">How to Deploy on Azure App Service</h2>
<p>Now that your Spring Boot application is ready, it's time to deploy it on Azure App Service.</p>
<h3 id="heading-create-a-new-web-app">Create a New Web App</h3>
<p>To deploy your Spring Boot application on Azure App Service, you'll first create a new <code>Web App</code>. You can do this through the Azure portal by following these steps:</p>
<ul>
<li>Log in to the <a target="_blank" href="https://portal.azure.com/">Azure portal</a>.</li>
<li>Click on the <code>Create a resource</code> button.</li>
<li>Search for <code>Web App</code> and select the <code>Create</code> option.</li>
<li>Fill in the necessary details like resource group, app name, runtime stack, and region.</li>
<li>Click the <code>Review + create</code> button.</li>
</ul>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flf2kmh12t8eucd1qa1pg.png" alt="Form for creating a web app in Azure" width="600" height="400" loading="lazy">
<em>Creating a Web App in Azure</em></p>
<h3 id="heading-deploy-the-application">Deploy the Application</h3>
<p>The Web App takes a couple of minutes to create. Once done, you can deploy your Spring Boot application to Azure App Service.</p>
<p>One of the easiest ways to deploy is to package your Spring Boot application as a JAR file and deploy it to Azure App Service using the Azure CLI.</p>
<p>To do this, run the below commands:</p>
<pre><code>mvn package
az webapp deploy --src-path neon-demo<span class="hljs-number">-0.0</span><span class="hljs-number">.1</span>-SNAPSHOT.jar --resource-group learn-ba1a439c<span class="hljs-number">-71</span>ca<span class="hljs-number">-4</span>cab<span class="hljs-number">-9</span>bb1-f5b1331bab04 --name neon-app
</code></pre><p>Here, you're packaging your Spring Boot application using Maven and deploying the JAR file to Azure App Service using the Azure CLI. You've provided the path to the JAR file, the resource group, and the app name you previously configured.</p>
<h3 id="heading-access-the-application">Access the Application</h3>
<p>Once the deployment is complete, you can access your Spring Boot application on Azure App Service by navigating to the URL of the Web App. Your app is available at neon-app.azurewebsites.net</p>
<p>Let's use _curl _to test the endpoints.</p>
<h4 id="heading-create-a-user">Create a User</h4>
<pre><code>curl -X POST -d <span class="hljs-string">'{"name":"John Doe","email":"john@gmail.com"}'</span> https:<span class="hljs-comment">//neon-app.azurewebsites.net/users</span>
</code></pre><p>Here you provide user data in JSON format to create a new user.</p>
<h4 id="heading-get-users">Get Users</h4>
<p>You can also can test that the user was created by fetching all users:</p>
<pre><code>curl -X GET https:<span class="hljs-comment">//neon-app.azurewebsites.net/users</span>
</code></pre><h2 id="heading-how-to-set-up-autoscaling">How to Set Up Autoscaling</h2>
<p>A production application may experience varying levels of traffic, and it's important to scale the application dynamically based on the load.</p>
<p>Let's explore how you can autoscale your application when needed.</p>
<h3 id="heading-autoscaling-in-azure">Autoscaling in Azure</h3>
<p>Azure App Service provides <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-premium-plan?tabs=portal#plan-and-sku-settings">autoscaling options</a> that let you automatically adjust the number of instances as needed.</p>
<p>You can configure autoscaling rules in the Azure portal by following these steps:</p>
<ul>
<li>Navigate to the Web App in the Azure portal.</li>
<li>Click the <code>Scale out (App Service Plan)</code> option from the left menu.</li>
<li>Configure the autoscaling rules – you can choose predefined rules like traffic or create custom rules based on metrics like CPU usage, memory usage, or custom metrics.</li>
<li>Save.</li>
</ul>
<p>Azure will automatically scale the application based on the configured rules.</p>
<h3 id="heading-autoscaling-in-neon">Autoscaling in Neon</h3>
<p>Since your application is automatically scaled based on the load, you'll want to ensure that the database can handle the increased traffic.</p>
<p>Neon provides <a target="_blank" href="https://neon.tech/docs/introduction/autoscaling">autoscaling options</a> to scale the database dynamically based on the load. You can configure autoscaling rules in the Neon dashboard to ensure the database can handle the increased load.</p>
<p>Follow the below steps to configure autoscaling in Neon:</p>
<ol>
<li>Navigate to the Neon dashboard and select the database. Then select the branch to configure autoscaling.</li>
</ol>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl6s84pqhk2avflpjbgrf.png" alt="Neon project dashboard with branches section highlighted " width="600" height="400" loading="lazy">
<em>Selecting a branch from Neon project dashboard</em></p>
<ol start="2">
<li>Click on the <code>Edit</code> button next to the <code>Compute</code> section. Configure the autoscaling rules based on metrics like CPU usage, memory usage, or custom metrics.</li>
</ol>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkn11nop1zz9xxbfamsr.png" alt="Branch details view in Neon with edit button in the computes section highlighted" width="600" height="400" loading="lazy">
<em>Branch details view in Neon</em></p>
<ol start="3">
<li>Configure the min-max compute size and Save. Neon will automatically scale the database based on the configured rules when needed.</li>
</ol>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdmuow8zvndz0dibv2kxt.png" alt="Form to enable autoscaling and select min and max size of the compute" width="600" height="400" loading="lazy">
<em>Setting up autoscaling for compute</em></p>
<p>Ensuring that both the application and the database can scale dynamically based on the load will help you handle varying levels of traffic efficiently.</p>
<h2 id="heading-how-to-configure-database-branches-in-neon">How to Configure Database Branches in Neon</h2>
<p>In a typical development workflow, multiple databases may be used for different purposes like development, testing, and production.</p>
<p>Neon Postgres provides <a target="_blank" href="https://neon.tech/docs/introduction/autoscaling">database branching</a> to create multiple branches for different purposes. Each branch is an instance of the database that you can use independently.</p>
<p>This Git-like feature helps set up a copy of the database for different environments like development, staging, and production. It also helps preserve data for different versions of the application.</p>
<p>Let's explore how you can create and manage branches in Neon Postgres:</p>
<ul>
<li>Navigate to the Neon dashboard and select the database.</li>
<li>In the <code>Branches</code> section, click on the <code>View All</code> button.</li>
<li>You can create a new branch from an existing one by clicking on the <code>Create Branch</code> button. You'll need to provide the branch name and what data to copy from the parent branch.</li>
</ul>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ncdgdrj32etd3gbqurf.png" alt="Branches view with Create branch option visible " width="600" height="400" loading="lazy">
<em>Create branch option</em></p>
<ul>
<li>You can either copy all the data or copy until a point in time or a specific record. This is useful for multiple purposes like restoring data, creating a new environment, or testing new features.</li>
</ul>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7gchucru5qw294icqw3.png" alt="Creating a new branch from an existing branch" width="600" height="400" loading="lazy">
<em>Creating a new branch</em></p>
<ul>
<li>Neon will create a new branch of the database that can be used independently. You can find the URL, username, and password for the new branch in the dashboard. And this happens in real time without any downtime and delays.</li>
</ul>
<p><img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fji79akuf193gtv94yaag.png" alt="Branch-specific connection details " width="600" height="400" loading="lazy">
<em>Branch-specific connection details</em></p>
<p>Now you can use your <code>dev</code> branch for local development and testing, and the <code>main</code> branch for production. This helps in keeping the data separate and ensures that changes in one branch do not affect the other branches.</p>
<h2 id="heading-summary">Summary</h2>
<p>In this article, we built a CRUD application using Spring Boot, Neon Postgres, and Azure App Service.</p>
<p>We explored how to set up the Neon Postgres database, build a basic CRUD application using Spring Boot, deploy the application on Azure App Service, and configure autoscaling for the application and the database.</p>
<p>We also learned about how the database branching feature in Neon Postgres helps you create branches of the database for different environments and purposes.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Create Database Migrations in Go Using Docker and Postgres ]]>
                </title>
                <description>
                    <![CDATA[ By Okure U. Edet Go is a fast programming language with a relatively simple syntax. While learning Go, it is important to learn how to build APIs and how to use them to communicate with databases. In the process of learning, I decided to take on a pr... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-create-database-migrations-in-go/</link>
                <guid isPermaLink="false">66d4608b264384a65d5a95c5</guid>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Go Language ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 26 Jun 2024 11:40:41 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/06/fotis-fotopoulos-DuHKoV44prg-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Okure U. Edet</p>
<p>Go is a fast programming language with a relatively simple syntax. While learning Go, it is important to learn how to build APIs and how to use them to communicate with databases. In the process of learning, I decided to take on a project that helped me in that regard: a simple inventory tracking API.</p>
<p>While working with an SQL database like Postgres, I learnt that it is important to make changes to the database in a timely manner. So if you have a schema that you may modify in the future, the best way to do that is with database migrations. It ensures that changes to the database are made accurately without affecting existing data.</p>
<p>In this article, you will learn about database migrations using Docker and Postgres.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#what-is-daabase-migration">What is Database Migration?</a></li>
<li><a class="post-section-overview" href="#how-to-start-and-run-a-docker-container">How to Start and Run a Docker Container</a></li>
<li><a class="post-section-overview" href="#how-to-create-and-run-a-schema-using-tableplus">How to Create and Run a Schema Using TablePlus</a></li>
<li><a class="post-section-overview" href="#how-to-install-golang-migrate">How to Install golang-migrate</a></li>
<li><a class="post-section-overview" href="#how-to-create-a-new-migration">How to Create a New Migration</a></li>
<li><a class="post-section-overview" href="#how-to-create-and-drop-the-database-inside-and-outside-a-docker-postgres-container">How to Create and Drop the Database Inside and Outside a Docker Postgres Container</a></li>
<li><a class="post-section-overview" href="#how-to-view-the-database-in-tableplus">How to View the Database in TablePlus</a></li>
<li><a class="post-section-overview" href="#how-to-run-the-migrations">How to Run the Migrations</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-what-is-database-migration">What is Database Migration?</h2>
<p>What is database migration and why should you use it? Well, as a backend developer, when working on a project that requires you to store data in a database, you will need to develop a schema for the data you want to store.</p>
<p>Database migrations help you manage the structure of data within a database and in this case, a relational database. Migrations help modify schemas from a current state to a specific/desired state. It may involve the addition of tables and columns, removing elements or changing types and constraints.</p>
<p>One importance of database migration is to make changes in a database repeatable and seamless without the concern of data loss. </p>
<p>It is advisable to use migrations if you are not sure of what your final data schema would look like. In this sense, you can incrementally implement changes to it.</p>
<h2 id="heading-how-to-start-and-run-a-docker-container">How to Start and Run a Docker Container</h2>
<p>Open your terminal and create a new directory <code>mkdir tracking-inventory-app</code>.</p>
<p>Then pull a postgres image from <a target="_blank" href="https://hub.docker.com/">Docker Hub</a>. I used the <code>postgres:14-alpine</code> tag. You can use any tag you want.</p>
<p>In your terminal, paste the following and press enter:</p>
<pre><code>$ docker pull postgres:<span class="hljs-number">14</span>-alpine
</code></pre><p>After installing it, start the container by using the <code>docker run</code> command:</p>
<pre><code>$ docker run --name postgres14 -e POSTGRES_USER=root -e POSTGRES_PASSWORD=passwordd -p <span class="hljs-number">5432</span>:<span class="hljs-number">5432</span> -d postgres:<span class="hljs-number">14</span>-alpine
</code></pre><p>The <code>--name</code> flag refers to the name of the container. The <code>-e</code> flag refers to the environment variables. The <code>-p</code> flag means publish. You should run your container on a specified port. The <code>-d</code> flag means you want to run it in detached mode.</p>
<p>After you have pressed enter, open your Docker Desktop if you have it installed. If you don't, you can download it from the <a target="_blank" href="https://www.docker.com/products/docker-desktop">docker website</a>.</p>
<p>In your Docker Desktop, you should see that the container has been started:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/docker-postgres14.png" alt="docker-postgres14" width="600" height="400" loading="lazy"></p>
<p>You can establish a connection with the the database using TablePlus:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/connectionok.png" alt="connectionok" width="600" height="400" loading="lazy"></p>
<p>Test the connection. If it says ok, then connect. If you are on Windows and it shows an authentication error, navigate to your start button and click on <code>Run</code>. In the popup, type <code>services.msc</code> and press enter. Look for postgres and click on stop service. Then try connecting again.</p>
<h2 id="heading-how-to-create-and-run-a-schema-using-tableplus">How to Create and Run a Schema Using TablePlus</h2>
<p>I have created a predefined schema/model for the tracking-inventory project with <a target="_blank" href="https://www.dbdiagram.io/d">db diagram</a>. This tracking-inventory should allow you add an item, serial number and value. So the schema will have an <code>item</code>, <code>serial_number</code>, <code>id</code> and <code>created_at</code> fields.</p>
<pre><code>CREATE TABLE <span class="hljs-string">"inventory"</span> (
  <span class="hljs-string">"id"</span> uuid PRIMARY KEY,
  <span class="hljs-string">"item"</span> varchar NOT NULL,
  <span class="hljs-string">"serial_number"</span> varchar NOT NULL,
  <span class="hljs-string">"user"</span> uuid NOT NULL,
  <span class="hljs-string">"created_at"</span> timestamptz NOT NULL DEFAULT <span class="hljs-string">'now()'</span>
);

CREATE TABLE <span class="hljs-string">"user"</span> (
  <span class="hljs-string">"id"</span> uuid PRIMARY KEY,
  <span class="hljs-string">"name"</span> varchar NOT NULL,
  <span class="hljs-string">"email"</span> varchar UNIQUE NOT NULL,
  <span class="hljs-string">"password"</span> varchar NOT NULL,
  <span class="hljs-string">"created_at"</span> timestamptz NOT NULL DEFAULT <span class="hljs-string">'now()'</span>
);

CREATE INDEX ON <span class="hljs-string">"inventory"</span> (<span class="hljs-string">"item"</span>);

ALTER TABLE <span class="hljs-string">"inventory"</span> ADD FOREIGN KEY (<span class="hljs-string">"user"</span>) REFERENCES <span class="hljs-string">"user"</span> (<span class="hljs-string">"id"</span>);
</code></pre><p>This is how mine looks. You can open your TablePlus and add the generated PostgreSQL code and run it.</p>
<h2 id="heading-how-to-install-golang-migrate">How to Install golang-migrate</h2>
<p>The next step is to install <code>golang-migrate</code> on your system. I am using Linux on Windows for this tutorial. </p>
<p>To install it, visit this <a target="_blank" href="https://github.com/golang-migrate/migrate/tree/master/cmd/migrate">documentation</a>.</p>
<p>I am using Linux so I will use <code>curl</code>:</p>
<pre><code>$ curl -L https:<span class="hljs-comment">//github.com/golang-migrate/migrate/releases/download/v4.12.2/migrate.linux-amd64.tar.gz | tar xvz</span>
</code></pre><p>Once it has been successfully installed, on your terminal, run the command <code>migrate -help</code> to see its various commands.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/migrate-help.png" alt="migrate-help" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-create-a-new-migration">How to Create a New Migration</h2>
<p>After installing <code>golang-migrate</code>, you can create a new migration script.</p>
<p>Firstly, in your terminal and within the tracking-app directory, open VS code with the <code>code</code> command.</p>
<p>Once that is done, create a new folder named <code>db</code> and another folder inside the db folder named <code>migrations</code>.</p>
<p>Then in your terminal, run the following command:</p>
<pre><code> $ migrate create -ext sql -dir db/migration -seq tracking_inventory_schema
</code></pre><p>The <code>-ext</code> flag refers to the extension you want the migration to be created with. In this case, it is sql. The <code>-dir</code> flag refers to the directory you want to create the files in. The <code>-seq</code> flag refers to the sequential number for the migration files.</p>
<p>Inside your VS code, there should be two files: one for <code>up</code> and another for <code>down</code>. The former is used to make forward changes to the directory while the latter is for reversing the changes.</p>
<p>In the <code>up</code> file, you are going to paste your schema to the file.</p>
<p>My schema looks like this:</p>
<pre><code>CREATE TABLE <span class="hljs-string">"inventory"</span> (
  <span class="hljs-string">"id"</span> uuid PRIMARY KEY,
  <span class="hljs-string">"item"</span> varchar NOT NULL,
  <span class="hljs-string">"serial_number"</span> varchar NOT NULL,
  <span class="hljs-string">"user"</span> uuid NOT NULL,
  <span class="hljs-string">"created_at"</span> timestamptz NOT NULL DEFAULT <span class="hljs-string">'now()'</span>
);

CREATE TABLE <span class="hljs-string">"user"</span> (
  <span class="hljs-string">"id"</span> uuid PRIMARY KEY,
  <span class="hljs-string">"name"</span> varchar NOT NULL,
  <span class="hljs-string">"email"</span> varchar UNIQUE NOT NULL,
  <span class="hljs-string">"password"</span> varchar NOT NULL,
  <span class="hljs-string">"created_at"</span> timestamptz NOT NULL DEFAULT <span class="hljs-string">'now()'</span>
);

CREATE INDEX ON <span class="hljs-string">"inventory"</span> (<span class="hljs-string">"item"</span>);

ALTER TABLE <span class="hljs-string">"inventory"</span> ADD FOREIGN KEY (<span class="hljs-string">"user"</span>) REFERENCES <span class="hljs-string">"user"</span> (<span class="hljs-string">"id"</span>);
</code></pre><p>Yours may look different depending on what project you are building.</p>
<p>For the <code>down</code> file, just paste this in:</p>
<pre><code>DROP TABLE IF EXISTS inventory;
DROP TABLE IF EXISTS user;
</code></pre><p>The inventory table should be dropped first because it references the user table.</p>
<h2 id="heading-how-to-create-and-drop-the-database-inside-and-outside-a-docker-postgres-container">How to Create and Drop the Database Inside and Outside a Docker Postgres Container</h2>
<p>Check if your docker container is running using the command:</p>
<pre><code>$ docker ps
</code></pre><p>If it is not, use the command <code>docker start ${container name}</code> to start it.</p>
<p>Next step is to access postgres shell using the following command since I'm on Linux:</p>
<pre><code>$ docker exec -it postgres14 bin/bash
</code></pre><p>The <code>-it</code> flag stands for interactive shell/terminal. Inside this shell, you can run the <code>createdb</code> command:</p>
<pre><code>/# createdb --username=root --owner=root tracking_inventory
</code></pre><p>Once created, you can run the <code>psql</code> command to interact with the db:</p>
<pre><code>/# psql tracking-inventory
psql (<span class="hljs-number">14.12</span>)
Type <span class="hljs-string">"help"</span> <span class="hljs-keyword">for</span> help.

tracking_inventory=#
</code></pre><p>You can also delete the database with the <code>dropdb</code> command.</p>
<p>To leave the shell, use the <code>exit</code> command.</p>
<p>To create the database outside the postgres container, paste the following command:</p>
<pre><code>$ docker exec -it postgres14 createdb --username=root --owner=root tracking_inventory
</code></pre><h2 id="heading-how-to-view-the-database-in-tableplus">How to View the Database in TablePlus</h2>
<p>To view the database that you have created, connect using the previous connection we established earlier. It'll take you to the root database and then click on the db icon on top.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/root-db.png" alt="root-db" width="600" height="400" loading="lazy"></p>
<p>The database created will appear, then just click on <code>open</code> to open it</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/tracking-inventory-db.png" alt="tracking-inventory-db" width="600" height="400" loading="lazy"></p>
<h2 id="heading-how-to-run-the-migrations">How to Run the Migrations</h2>
<p>To run the migrations, run this command in your terminal:</p>
<pre><code>$ migrate -path db/migration -database <span class="hljs-string">"postgresql://root:passwordd@localhost:5432/tracking_inventory?sslmode=disable"</span> -verbose up
</code></pre><p>The <code>-path</code> flag specifies the path that contains the migration files. The <code>-database</code> option specifies the url to the database.</p>
<p>Inside the url, the driver is <code>postgresql</code>. The username and pasword is <code>root</code> and <code>passwordd</code> respectively. It is also important to add the <code>sslmode=disable</code> option because Postgres does not enable SSL by default.</p>
<p>Now run the migrations:</p>
<pre><code>$ migrate -path db/migration -database <span class="hljs-string">"postgresql://root:passwordd@localhost:5432/tracking_inventory?sslmode=disable"</span> -verbose up
<span class="hljs-attr">calhost</span>:<span class="hljs-number">5432</span>/tracking_inventory?sslmode=disable<span class="hljs-string">" -verbose up
2024/06/25 00:13:25 Start buffering 1/u tracking_inventory_schema
2024/06/25 00:13:25 Read and execute 1/u tracking_inventory_schema
2024/06/25 00:13:26 Finished 1/u tracking_inventory_schema (read 43.186044ms, ran 255.501635ms)
2024/06/25 00:13:26 Finished after 312.928488ms
2024/06/25 00:13:26 Closing source and database</span>
</code></pre><p>The migration is successful!</p>
<p>Refresh the database and see the new tables:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/06/schema-migrations.png" alt="schema-migrations" width="600" height="400" loading="lazy"></p>
<p>##Conclusion</p>
<p>Throughout this tutorial, you have learnt how to seamlessly write and run database migrations in Go using Docker and Postgres. I hope you have learnt much from this article.</p>
<p>You can connect with me on <a target="_blank" href="https://x.com/itzz_okure">twitter</a> or on <a target="_blank" href="https://www.linkedin.com/in/okure/">linkedin</a>.</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 Automate Data Exports and Email Reports with Python – a Step-by-Step Guide ]]>
                </title>
                <description>
                    <![CDATA[ In today's data-driven world, automation is key to streamlining tasks and saving time. In this beginner-friendly tutorial, I'll walk you through the process of automating data exports from a PostgreSQL database and sending them as an email attachment... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/automate-data-exports-email-reports-with-python/</link>
                <guid isPermaLink="false">66ba0e68d14c87384322b68d</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ excel ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ashutosh Krishna ]]>
                </dc:creator>
                <pubDate>Mon, 30 Oct 2023 16:08:37 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/report-automation.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In today's data-driven world, automation is key to streamlining tasks and saving time. In this beginner-friendly tutorial, I'll walk you through the process of automating data exports from a PostgreSQL database and sending them as an email attachment using Python. </p>
<p>This step-by-step guide will help you grasp the fundamentals of working with databases, data manipulation, and email communication, all while automating these processes with a Python script.</p>
<h2 id="heading-business-context">Business Context</h2>
<p>Imagine you're a part of an organization where your managers expect a weekly report filled with valuable insights. But creating this report is far from a straightforward task. </p>
<p>To get the information you need, you have to manually run ten different database queries, gather the results, and then meticulously compile them into an Excel spreadsheet. It's a time-consuming and error-prone process that can leave you exhausted.</p>
<p>In this scenario, wouldn't it be a game-changer if Python could take the reins and handle this entire process for you? </p>
<p>Picture this: Every week, without any manual intervention, Python seamlessly extracts the required data, compiles it into a neat Excel sheet, and even sends it off to your managers like clockwork. </p>
<p>This tutorial will help you learn how to do this. I'll walk you through the steps to automate this process, making your weekly or monthly reporting a breeze, and freeing you up to focus on more critical tasks.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-your-virtual-environment">How to Set Up Your Virtual Environment</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-your-sample-database">How to Set Up Your Sample Database</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-logging-and-environment-variables">How to Set Up Logging and Environment Variables</a></li>
<li><a class="post-section-overview" href="#heading-how-to-extract-the-data-from-the-database">How to Extract the Data From the Database</a></li>
<li><a class="post-section-overview" href="#heading-how-to-structure-the-booking-data-with-the-bookinginfo-class">How to Structure the Booking Data with the <code>BookingInfo</code> Class</a></li>
<li><a class="post-section-overview" href="#heading-how-to-convert-the-data-into-an-excel-sheet">How to Convert the Data into an Excel Sheet</a></li>
<li><a class="post-section-overview" href="#heading-how-to-combine-the-functionalities">How to Combine the Functionalities</a></li>
<li><a class="post-section-overview" href="#heading-how-to-send-an-email-with-the-bookings-data-report">How to Send an Email with the Bookings Data Report</a></li>
<li><a class="post-section-overview" href="#heading-how-to-test-the-flow">How to Test the Flow</a></li>
<li><a class="post-section-overview" href="#heading-how-to-schedule-the-application">How to Schedule the Application</a></li>
<li><a class="post-section-overview" href="#heading-wrapping-up">Wrapping Up</a></li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before you get started, make sure you have the following:</p>
<ol>
<li>Python installed on your computer. You can download Python from <a target="_blank" href="https://www.python.org/downloads/">Python.org</a>.</li>
<li>Basic knowledge of the Python programming language</li>
<li>Familiarity with <a target="_blank" href="https://blog.ashutoshkrris.in/how-to-send-emails-using-python">sending emails in Python</a></li>
<li>PostgreSQL installed on your computer. You can download PostgreSQL from <a target="_blank" href="https://www.postgresql.org/download/">here</a>.</li>
</ol>
<h2 id="heading-how-to-set-up-your-virtual-environment">How to Set Up Your Virtual Environment</h2>
<p>Before you start coding, you'll need to make sure you have all the necessary tools and libraries installed. </p>
<p>To ensure that you have a clean and isolated environment, you'll <a target="_blank" href="https://www.freecodecamp.org/news/how-to-setup-virtual-environments-in-python/">create a virtual environment</a> using <code>venv</code>.</p>
<p>Create a project directory and navigate to it in the terminal:</p>
<pre><code class="lang-bash">mkdir report-automation
<span class="hljs-built_in">cd</span> report-automation
</code></pre>
<p>Create a virtual environment named <code>env</code> using the following command:</p>
<pre><code class="lang-bash">python -m venv env
</code></pre>
<p>Python now ships with the pre-installed <code>venv</code> library to create virtual environments.</p>
<p>Activate the virtual environment like this:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">source</span> env/bin/activate
</code></pre>
<p>Note: if you're on Windows, you'll need to use <code>source env/Scripts/activate</code> to activate the environment.</p>
<p>You should see <code>(env)</code> in your terminal prompt, indicating that the virtual environment has been activated.</p>
<h3 id="heading-how-to-install-the-required-libraries">How to Install the Required Libraries</h3>
<p>Now that you've created the virtual environment, you can install the following libraries:</p>
<ul>
<li><code>psycopg2</code>: Python adapter for PostgreSQL, enabling Python applications to interact with PostgreSQL databases.</li>
<li><code>pandas</code>: A versatile data manipulation and analysis library for Python, ideal for working with structured data.</li>
<li><code>xlsxwriter</code>: Python module for creating and formatting Excel (XLSX) files, useful for generating reports and spreadsheets.</li>
</ul>
<p>To install the libraries, run the following command:</p>
<pre><code class="lang-bash">pip install psycopg2 pandas xlsxwriter
</code></pre>
<h2 id="heading-how-to-set-up-your-sample-database">How to Set Up Your Sample Database</h2>
<p>In this section, I will guide you through setting up a demo database named "airlines" that we'll use throughout this tutorial. The database includes three tables: <code>bookings</code>, <code>flights</code>, and <code>airports_data</code>. </p>
<p>I will provide you with an SQL script file named <code>airlines_db.sql</code> that creates the database and populates it with sample data. To set up the database, you will need PostgreSQL installed on your system.</p>
<h3 id="heading-download-and-install-the-database">Download and Install the Database</h3>
<ol>
<li>Download the SQL script file "airlines_db.sql" from <a target="_blank" href="https://drive.google.com/file/d/1CPo4ZC8dmuyCetEwpyDa6pfKnpbiqyO3/view?usp=sharing">here</a>.</li>
<li>Open your terminal or command prompt.</li>
<li>Use the following command to install the database. Make sure you have the PostgreSQL command-line tools installed and that you can access the <code>psql</code> command. Replace <code>postgres</code> with your PostgreSQL username if it's different.</li>
</ol>
<pre><code class="lang-bash">psql -f airlines_db.sql -U postgres
</code></pre>
<p>This command will execute the SQL script and create the "airlines" database with the <code>bookings</code>, <code>flights</code>, and <code>airports_data</code> tables.</p>
<h3 id="heading-schema-description">Schema Description</h3>
<p>The main schema in the database is <code>bookings</code>. Let's take a closer look at the tables in the "airlines" database:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/Screenshot-2023-10-29-115228.png" alt="Image" width="600" height="400" loading="lazy">
<em>Schema Diagram</em></p>
<h4 id="heading-table-bookingsbookings">Table <code>bookings.bookings</code></h4>
<p>The "bookings" table is designed to store crucial information about bookings made for flights. Each booking is uniquely identified by the <code>book_ref</code>, which is a <code>character(6)</code> field. The <code>total_amount</code> field is a <code>numeric(10,2)</code> type and represents the total cost of the booking. </p>
<p>To track the booking date and time, the table includes a <code>book_date</code> field of type <code>bigint</code>. This table serves as the central repository for booking data and is essential for tracking passenger reservations, costs, and booking dates.</p>
<h4 id="heading-table-bookingsflights">Table <code>bookings.flights</code></h4>
<p>The "flights" table is dedicated to capturing comprehensive details about flights, including information about their statuses, scheduled and actual times of departure and arrival, and other important flight-related data. </p>
<p>The primary key for this table is the <code>flight_id</code>, an <code>integer</code> identifier. Each flight is associated with a specific flight number denoted by the <code>flight_no</code> field, a <code>character(6)</code> type. </p>
<p>To understand the flight's origin and destination, the <code>departure_airport</code> and <code>arrival_airport</code> fields store the departure and arrival airport codes as <code>character(3)</code> types, respectively. </p>
<p>The <code>status</code> field is a <code>character varying(20)</code> that records the flight's status, which must be one of 'On Time,' 'Delayed,' 'Departed,' 'Arrived,' 'Scheduled,' or 'Cancelled.' The table also includes fields for scheduled departure and arrival times (<code>scheduled_departure</code> and <code>scheduled_arrival</code>) and actual departure and arrival times (<code>actual_departure</code> and <code>actual_arrival</code>). </p>
<p>Furthermore, this table establishes two essential foreign keys: <code>flights_arrival_airport_fkey</code> and <code>flights_departure_airport_fkey</code>, which link to the <code>airport_code</code> in the "airports_data" table. This establishes connections between flights and their respective departure and arrival airports.</p>
<h4 id="heading-table-bookingsairportsdata">Table <code>bookings.airports_data</code></h4>
<p>The "airports_data" table serves as a repository for data related to airports and their geographic locations. Each airport is identified by a unique <code>character(3)</code> code stored in the <code>airport_code</code> field, which also serves as the primary key. </p>
<p>The <code>timezone</code> field, of type <code>text</code>, records the specific timezone of the airport, providing essential information for scheduling and operational purposes. The <code>airport_name</code> field is a <code>character varying</code> type that holds the name of the airport. Additionally, the table includes the <code>city</code> field as a <code>character varying</code> type, indicating the city in which the airport is situated. </p>
<p>These details enable the "airports_data" table to provide a comprehensive overview of airport locations and information. This serves as a reference for the "flights" table through the <code>flights_arrival_airport_fkey</code> and <code>flights_departure_airport_fkey</code> foreign keys, facilitating the association between flights and their respective departure and arrival airports.</p>
<h2 id="heading-how-to-set-up-logging-and-environment-variables">How to Set Up Logging and Environment Variables</h2>
<p>In this section, we'll configure logging to provide informative messages and handle errors throughout the code. We'll also set up environment variables to securely store sensitive information and configuration parameters. These practices enhance code readability, maintainability, and security.</p>
<h3 id="heading-logging-configuration">Logging Configuration</h3>
<p>We will utilize Python's built-in <code>logging</code> module to configure a logging system. Logging is essential for tracking the execution flow of the code and capturing important information or errors. </p>
<p>The <code>logging.basicConfig</code> method is called to define the format of log messages and set the logging level to <code>INFO</code>.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging

logging.basicConfig(
    format=<span class="hljs-string">"%(asctime)s | %(levelname)s : %(message)s"</span>, level=logging.INFO
)
</code></pre>
<ul>
<li><strong>Format</strong>: The <code>format</code> parameter specifies the format of log messages. In this case, each log entry includes a timestamp, log level (for example, INFO, ERROR), and the actual log message.</li>
<li><strong>Log Levels</strong>: We set the logging level to <code>INFO</code>, which means the logger will record informational messages. You can also use higher severity levels, such as <code>WARNING</code> or <code>ERROR</code>, for more critical issues.</li>
</ul>
<p>You can learn more about logging in Python in <a target="_blank" href="https://earthly.dev/blog/logging-in-python/">this tutorial</a>.</p>
<h3 id="heading-how-to-manage-environment-variables">How to Manage Environment Variables</h3>
<p>We will create a <code>.env</code> file to manage environment variables. Environment variables are used to store sensitive information and configuration settings, allowing us to keep such data separate from the code. </p>
<p>In this case, we set environment variables for email credentials and database connection details.</p>
<pre><code><span class="hljs-keyword">export</span> EMAIL=
<span class="hljs-keyword">export</span> PASSWORD=
<span class="hljs-keyword">export</span> EMAIL_PORT=<span class="hljs-number">587</span>
<span class="hljs-keyword">export</span> SMTP_SERVER=smtp.gmail.com
<span class="hljs-keyword">export</span> DB_HOSTNAME=localhost
<span class="hljs-keyword">export</span> DB_NAME=airlines
<span class="hljs-keyword">export</span> DB_PORT=<span class="hljs-number">5432</span>
<span class="hljs-keyword">export</span> DB_USERNAME=postgres
<span class="hljs-keyword">export</span> DB_PASSWORD=postgres
</code></pre><p>Here's a breakdown of the variables:</p>
<ul>
<li><strong>EMAIL</strong>: The email address to be used for sending emails.</li>
<li><strong>PASSWORD</strong>: The password associated with the email account.</li>
<li><strong>EMAIL_PORT</strong>: The port for the email server (for example, SMTP server). The default is 587 for secure email transmission (TLS/SSL).</li>
<li><strong>SMTP_SERVER</strong>: The SMTP server address, often specific to the email service provider.</li>
<li><strong>DB_HOSTNAME</strong>: The hostname or IP address of the PostgreSQL database server.</li>
<li><strong>DB_NAME</strong>: The name of the PostgreSQL database.</li>
<li><strong>DB_PORT</strong>: The port number for connecting to the database (default is 5432 for PostgreSQL).</li>
<li><strong>DB_USERNAME</strong>: The username for authenticating with the database.</li>
<li><strong>DB_PASSWORD</strong>: The password for the database user.</li>
</ul>
<p>Make sure you run <code>source .env</code> to load the environment variables.</p>
<p>By using environment variables, sensitive data like passwords and email credentials can be kept separate from the code, reducing the risk of accidental exposure or unauthorized access. The code can access these variables at runtime, ensuring security and flexibility in configuration.</p>
<h2 id="heading-how-to-extract-the-data-from-the-database">How to Extract the Data From the Database</h2>
<p>Let's start by setting the database configurations.</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> os

logging.basicConfig(
    format=<span class="hljs-string">"%(asctime)s | %(levelname)s : %(message)s"</span>, level=logging.INFO
)

DB_CONFIG = {
    <span class="hljs-string">"host"</span>: os.environ.get(<span class="hljs-string">"DB_HOSTNAME"</span>),
    <span class="hljs-string">"database"</span>: os.environ.get(<span class="hljs-string">"DB_NAME"</span>),
    <span class="hljs-string">"user"</span>: os.environ.get(<span class="hljs-string">"DB_USERNAME"</span>),
    <span class="hljs-string">"password"</span>: os.environ.get(<span class="hljs-string">"DB_PASSWORD"</span>),
}
</code></pre>
<p>The <code>DB_CONFIG</code> dictionary is used to store the configuration parameters for connecting to the PostgreSQL database. These parameters include the host, database name, username, and password. These values can be set through environment variables.</p>
<h3 id="heading-how-to-connect-to-the-database">How to Connect to the Database</h3>
<p>Before we extract the data from the database, we need to connect to our database. We will use the <code>psycopg2</code> library to connect to the PostgreSQL database.</p>
<p>We will start by defining a <code>DataExporter</code> class that will contain methods to extract the database and generate the Excel sheet.</p>
<pre><code class="lang-python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataExporter</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""Initialize the DataExporter with the database configuration."""</span>
        self.db_config = DB_CONFIG
</code></pre>
<p>The class constructor initializes the <code>DataExporter</code> with the database configuration stored in the <code>DB_CONFIG</code> <a target="_blank" href="https://blog.ashutoshkrris.in/everything-you-need-to-know-about-python-dictionaries">dictionary</a>.</p>
<p>Next, let's define a method that connects to the database.</p>
<pre><code class="lang-python">...
<span class="hljs-keyword">import</span> psycopg2

...

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataExporter</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self</span>):</span>
        <span class="hljs-string">"""Initialize the DataExporter with the database configuration."""</span>
        self.db_config = DB_CONFIG

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__connect_to_database</span>(<span class="hljs-params">self</span>) -&gt; <span class="hljs-keyword">None</span>:</span>
        <span class="hljs-string">"""
        Establish a connection to the PostgreSQL database.

        Raises:
            Exception: If a connection to the database cannot be established.
        """</span>
        <span class="hljs-keyword">try</span>:
            self.conn = psycopg2.connect(**self.db_config)
            self.cursor = self.conn.cursor()
            logging.info(<span class="hljs-string">"Connected to the database"</span>)
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            logging.error(
                <span class="hljs-string">"Failed to connect to the database with error: %s"</span>, e)
            <span class="hljs-keyword">raise</span>
</code></pre>
<p>The <code>__connect_to_database</code> private method is responsible for establishing a connection to the PostgreSQL database. It uses the <code>psycopg2</code> library to create a connection and a cursor for executing SQL queries. If the connection fails, it logs an error and raises an exception.</p>
<p>You can learn more about exception handling in Python <a target="_blank" href="https://blog.ashutoshkrris.in/exception-handling-in-python">here</a>.</p>
<h3 id="heading-how-to-fetch-data-from-the-database">How to Fetch Data from the Database</h3>
<p>Now we'll define another private method that connects to the database and fetches the total number of bookings and the total amount from the database.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataExporter</span>:</span>
    ...

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__fetch_from_database</span>(<span class="hljs-params">self, start_timestamp, end_timestamp</span>) -&gt; list | <span class="hljs-keyword">None</span>:</span>
        <span class="hljs-string">"""
        Fetch booking data from the database for a given time range.

        Args:
            start_timestamp (datetime): The start of the time range.
            end_timestamp (datetime): The end of the time range.

        Returns:
            list: A list containing booking data (num_bookings, total_amount) or None if an error occurs.
        """</span>
        self.__connect_to_database()
        query = <span class="hljs-string">f"""
        SELECT COUNT(*) AS num_bookings, SUM(total_amount) AS total_amount
        FROM bookings
        WHERE book_date &gt;= <span class="hljs-subst">{int(start_timestamp.timestamp()) * <span class="hljs-number">1000</span>}</span> AND book_date &lt;= <span class="hljs-subst">{int(end_timestamp.timestamp()) * <span class="hljs-number">1000</span>}</span>
        """</span>
        logging.info(
            <span class="hljs-string">"Exracting bookings data from database for start timestamp=%s and end_timestamp=%s"</span>,
            start_timestamp,
            end_timestamp,
        )
        result = <span class="hljs-literal">None</span>
        <span class="hljs-keyword">try</span>:
            self.cursor.execute(query)
            result = list(self.cursor.fetchone())
            result.append(
                <span class="hljs-string">f'<span class="hljs-subst">{start_timestamp.strftime(<span class="hljs-string">"%d %b, %Y"</span>)}</span> - <span class="hljs-subst">{end_timestamp.strftime(<span class="hljs-string">"%d %b, %Y"</span>)}</span>'</span>
            )
            logging.info(
                <span class="hljs-string">"Successfully exracted bookings data from database for start timestamp=%s and end_timestamp=%s"</span>,
                start_timestamp,
                end_timestamp,
            )
        <span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
            logging.error(
                <span class="hljs-string">"Error occurred while extracting bookings data from database: %s"</span>, e
            )
        <span class="hljs-keyword">return</span> result
</code></pre>
<p>This private method retrieves booking data from the database for a specified time range. </p>
<p>It takes two <code>datetime</code> objects as arguments, <code>start_timestamp</code> and <code>end_timestamp</code>. It also constructs a SQL query to retrieve the count of bookings and the total booking amount for that time range. </p>
<p>The query is executed, and if it's successful, the method returns the data as a tuple. We convert the tuple into a list and append the timeframe for which data was extracted to the list. If an error occurs during the database interaction, it logs an error and returns <code>None</code>.</p>
<p>Using the above method, you can extract booking data for various timeframes, whether it's for a week, a month, a year, or any custom time range of your choice.</p>
<h2 id="heading-how-to-structure-the-booking-data-with-the-bookinginfo-class">How to Structure the Booking Data with the <code>BookingInfo</code> Class</h2>
<p>In this section, we will define a <code>BookingInfo</code> class in <code>booking_info.py</code>, which serves as a structured container for booking data retrieved from the database. The class encapsulates booking-related information, making it easier to work with and present the data. </p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> decimal <span class="hljs-keyword">import</span> Decimal


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BookingInfo</span>:</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span>(<span class="hljs-params">self, data_list: list</span>):</span>
        <span class="hljs-string">"""
        Initialize BookingInfo with data from the database.

        Args:
            data_list (list): A list containing booking data (total_bookings, total_amount, timestamp).

        Note:
            The total_amount is converted to a Decimal type.

        """</span>
        self.__total_bookings, self.__total_amount, self.__timestamp = data_list
        self.__total_amount = Decimal(self.__total_amount) <span class="hljs-keyword">if</span> self.__total_amount <span class="hljs-keyword">else</span> Decimal(<span class="hljs-number">0</span>)

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__str__</span>(<span class="hljs-params">self</span>) -&gt; str:</span>
        <span class="hljs-string">"""
        Return a string representation of BookingInfo.

        Returns:
            str: A string in the format "Total Bookings: X, Total Amount: $Y".

        """</span>
        <span class="hljs-keyword">return</span> <span class="hljs-string">f"Total Bookings: <span class="hljs-subst">{self.__total_bookings}</span>, Total Amount: $<span class="hljs-subst">{self.__total_amount}</span>"</span>

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_total_bookings</span>(<span class="hljs-params">self</span>) -&gt; int:</span>
        <span class="hljs-string">"""
        Get the total number of bookings.

        Returns:
            int: The total number of bookings.

        """</span>
        <span class="hljs-keyword">return</span> self.__total_bookings

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_total_amount</span>(<span class="hljs-params">self</span>) -&gt; Decimal:</span>
        <span class="hljs-string">"""
        Get the total booking amount as a Decimal.

        Returns:
            Decimal: The total booking amount.

        """</span>
        <span class="hljs-keyword">return</span> self.__total_amount

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_timestamp</span>(<span class="hljs-params">self</span>) -&gt; str:</span>
        <span class="hljs-string">"""
        Get the timestamp associated with the booking data.

        Returns:
            str: The timestamp as a string.

        """</span>
        <span class="hljs-keyword">return</span> self.__timestamp
</code></pre>
<p>The <code>BookingInfo</code> class is designed to organize and represent booking data returned from the database. It receives a list of values containing total bookings, total booking amount, and a timestamp as input and converts the total amount to a Decimal type. The class offers methods for accessing and presenting this data in a structured manner.</p>
<p>The constructor of the <code>BookingInfo</code> class takes a <code>data_list</code> as input, which is expected to be a list containing the following elements:</p>
<ul>
<li><code>total_bookings</code>: An integer representing the total number of bookings.</li>
<li><code>total_amount</code>: A floating-point value representing the total booking amount.</li>
<li><code>timestamp</code>: A timestamp associated with the booking data.</li>
</ul>
<p>The <code>__init__</code> method initializes private instance variables (<code>__total_bookings</code>, <code>__total_amount</code>, and <code>__timestamp</code>) with the values from the <code>data_list</code>. It also converts the <code>__total_amount</code> to a decimal type for precise handling of monetary values.</p>
<p>The <code>__str__</code> method is implemented to provide a string representation of the <code>BookingInfo</code> object. It returns a string in the format "Total Bookings: X, Total Amount: $Y", where <code>X</code> is the total number of bookings and <code>Y</code> is the total booking amount formatted as dollars.</p>
<h3 id="heading-getter-methods">Getter Methods</h3>
<p>The class provides three getter methods to access the encapsulated data:</p>
<ul>
<li><code>get_total_bookings()</code>: Returns the total number of bookings as an integer.</li>
<li><code>get_total_amount()</code>: Returns the total booking amount as a Decimal type.</li>
<li><code>get_timestamp()</code>: Returns the timestamp associated with the booking data as a string.</li>
</ul>
<p>By encapsulating the booking data within the <code>BookingInfo</code> class, the code is more organized, readable, and reusable. This structured approach simplifies the handling of booking information throughout the application, making it more intuitive to work with and present the data.</p>
<h2 id="heading-how-to-convert-the-data-into-an-excel-sheet">How to Convert the Data into an Excel Sheet</h2>
<p>Now that you can retrieve data from the database for a specific time range, you can also generate an Excel sheet based on the extracted data. </p>
<p>To do this, let's define another private method to create the Excel sheet.</p>
<pre><code class="lang-python">...
<span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd

<span class="hljs-keyword">from</span> booking_info <span class="hljs-keyword">import</span> BookingInfo


...

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataExporter</span>:</span>

    ...

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__convert_to_excelsheet</span>(<span class="hljs-params">self, data: list, sheet_name: str</span>):</span>
        <span class="hljs-string">"""
        Convert the fetched data into an Excel sheet.

        Args:
            data (list): A list containing booking data.
            sheet_name (str): Name of the Excel sheet to be created.

        Raises:
            ValueError: If there is an error in converting data to an Excel sheet.
        """</span>
        <span class="hljs-keyword">try</span>:
            booking_info = BookingInfo(data)
            data = {
                <span class="hljs-string">""</span>: [<span class="hljs-string">"Total Bookings"</span>, <span class="hljs-string">"Total Amount ($)"</span>],
                booking_info.get_timestamp(): [
                    booking_info.get_total_bookings(),
                    booking_info.get_total_amount(),
                ],
            }
            logging.info(<span class="hljs-string">"Converting the data into pandas dataframe"</span>)
            df = pd.DataFrame(data)
            logging.info(<span class="hljs-string">"Inserting the data into the excelsheet"</span>)
            <span class="hljs-keyword">with</span> pd.ExcelWriter(sheet_name, engine=<span class="hljs-string">"xlsxwriter"</span>) <span class="hljs-keyword">as</span> writer:
                df.to_excel(writer, sheet_name=<span class="hljs-string">"Sheet1"</span>, index=<span class="hljs-literal">False</span>)
            logging.info(<span class="hljs-string">"Successfully inserted data into the excelsheet"</span>)
        <span class="hljs-keyword">except</span> ValueError <span class="hljs-keyword">as</span> e:
            logging.error(<span class="hljs-string">"Error converting data into excel: %s"</span>, e)
</code></pre>
<p>The <code>__convert_to_excelsheet</code> method within the <code>DataExporter</code> class is responsible for structuring and converting extracted booking data into an Excel sheet. </p>
<p>It accepts two input parameters. The first parameter, <code>data</code>, is expected to be a list containing specific booking data. This data includes the total number of bookings, the total booking amount, and a timestamp for which data was extracted. The second parameter, <code>sheet_name</code>, represents the desired name for the Excel sheet that will contain the formatted data.</p>
<p>A key aspect of the method is the structuring of the data. To achieve this, the method initiates the creation of a <code>BookingInfo</code> object, referred to as <code>booking_info</code>. The <code>BookingInfo</code> object provides a structured representation of the booking data, which simplifies the subsequent formatting and presentation.</p>
<p>Following the creation of the <code>booking_info</code> object, a new dictionary called <code>data</code> is generated. This dictionary is designed to structure the data in a format suitable for conversion into an Excel sheet. </p>
<p>The dictionary consists of two key-value pairs:</p>
<ul>
<li>The first pair uses an empty string as the key and contains a list with two header values, "Total Bookings" and "Total Amount ($)".</li>
<li>The second pair uses the timestamp obtained from <code>booking_info.get_timestamp()</code> as the key and includes a list with two elements: the total number of bookings (<code>booking_info.get_total_bookings()</code>) and the total booking amount (<code>booking_info.get_total_amount()</code>).</li>
</ul>
<p>This dictionary allows the data to be inserted in the excel sheet as below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/Screenshot-2023-10-29-135512.png" alt="Image" width="600" height="400" loading="lazy">
<em>Sample Excel Sheet</em></p>
<p>Then, the structured <code>data</code> dictionary is converted into a pandas DataFrame, referred to as <code>df</code>. Dataframes are a commonly used data structures for handling tabular data in Python. This step streamlines the manipulation and export of the data for further processing or visualization. </p>
<p>To create the Excel sheet, the code uses the <code>pd.ExcelWriter</code> context manager with the "xlsxwriter" engine. This context manager ensures that the Excel file is appropriately prepared for data insertion. The <code>sheet_name</code> parameter is supplied to specify the name of the sheet within the Excel file.</p>
<p>The data within the DataFrame, <code>df</code>, is then written to the Excel sheet. The <code>to_excel</code> method is used in conjunction with the <code>writer</code> object, and the <code>index</code> parameter is set to <code>False</code>. This specific configuration excludes the default row numbers that are typically included in Excel sheets.</p>
<h2 id="heading-how-to-combine-the-functionalities">How to Combine the Functionalities</h2>
<p>Now let's write a public method that the users can use to extract the data from the database and convert the extracted data into the Excel sheet file.</p>
<pre><code class="lang-python">...


<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DataExporter</span>:</span>

    ...

    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">generate_excelsheet</span>(<span class="hljs-params">
        self,
        start_timestamp: datetime,
        end_timestamp: datetime,
        sheet_name: str = <span class="hljs-string">"Bookings Data.xlsx"</span>,
    </span>) -&gt; bool:</span>
        <span class="hljs-string">"""
        Generate an Excel sheet with booking data for a specified time range.

        Args:
            start_timestamp (datetime): The start of the time range.
            end_timestamp (datetime): The end of the time range.
            sheet_name (str, optional): Name of the Excel sheet to be created. Defaults to "Bookings Data.xlsx".

        Returns:
            bool: True if excelsheet was generated successfully else False

        Note:
            This method logs errors but does not raise exceptions to avoid breaking the workflow.
        """</span>
        data = self.__fetch_from_database(start_timestamp, end_timestamp)
        <span class="hljs-keyword">if</span> data <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>:
            self.__convert_to_excelsheet(data, sheet_name)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
        <span class="hljs-keyword">else</span>:
            logging.error(<span class="hljs-string">"No data to convert generate excelsheet"</span>)
            <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
</code></pre>
<p>This method accepts several parameters, including <code>start_timestamp</code> and <code>end_timestamp</code>, which define the beginning and end of the time period for data extraction. There's also an optional <code>sheet_name</code> parameter that allows the user to specify the name of the Excel sheet. By default, the sheet is named "Bookings Data.xlsx" to provide a convenient default option.</p>
<p>Upon execution, the method initiates the data retrieval process by calling the <code>__fetch_from_database</code> method, an internal private method of the class, with the specified time range. </p>
<p>If the data retrieval is successful and data is available, the method proceeds to call the <code>__convert_to_excelsheet</code> method. This structures and formats the data for insertion into the Excel sheet. </p>
<p>If, on the other hand, no data is available for the provided time range, the method logs an error message and returns "False" to indicate that the Excel sheet generation was unsuccessful.</p>
<h2 id="heading-how-to-send-an-email-with-the-bookings-data-report">How to Send an Email with the Bookings Data Report</h2>
<p>In this section, you will learn how you can <a target="_blank" href="https://blog.ashutoshkrris.in/how-to-send-emails-using-python">use Python to send an email</a> with a bookings data report as an attachment.</p>
<p>Create a <code>mailer.py</code> file and add the following content:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> logging
<span class="hljs-keyword">import</span> os
<span class="hljs-keyword">import</span> smtplib
<span class="hljs-keyword">import</span> ssl

<span class="hljs-keyword">from</span> email <span class="hljs-keyword">import</span> encoders
<span class="hljs-keyword">from</span> email.mime.base <span class="hljs-keyword">import</span> MIMEBase
<span class="hljs-keyword">from</span> email.mime.multipart <span class="hljs-keyword">import</span> MIMEMultipart
<span class="hljs-keyword">from</span> email.mime.text <span class="hljs-keyword">import</span> MIMEText

logging.basicConfig(
    format=<span class="hljs-string">"%(asctime)s | %(levelname)s : %(message)s"</span>, level=logging.INFO
)

SMTP_SERVER = os.environ.get(<span class="hljs-string">"SMTP_SERVER"</span>)
PORT = os.environ.get(<span class="hljs-string">"EMAIL_PORT"</span>)
EMAIL = os.environ.get(<span class="hljs-string">"EMAIL"</span>)
PASSWORD = os.environ.get(<span class="hljs-string">"PASSWORD"</span>)


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">send_email</span>(<span class="hljs-params">to_email: str, subject: str, attachment_name: str</span>):</span>
    <span class="hljs-string">"""
    Send an email with an attachment to the specified recipient.

    Args:
        to_email (str): The recipient's email address.
        subject (str): The subject of the email.
        attachment_name (str): The filename of the attachment.

    Note:
        This function assumes that the SMTP server requires TLS encryption.

    Raises:
        smtplib.SMTPException: If there is an issue with sending the email.

    """</span>
    message = MIMEMultipart()
    message[<span class="hljs-string">"From"</span>] = EMAIL
    message[<span class="hljs-string">"To"</span>] = to_email
    message[<span class="hljs-string">"Subject"</span>] = subject
    body = <span class="hljs-string">"Hi there\n\nPlease find attached your report.\n\nThanks"</span>

    message.attach(MIMEText(body, <span class="hljs-string">"plain"</span>))

    <span class="hljs-keyword">with</span> open(attachment_name, <span class="hljs-string">"rb"</span>) <span class="hljs-keyword">as</span> file:
        part = MIMEBase(
            <span class="hljs-string">"application"</span>, <span class="hljs-string">"vnd.openxmlformats-officedocument.spreadsheetml.sheet"</span>
        )
        part.set_payload(file.read())

    encoders.encode_base64(part)

    part.add_header(
        <span class="hljs-string">"Content-Disposition"</span>,
        <span class="hljs-string">f"attachment; filename= <span class="hljs-subst">{attachment_name}</span>"</span>,
    )

    logging.info(<span class="hljs-string">f"Attaching <span class="hljs-subst">{attachment_name}</span> to the email"</span>)
    message.attach(part)
    text = message.as_string()

    context = ssl.create_default_context()
    <span class="hljs-keyword">with</span> smtplib.SMTP(SMTP_SERVER, PORT) <span class="hljs-keyword">as</span> server:
        logging.info(<span class="hljs-string">f"Sending email to <span class="hljs-subst">{to_email}</span>"</span>)
        server.starttls(context=context)
        server.login(EMAIL, PASSWORD)
        server.sendmail(EMAIL, to_email, text)
        logging.info(<span class="hljs-string">f"Successfully sent the email to <span class="hljs-subst">{to_email}</span>"</span>)
</code></pre>
<p>As usual, we have configured the logger and environment variables in our script. </p>
<p>The core functionality is encapsulated within the <code>send_email</code> function. This function takes three parameters:</p>
<ol>
<li><code>to_email</code>: The recipient's email address.</li>
<li><code>subject</code>: The subject of the email.</li>
<li><code>attachment_name</code>: The filename of the attachment, which should be the bookings data report in this context.</li>
</ol>
<p>Within the function, we construct an email message using the <code>MIMEMultipart</code> class. This message includes the sender's email address, recipient's email address, subject, and a plain text body with a simple message.</p>
<p>The script allows attaching the bookings data report as an attachment. It reads the attachment file, encodes it, and adds it to the email message. This ensures that the recipient can easily access and download the data report from the email.</p>
<p>You can learn how you can add attachments while sending emails using Python <a target="_blank" href="https://blog.ashutoshkrris.in/how-to-send-emails-using-python#heading-including-attachments">here</a>.</p>
<p>The <code>create_default_context</code> function from the <code>ssl</code> library creates a secure SSL context for email communication. Finally, the script connects to the SMTP server, logs in using the sender's email address and password, sends the email, and logs a success message upon successful transmission.</p>
<h2 id="heading-how-to-test-the-flow">How to Test the Flow</h2>
<p>Let's finally test the flow of the application.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/finally-about-time.gif" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In this section, we will automate the monthly reports. Create a <code>main.py</code> file and add the following content:</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> exporter <span class="hljs-keyword">import</span> DataExporter
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime
<span class="hljs-keyword">from</span> mailer <span class="hljs-keyword">import</span> send_email

start_timestamp = datetime(<span class="hljs-number">2023</span>, <span class="hljs-number">5</span>, <span class="hljs-number">28</span>, <span class="hljs-number">00</span>, <span class="hljs-number">00</span>, <span class="hljs-number">00</span>)  <span class="hljs-comment"># May 28 2023 00:00:00</span>
end_timestamp = datetime(<span class="hljs-number">2023</span>, <span class="hljs-number">8</span>, <span class="hljs-number">20</span>, <span class="hljs-number">23</span>, <span class="hljs-number">59</span>, <span class="hljs-number">59</span>)  <span class="hljs-comment"># Aug 20 2023 23:59:59</span>

exporter = DataExporter()
<span class="hljs-keyword">if</span> exporter.generate_excelsheet(
        start_timestamp, end_timestamp, sheet_name=<span class="hljs-string">"Bookings Data.xlsx"</span>):
    send_email(<span class="hljs-string">"myemail@gmail.com"</span>, <span class="hljs-string">"Your Report"</span>, <span class="hljs-string">"Bookings Data.xlsx"</span>)
</code></pre>
<p>In the above code, we create two timestamp objects, <code>start_timestamp</code> and <code>end_timestamp</code>, to specify a time range. We have the start date set to May 28, 2023 at midnight and the end date set to August 20, 2023 just before midnight. </p>
<p>Next, we create an instance of the <code>DataExporter</code> class, which handles the data export and Excel sheet generation. The <code>generate_excelsheet</code> method of this instance is called with the previously defined timestamps to create a report related to bookings. </p>
<p>Finally, the code sends an email with the generated Excel sheet as an attachment using the <code>send_email</code> function.</p>
<h2 id="heading-how-to-schedule-the-application">How to Schedule the Application</h2>
<p>Next, our goal is to automate the report scheduling process. We aim to schedule report deliveries for two distinct scenarios: on every Monday for the previous week's data, and on the 1st day of every month for the previous month's information. </p>
<p>To schedule the execution, you will need to install the <code>schedule</code> library:</p>
<pre><code class="lang-bash">pip install schedule
</code></pre>
<p>Once the library is installed, here's how you can do automate the monthly and weekly reports:</p>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> schedule
<span class="hljs-keyword">from</span> exporter <span class="hljs-keyword">import</span> DataExporter
<span class="hljs-keyword">from</span> datetime <span class="hljs-keyword">import</span> datetime, timedelta
<span class="hljs-keyword">from</span> mailer <span class="hljs-keyword">import</span> send_email


<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">main</span>():</span>
    today = datetime.now()
    sheet_name = <span class="hljs-string">"Bookings Data.xlsx"</span>

    <span class="hljs-keyword">if</span> today.weekday() == <span class="hljs-number">0</span>:  <span class="hljs-comment"># Check if it's Monday (0 means Monday)</span>
        <span class="hljs-comment"># It's Monday, fetch data for the previous week (Monday to Sunday)</span>
        start_timestamp = (today - timedelta(days=<span class="hljs-number">7</span>)
                           ).replace(hour=<span class="hljs-number">0</span>, minute=<span class="hljs-number">0</span>, second=<span class="hljs-number">0</span>, microsecond=<span class="hljs-number">0</span>)
        end_timestamp = (today - timedelta(days=<span class="hljs-number">1</span>)
                         ).replace(hour=<span class="hljs-number">23</span>, minute=<span class="hljs-number">59</span>, second=<span class="hljs-number">59</span>, microsecond=<span class="hljs-number">0</span>)
        sheet_name = <span class="hljs-string">"Weekly Report.xlsx"</span>
    <span class="hljs-keyword">elif</span> today.day == <span class="hljs-number">29</span>:
        <span class="hljs-comment"># It's the 1st day of the month, fetch data for the last month</span>
        start_timestamp = (today.replace(day=<span class="hljs-number">1</span>) - timedelta(days=<span class="hljs-number">1</span>)
                           ).replace(day=<span class="hljs-number">1</span>, hour=<span class="hljs-number">0</span>, minute=<span class="hljs-number">0</span>, second=<span class="hljs-number">0</span>, microsecond=<span class="hljs-number">0</span>)
        end_timestamp = (today.replace(day=<span class="hljs-number">1</span>) - timedelta(days=<span class="hljs-number">1</span>)
                         ).replace(hour=<span class="hljs-number">23</span>, minute=<span class="hljs-number">59</span>, second=<span class="hljs-number">59</span>, microsecond=<span class="hljs-number">0</span>)
        sheet_name = <span class="hljs-string">"Monthly Report.xlsx"</span>

    exporter = DataExporter()
    exporter.generate_excelsheet(
        start_timestamp, end_timestamp, sheet_name)

    send_email(<span class="hljs-string">"youremail@gmail.com"</span>,
               <span class="hljs-string">"Your Report"</span>, sheet_name)


schedule.every().day.at(<span class="hljs-string">"00:00"</span>).do(main)

<span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
    schedule.run_pending()
</code></pre>
<p>The above script uses the <code>schedule</code> library to run the <code>main</code> function daily at midnight. The <code>main</code> function calculates the timestamps for data extraction and Excel sheet generation. After generating the Excel sheet, the script sends it via email to a specified recipient.</p>
<p>If the script runs on a Monday, it sets up to generate a weekly report. It calculates the <code>start_timestamp</code> and <code>end_timestamp</code> for the previous week. The <code>start_timestamp</code> is set to the previous Monday at midnight (00:00:00), and the <code>end_timestamp</code> is set to the previous Sunday just before midnight (23:59:59). The Excel sheet is named "Weekly Report.xlsx."</p>
<p>On the 1st day of the month, the script shifts its focus to generating a monthly report. It calculates the <code>start_timestamp</code> and <code>end_timestamp</code> to encompass the entire previous month. The <code>start_timestamp</code> is set to the first day of the previous month at midnight (00:00:00), while the <code>end_timestamp</code> is set to the last day of the previous month just before midnight (23:59:59). The Excel sheet is named "Monthly Report.xlsx."</p>
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>In this tutorial, you learned how you can leverage Python to automate generating a report and sending it to email recipients. I hope you found the tutorial helpful!</p>
<h3 id="heading-future-scope">Future Scope</h3>
<ul>
<li>You can add the email recipients in a database and fetch their list from there instead of hardcoding them in the code itself. This will make the application more configurable.</li>
<li>You can also use Cron Jobs to automate the execution of the script every day at midnight. In that case, you won't need the <code>schedule</code> library.</li>
</ul>
<p>Here's a link to the <a target="_blank" href="https://github.com/ashutoshkrris/report-automation">Github Code Repository</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Full Stack Project Tutorial – Create a Recipe App Using React, Node.js and PostgreSQL ]]>
                </title>
                <description>
                    <![CDATA[ In this in-depth tutorial, we'll build a full stack recipe app from scratch, using React, Node.js, Postgres and the Spoonacular API. We'll cover features such as: Building an API server in Node Integrating securely with a 3rd party API Interacting w... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/full-stack-project-create-a-recipe-app-using-react-node-js/</link>
                <guid isPermaLink="false">66c8c8d4c4cede4e0083f737</guid>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chris Blakely ]]>
                </dc:creator>
                <pubDate>Thu, 19 Oct 2023 20:31:03 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/react-note-photo-gallery-app--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this in-depth tutorial, we'll build a full stack recipe app from scratch, using React, Node.js, Postgres and the Spoonacular API. We'll cover features such as:</p>
<ul>
<li>Building an API server in Node</li>
<li>Integrating securely with a 3rd party API</li>
<li>Interacting with a Postgres database using Prisma</li>
<li>Making API requests from React</li>
<li>Creating reusable components</li>
<li>Working with pagination</li>
<li>Working with UI elements such as tabs, image grids, modals and styling</li>
</ul>
<p>Let's dive in.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></li>
<li><a class="post-section-overview" href="#try-it-yourself-first">GitHub Repositor</a>ies</li>
<li><a class="post-section-overview" href="#heading-video-tutorial">Video Tutorial</a></li>
<li><a class="post-section-overview" href="#heading-project-architecture">Project Architecture</a></li>
<li><a class="post-section-overview" href="#heading-how-to-setup-the-backend">How to Setup the Backend</a></li>
<li><a class="post-section-overview" href="#heading-how-to-setup-the-database-and-prisma">How to Setup the Database and Prisma</a></li>
<li><a class="post-section-overview" href="#heading-how-to-get-and-secure-a-spoonacular-api-key">How to Get and Secure a Spoonacular API Key</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-search-endpoint">How to Create the Search Endpoint</a></li>
<li><a class="post-section-overview" href="#heading-how-to-setup-the-frontend">How to Setup the Frontend</a></li>
<li><a class="post-section-overview" href="#heading-how-to-call-the-search-api-and-display-results-on-the-frontend">How to Call the Search API and Display Results on the Frontend</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-the-search-input-and-recipe-card-component">How to Create the Search Input and Recipe Card Component</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-pagination-and-view-more-functionality">How to Build the Pagination and View More Functionality</a></li>
<li><a class="post-section-overview" href="#how-to-build-the-recipe-summary-and-build-more-component">How to Build the Recipe Summary Modal Component</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-endpoints-to-getcreatedelete-favorite-recipes">How to Create Endpoints to Get/Create/Delete Favorite Recipes</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-favorites-functionality-to-the-frontend">How to Add Favorites Functionality to the Frontend</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-cssstyling">How to Adding CSS/Styling</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Since we will be focusing on how to build a project, there are a few prerequisites that will be needed to get the most out of this tutorial:</p>
<ul>
<li>Some knowledge about web development concepts (frontend, backend, databases, API's, REST).</li>
<li>Some knowledge of JavaScript (variables, functions, objects, arrays, and so on).</li>
<li>Basic understanding on React (how to create components, add styles, work with state).</li>
<li>Basic understanding on Node.js/Express (working with APIs).</li>
</ul>
<h2 id="heading-github-repositories">GitHub Repositories</h2>
<h3 id="heading-completed-code">Completed Code</h3>
<p><a target="_blank" href="https://github.com/chrisblakely01/react-node-recipe-app">You can find the completed code on GitHub by clicking here,</a> or clone the repo:</p>
<pre><code>git clone git@github.com:chrisblakely01/react-node-recipe-app.git
</code></pre><h3 id="heading-starter-code">Starter Code</h3>
<p>If you want to save some time and skip the initial setup, <a target="_blank" href="https://github.com/chrisblakely01/react-node-recipe-app-starter">you can find the starter code here.</a> This has a skeleton frontend/backend project already setup, as well as the basic layout and CSS. Or clone the repo:</p>
<pre><code>git clone git@github.com:chrisblakely01/react-node-recipe-app-starter.git
</code></pre><h2 id="heading-video-tutorial">Video Tutorial</h2>
<p>If you'd like to learn from the video version as well, here it is:</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/5wwaQ4GiSNU" 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-project-architecture">Project Architecture</h2>
<p>Here's a diagram that illustrates how the various components of our app will interact with each other:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/10/recipe-app-architecture.png" alt="recipe-app-architecture" width="600" height="400" loading="lazy">
<em>Diagram illustrating the various components of the app</em></p>
<p>We will have a React front end and a Node backend. These two will communicate through specific endpoints. We will establish five endpoints, all housed within our backend.</p>
<p>We can categorize these endpoints into two distinct groups. The first group will cater to recipe searches. It will invoke the recipe API and return the results based on a given search query. Our front end will initiate a call to our backend, which will then relay the search request to the recipe API. </p>
<p>We choose not to call the recipe API directly from our front end because it requires an API key—a form of authentication similar to a password. Exposing this key in our front end code could lead to unauthorized access if someone delves into the code through their browser to retrieve the API key.</p>
<p>It's a more secure practice to house the API key on our backend within environment variables. From there, we can call the recipe API and then transmit the response back to our front end. </p>
<p>This approach aligns with common practices in production environments. It also offers the flexibility to modify the data on the backend, if necessary, before sending it back to the front end. And it enhances performance, as the UI will not have to manage multiple API requests to and from the recipe API.</p>
<p>That's the essence of how our search functionality will operate. We'll also have several endpoints to add, create, and delete favorites. These favorites will be stored in our own database, providing a clear picture of what we aim to build.</p>
<h2 id="heading-how-to-setup-the-backend">How to Setup the Backend</h2>
<p>In this tutorial, we will walk through the process of building a full-stack recipe application. We'll set up the backend, create the frontend, and link it to a database. We will also connect to a recipe API using an API key. </p>
<p>If you prefer to skip the setup, a starter code is available on CodeCoyotes, which includes some basic setup and CSS. But you'll still need to create a database and obtain an API key.</p>
<p>Let’s start by setting up our workspace:</p>
<h3 id="heading-step-1-setup-your-workspace">Step 1: Setup Your Workspace</h3>
<p>Start by opening Visual Studio Code (or your preferred code editor). Create a new folder named <code>recipe-app</code> on your desktop or another location. Then drag this folder into the Visual Studio Code window to open it.</p>
<p>Your folder structure should now look like this:</p>
<pre><code class="lang-plaintext">recipe-app
</code></pre>
<h3 id="heading-step-2-setup-the-backend">Step 2: Setup the Backend</h3>
<p>In the <code>recipe-app</code> folder, create another folder named <code>backend</code>.</p>
<p>Navigate to <code>View -&gt; Terminal</code> in Visual Studio Code to open a terminal. Change your directory to the <code>backend</code> folder using the command <code>cd backend</code>.</p>
<p>Type <code>npm init</code> to initialize a new npm package, then hit Enter to move through the prompts. For the entry point, type <code>./src/index.ts</code> and hit Enter.</p>
<p>Your folder structure should now look like this:</p>
<pre><code class="lang-plaintext">recipe-app
|-- backend
    |-- package.json
</code></pre>
<h3 id="heading-step-3-install-the-dependencies">Step 3: Install the Dependencies</h3>
<p>First, install the necessary dependencies using the following command:</p>
<pre><code class="lang-bash">npm install express prisma @prisma/client cors
</code></pre>
<p>Now, install the development dependencies:</p>
<pre><code class="lang-bash">npm install --save-dev ts-node typescript nodemon @types/cors @types/express @types/node
</code></pre>
<p>Your folder structure should now look like this:</p>
<pre><code class="lang-plaintext">recipe-app
|-- backend
    |-- node_modules
    |-- package.json
    |-- package-lock.json
</code></pre>
<h3 id="heading-step-4-setup-your-backend-code">Step 4: Setup Your Backend Code</h3>
<p>In the <code>backend</code> folder, create a new folder named <code>src</code>. Inside <code>src</code>, create a file named <code>index.ts</code>.</p>
<p>Add the following code to <code>index.ts</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> cors <span class="hljs-keyword">from</span> <span class="hljs-string">"cors"</span>;

<span class="hljs-keyword">const</span> app = express();

app.use(express.json());
app.use(cors());

app.get(<span class="hljs-string">"/api/recipe/search"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  res.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"success"</span> });
});

app.listen(<span class="hljs-number">5000</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Server running on localhost:5000"</span>);
});
</code></pre>
<h3 id="heading-step-5-add-the-start-script">Step 5: Add the Start Script</h3>
<p>First, open <code>package.json</code> in the <code>backend</code> folder. In the <code>scripts</code> section, replace the <code>test</code> script with a <code>start</code> script as follows:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"npx nodemon ./src/index.ts"</span>
}
</code></pre>
<h3 id="heading-step-6-run-your-backend">Step 6: Run Your Backend</h3>
<p>In the terminal, ensure you are in the <code>backend</code> folder, then type <code>npm start</code> to run your backend server.</p>
<p>Then open a browser and go to <code>http://localhost:5000/api/recipe/search</code>. You should see a response with the message <code>success</code>.</p>
<p>Congratulations! You have successfully set up and run your backend server. In the next part of this tutorial, we will focus on setting up the frontend and connecting to a database.</p>
<h2 id="heading-how-to-setup-the-database-and-prisma">How to Setup the Database and Prisma</h2>
<p>In this section, we'll focus on setting up a Postgres database using ElephantSQL and integrating Prisma to interact with our database effortlessly. Let's jump right in!</p>
<h3 id="heading-step-1-set-up-the-elephantsql-database">Step 1: Set Up the ElephantSQL Database</h3>
<p>Start by navigating to <a target="_blank" href="https://www.elephantsql.com/">ElephantSQL</a>. Click on "Get a managed database today," followed by selecting the "Tiny Turtle" plan for a free instance.</p>
<p>Sign in or create an account to proceed to the "Create new instance" page.</p>
<p>Then enter a name for your database (for example, <code>recipe-app-db</code>), keep the plan on the free tier, and choose a region closest to you.</p>
<p>Click on "Review," verify the details, and then click on "Create instance."</p>
<h3 id="heading-step-2-retrieve-the-database-credentials">Step 2: Retrieve the Database Credentials</h3>
<p>Once your instance is created, click on it to view the details.</p>
<p>Locate and copy the URL under the "Details" section. This URL contains the credentials needed to connect to your database.</p>
<h3 id="heading-step-3-create-an-environment-file">Step 3: Create an Environment File</h3>
<p>Now, return to your code editor and open the <code>backend</code> folder.</p>
<p>Create a new file named <code>.env</code>. Inside the <code>.env</code> file, add the following line:</p>
<pre><code class="lang-plaintext">DATABASE_URL=&lt;Your-Copied-Database-URL&gt;
</code></pre>
<p>Replace <code>&lt;Your-Copied-Database-URL&gt;</code> with the URL you copied from ElephantSQL.</p>
<h3 id="heading-step-4-integrate-prisma">Step 4: Integrate Prisma</h3>
<p>Stop your server if it's running by pressing <code>Ctrl + C</code> (or <code>Cmd + C</code> on Mac) in the terminal.</p>
<p>In the terminal, ensure you are in the <code>backend</code> directory, and type the following command to initialize Prisma:</p>
<pre><code class="lang-bash">npx prisma init
</code></pre>
<p>This command will create a new folder named <code>prisma</code> with a file named <code>schema.prisma</code>.</p>
<h3 id="heading-step-5-verify-prisma-integration">Step 5: Verify Prisma Integration</h3>
<p>Now, open <code>prisma/schema.prisma</code> to ensure your <code>DATABASE_URL</code> has been detected correctly.</p>
<p>Start your server again with the command <code>npm start</code>. Then navigate to <code>http://localhost:5000/api/recipe/search</code> in your browser to ensure your API still works and returns the success message.</p>
<p>Your folder structure should now include the Prisma folder and look like this:</p>
<pre><code class="lang-plaintext">recipe-app
|-- backend
    |-- prisma
        |-- schema.prisma
    |-- .env
    |-- ...
</code></pre>
<h2 id="heading-how-to-get-and-secure-a-spoonacular-api-key">How to Get and Secure a Spoonacular API Key</h2>
<h3 id="heading-step-1-obtain-an-api-key-from-spoonacular">Step 1: Obtain an API Key from Spoonacular</h3>
<p>To do this, navigate to <a target="_blank" href="https://spoonacular.com/">Spoonacular</a> and click on "Start Now." Sign up for an account and proceed to the dashboard.</p>
<p>Within the dashboard, click on “Profile” on the left-hand side, and find the section related to API keys. Generate a new API key, and copy it to your clipboard.</p>
<h3 id="heading-step-2-store-the-api-key-securely">Step 2: Store the API Key Securely</h3>
<p>Now, return to your code editor and open the <code>.env</code> file in the <code>backend</code> folder.</p>
<p>Add a new environment variable to store your API key as follows:</p>
<pre><code class="lang-plaintext">API_KEY=&lt;Your-Copied-API-Key&gt;
</code></pre>
<p>Replace <code>&lt;Your-Copied-API-Key&gt;</code> with the API key you copied from Spoonacular.</p>
<h3 id="heading-step-3-install-and-setup-thunder-client">Step 3: Install and Setup Thunder Client</h3>
<p>In Visual Studio Code, navigate to the extensions tab (square icon on the sidebar), and search for Thunder Client.</p>
<p>Install Thunder Client by clicking on the Install button. Once installed, you will see a new icon (a purple thunderbolt) on the sidebar.</p>
<h3 id="heading-step-4-test-the-api-key-with-thunder-client">Step 4: Test the API Key with Thunder Client</h3>
<p>Now click on the Thunder Client icon, then click on “New Request.”</p>
<p>Copy the endpoint URL for searching recipes from Spoonacular's documentation. It should look something like this: <code>https://api.spoonacular.com/recipes/complexSearch</code>.</p>
<p>Paste this URL into the URL bar in Thunder Client.</p>
<p>Under the query tab, add two new parameters:</p>
<ul>
<li><code>apiKey</code> with the value of your API key.</li>
<li><code>query</code> with a value of <code>burgers</code> or any other term you wish to search for.</li>
</ul>
<p>Then hit "Send" to issue the request and observe the response. You should see a list of recipes related to the term you searched for, indicating that your API key and endpoint are working correctly.</p>
<h2 id="heading-how-to-create-the-search-endpoint">How to Create the Search Endpoint</h2>
<h3 id="heading-step-1-setup-a-new-file-for-recipe-api-logic">Step 1: Setup a New File for Recipe API Logic</h3>
<p>First, navigate to your <code>backend/src</code> folder and create a new file named <code>recipe-api.ts</code>.</p>
<p>In <code>recipe-api.ts</code>, initiate a constant to store your API key from the environment variables:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> API_KEY = process.env.API_KEY;
</code></pre>
<h3 id="heading-step-2-create-the-searchrecipes-function">Step 2: Create the searchRecipes Function</h3>
<p>In <code>recipe-api.ts</code>, define a new asynchronous function <code>searchRecipes</code> that takes in a <code>searchTerm</code> and a <code>page</code> as parameters:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> searchRecipes = <span class="hljs-keyword">async</span> (searchTerm: <span class="hljs-built_in">string</span>, page: <span class="hljs-built_in">number</span>) =&gt; {
  <span class="hljs-keyword">if</span> (!API_KEY) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"API key not found"</span>);
  }

  <span class="hljs-keyword">const</span> baseURL = <span class="hljs-string">"https://api.spoonacular.com/recipes/complexSearch"</span>;
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(baseURL);

  <span class="hljs-keyword">const</span> queryParams = {
    apiKey: API_KEY,
    query: searchTerm,
    <span class="hljs-built_in">number</span>: <span class="hljs-number">10</span>,
    offset: (page - <span class="hljs-number">1</span>) * <span class="hljs-number">10</span>,
  };

  url.search = <span class="hljs-keyword">new</span> URLSearchParams(queryParams).toString();

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> searchResponse = <span class="hljs-keyword">await</span> fetch(url.toString());
    <span class="hljs-keyword">const</span> resultsJson = <span class="hljs-keyword">await</span> searchResponse.json();
    <span class="hljs-keyword">return</span> resultsJson;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
  }
};
</code></pre>
<h3 id="heading-step-3-import-and-use-searchrecipes-in-indexts">Step 3: Import and Use <code>searchRecipes</code> in index.ts</h3>
<p>Now, navigate back to <code>index.ts</code> in the <code>backend/src</code> folder.</p>
<p>Import the <code>searchRecipes</code> function from <code>recipe-api.ts</code> at the top of your file:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> RecipeAPI <span class="hljs-keyword">from</span> <span class="hljs-string">"./recipe-api"</span>;
</code></pre>
<p>Locate the endpoint where you wish to utilize the <code>searchRecipes</code> function, and modify it to call <code>searchRecipes</code> with the appropriate arguments, then return the results:</p>
<pre><code class="lang-typescript">app.get(<span class="hljs-string">"/api/recipe/search"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> searchTerm = req.query.searchTerm <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>;
  <span class="hljs-keyword">const</span> page = <span class="hljs-built_in">parseInt</span>(req.query.page <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>);

  <span class="hljs-keyword">const</span> results = <span class="hljs-keyword">await</span> recipeAPI.searchRecipes(searchTerm, page);
  <span class="hljs-keyword">return</span> res.json(results);
});
</code></pre>
<h3 id="heading-step-4-test-your-endpoint">Step 4: Test Your Endpoint</h3>
<p>Now you can restart your server by stopping it (<code>Ctrl + C</code> or <code>Cmd + C</code> on Mac) and then running <code>npm start</code>.</p>
<p>Test your endpoint by sending a GET request with the appropriate query parameters. For example, navigate to <code>http://localhost:5000/api/recipe/search?searchTerm=burgers&amp;page=1</code> in your browser or use a REST client like Postman or Thunder Client.</p>
<p>You should now see a list of recipes returned in response to your request, indicating that your backend logic for calling the Recipe API and returning the results is functioning as expected.</p>
<h2 id="heading-how-to-setup-the-frontend">How to Setup the Frontend</h2>
<p>Before you get started on this section, make sure you're in the top-level directory of your project, which in this case is named <code>recipe-app</code>.</p>
<h3 id="heading-step-1-create-a-react-app-with-vite">Step 1: Create a React App with Vite</h3>
<p>Start by opening a terminal and ensuring you're in the top-level folder (<code>recipe-app</code>).</p>
<p>Then run the following command to install the latest version of Vite:</p>
<pre><code class="lang-bash">npm install vite@latest --save-dev
</code></pre>
<p>Now, initiate a new React project with Vite by running:</p>
<pre><code class="lang-bash">npx create-vite frontend --template react-ts
</code></pre>
<p>This command creates a new folder named <code>frontend</code>, sets it up as a React project, and specifies TypeScript as the language.</p>
<h3 id="heading-step-2-navigate-to-your-new-react-app">Step 2: Navigate to Your New React App</h3>
<p>Change your directory to the <code>frontend</code> folder:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> frontend
</code></pre>
<p>Then install any necessary dependencies:</p>
<pre><code class="lang-bash">npm install
</code></pre>
<h3 id="heading-step-3-start-the-development-server">Step 3: Start the Development Server</h3>
<p>You can start the development server with the following command:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Your default web browser should now open displaying the initial setup of your React app, which includes a counter example.</p>
<h3 id="heading-step-4-clean-up-and-personalize-your-react-app">Step 4: Clean Up and Personalize Your React App</h3>
<p>Now you can head back to your code editor and navigate to <code>frontend/src</code>. Delete the <code>index.css</code> file as it won't be needed.</p>
<p>In <code>main.tsx</code>, remove the import statement for <code>index.css</code>.</p>
<p>Now, open <code>App.tsx</code>. Here, you'll see code for a counter. Delete all the content inside <code>App.tsx</code>.</p>
<p>Let’s start fresh by adding the following code to <code>App.tsx</code>:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// src/App.tsx</span>
<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Hello from Recipe App<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>;
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Save <code>App.tsx</code> and check your browser to see the updated text: "Hello from Recipe App".</p>
<h3 id="heading-step-5-setup-your-stylesheet">Step 5: Setup Your Stylesheet</h3>
<p>Go to <code>src</code> and open <code>App.css</code>. Delete all the pre-filled styles but keep the <code>.root</code> class definition empty for now.</p>
<p>Add a <code>font-family</code> property to the <code>.root</code> class:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* src/App.css */</span>
<span class="hljs-selector-class">.root</span> {
  <span class="hljs-attribute">font-family</span>: Helvetica, Arial, sans-serif;
}
</code></pre>
<p>Then head back to <code>App.tsx</code> and import your stylesheet:</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// src/App.tsx</span>
<span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>Then save <code>App.tsx</code> and check your browser to see the updated font.</p>
<p>Now you have a clean slate to start building out the frontend of your recipe app with React and TypeScript, using Vite as your build tool. As you progress, you can now start adding components, routing, and state management to build out your application's UI and functionality.</p>
<h2 id="heading-how-to-call-the-search-api-and-display-results-on-the-frontend">How to Call the Search API and Display Results on the Frontend</h2>
<p>Now we are going to fetch data from an API and display the results on the UI. The API response is structured as follows:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"results"</span>: [
    {
      <span class="hljs-attr">"id"</span>: <span class="hljs-number">650235</span>,
      <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Loaded Turkey Burgers"</span>,
      <span class="hljs-attr">"image"</span>: <span class="hljs-string">"https://spoonacular.com/recipeImages/650235-312x231.jpg"</span>,
      <span class="hljs-attr">"imageType"</span>: <span class="hljs-string">"jpg"</span>
    }
    <span class="hljs-comment">// ... other recipes</span>
  ],
  <span class="hljs-attr">"offset"</span>: <span class="hljs-number">10</span>,
  <span class="hljs-attr">"number"</span>: <span class="hljs-number">10</span>,
  <span class="hljs-attr">"totalResults"</span>: <span class="hljs-number">50</span>
}
</code></pre>
<p>Each recipe object contains an <code>id</code>, <code>title</code>, <code>image</code>, and <code>imageType</code>. We will map through the <code>results</code> array and display each recipe on our UI.</p>
<h3 id="heading-step-1-setup-state">Step 1: Setup State</h3>
<p>In your <code>App.tsx</code>, setup state to store the <code>searchTerm</code> and <code>recipes</code>.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [searchTerm, setSearchTerm] = useState(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [recipes, setRecipes] = useState([]);

  <span class="hljs-comment">// ... rest of your component</span>
};
</code></pre>
<h3 id="heading-step-2-fetch-data-from-the-api">Step 2: Fetch Data from the API</h3>
<p>Create a new file <code>API.ts</code> and set up a function to make the API call.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/API.ts</span>
<span class="hljs-keyword">const</span> searchRecipes = <span class="hljs-keyword">async</span> (searchTerm: <span class="hljs-built_in">string</span>, page: <span class="hljs-built_in">number</span>) =&gt; {
  <span class="hljs-keyword">const</span> baseURL = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">"http://localhost:5000/api/recipes/search"</span>);
  baseURL.searchParams.append(<span class="hljs-string">"searchTerm"</span>, searchTerm);
  baseURL.searchParams.append(<span class="hljs-string">"page"</span>, page.toString());

  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(baseURL.toString());

  <span class="hljs-keyword">if</span> (!response.ok) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`HTTP Error: <span class="hljs-subst">${response.status}</span>`</span>);
  }

  <span class="hljs-keyword">return</span> response.json();
};

<span class="hljs-keyword">export</span> { searchRecipes };
</code></pre>
<h3 id="heading-step-3-create-a-handler-function">Step 3: Create a Handler Function</h3>
<p>Back in <code>App.tsx</code>, import the <code>searchRecipes</code> function and create a handler function to be called when the form is submitted.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React, { useState, FormEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { searchRecipes } <span class="hljs-keyword">from</span> <span class="hljs-string">"./API"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// ... previous state setup</span>

  <span class="hljs-keyword">const</span> handleSearchSubmit = <span class="hljs-keyword">async</span> (event: FormEvent) =&gt; {
    event.preventDefault();

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> { results } = <span class="hljs-keyword">await</span> searchRecipes(searchTerm, <span class="hljs-number">1</span>);
      setRecipes(results);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(error);
    }
  };

  <span class="hljs-comment">// ... rest of your component</span>
};
</code></pre>
<h3 id="heading-step-4-display-the-data">Step 4: Display the Data</h3>
<p>Display the recipes data in your component's return statement.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// ... previous code</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">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSearchSubmit}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
      {recipes.map((recipe) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{recipe.id}</span>&gt;</span>
          Recipe Image Location: {recipe.image}
          <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
          Recipe Title: {recipe.title}
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h3 id="heading-step-5-define-the-recipe-type">Step 5: Define the Recipe Type</h3>
<p>Define a <code>Recipe</code> interface in a new file named <code>types.ts</code>.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// src/types.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> Recipe {
  id: <span class="hljs-built_in">number</span>;
  title: <span class="hljs-built_in">string</span>;
  image: <span class="hljs-built_in">string</span>;
  imageType: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>Back in <code>App.tsx</code>, import the <code>Recipe</code> interface and use it to type your state and map function.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React, { useState, FormEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> { searchRecipes } <span class="hljs-keyword">from</span> <span class="hljs-string">'./API'</span>;
<span class="hljs-keyword">import</span> { Recipe } <span class="hljs-keyword">from</span> <span class="hljs-string">'./types'</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [searchTerm, setSearchTerm] = useState(<span class="hljs-string">''</span>);
  <span class="hljs-keyword">const</span> [recipes, setRecipes] = useState&lt;Recipe[]&gt;([]);

  <span class="hljs-comment">// ... rest of your code</span>

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      {/* ... rest of your return statement */}
      {recipes.map((recipe: Recipe) =&gt; (
        {/* ... rest of your map function */}
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h3 id="heading-step-6-test-your-setup">Step 6: Test Your Setup</h3>
<p>Now, start both your frontend and backend servers. Open your browser, navigate to your app, and try submitting a search. You should see the recipes data displayed on the screen.</p>
<pre><code class="lang-bash"><span class="hljs-comment"># In one terminal</span>
<span class="hljs-built_in">cd</span> frontend
npm run dev

<span class="hljs-comment"># In another terminal</span>
<span class="hljs-built_in">cd</span> backend
npm start
</code></pre>
<p>This setup should now fetch and display recipe data based on the hardcoded <code>searchTerm</code> of "burgers". In a real-world scenario, you'd replace the hardcoded <code>searchTerm</code> with a dynamic value from a user input field.</p>
<h2 id="heading-how-to-create-the-search-input-and-recipe-card-component">How to Create the Search Input and Recipe Card Component</h2>
<h3 id="heading-step-1-setup-your-project">Step 1: Setup Your Project</h3>
<p>Start by setting up a new React project or navigate to your existing project directory.</p>
<pre><code class="lang-bash">npx create-react-app recipe-search-ui
<span class="hljs-built_in">cd</span> recipe-search-ui
</code></pre>
<h3 id="heading-step-2-create-state-hooks-for-user-input-and-data">Step 2: Create State Hooks for User Input and Data</h3>
<p>In your <code>src</code> folder, create a new file named <code>App.tsx</code> and import the necessary dependencies:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> React, { useState, FormEvent } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [searchTerm, setSearchTerm] = useState &lt; string &gt; <span class="hljs-string">""</span>;
  <span class="hljs-keyword">const</span> [recipes, setRecipes] = useState &lt; <span class="hljs-built_in">Array</span> &lt; any &gt;&gt; [];

  <span class="hljs-comment">// ... rest of the code</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Here, we have set up two state hooks: one for capturing the user's search term and another for holding the recipe data returned from the backend.</p>
<h3 id="heading-step-3-build-a-form-to-capture-user-input">Step 3: Build a Form to Capture User Input</h3>
<p>Within the <code>App</code> component, build a form with an input field and a submit button. We'll also create a function to handle the form submission, which will trigger the API call.</p>
<pre><code class="lang-jsx"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ... rest of the code</span>

  <span class="hljs-keyword">const</span> handleSearchSubmit = <span class="hljs-keyword">async</span> (event: FormEvent) =&gt; {
    event.preventDefault();
    <span class="hljs-comment">// ... API call logic</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">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSearchSubmit}</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
          <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span>
          <span class="hljs-attr">required</span>
          <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Enter a search term"</span>
          <span class="hljs-attr">value</span>=<span class="hljs-string">{searchTerm}</span>
          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(event)</span> =&gt;</span> setSearchTerm(event.target.value)}
        /&gt;
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
      {/* ... rest of the code */}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>Now, users can enter their search term, and upon form submission, <code>handleSearchSubmit</code> will be triggered.</p>
<h3 id="heading-step-4-fetch-recipe-data-from-the-backend">Step 4: Fetch Recipe Data from the Backend</h3>
<p>In the <code>handleSearchSubmit</code> function, use the <code>fetch</code> API to send a request to your backend, capture the returned data, and update the <code>recipes</code> state.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> handleSearchSubmit = <span class="hljs-keyword">async</span> (event: FormEvent) =&gt; {
  event.preventDefault();
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
      <span class="hljs-string">`http://localhost:5000/api/recipes/search?searchTerm=<span class="hljs-subst">${searchTerm}</span>`</span>
    );
    <span class="hljs-keyword">if</span> (!response.ok) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`HTTP error! status: <span class="hljs-subst">${response.status}</span>`</span>);
    }
    <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();
    setRecipes(data.results);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
  }
};
</code></pre>
<h3 id="heading-step-5-display-recipe-data">Step 5: Display Recipe Data</h3>
<p>Create a new folder named <code>components</code> in your <code>src</code> directory. Inside this folder, create a file named <code>RecipeCard.tsx</code>. This component will display individual recipe data.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { Recipe } <span class="hljs-keyword">from</span> <span class="hljs-string">"../types"</span>;

interface Props {
  <span class="hljs-attr">recipe</span>: Recipe;
}

<span class="hljs-keyword">const</span> RecipeCard = <span class="hljs-function">(<span class="hljs-params">{ recipe }: Props</span>) =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"recipe-card"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">{recipe.image}</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">img</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"recipe-card-title"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{recipe.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>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> RecipeCard;
</code></pre>
<p>Now, go back to <code>App.tsx</code> and import <code>RecipeCard</code>. Map over the <code>recipes</code> state to display each recipe using the <code>RecipeCard</code> component.</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> RecipeCard <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/RecipeCard"</span>;

<span class="hljs-comment">// ... rest of the code</span>

<span class="hljs-keyword">return</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    {/* ... rest of the code */}
    {recipes.map((recipe) =&gt; (
      <span class="hljs-tag">&lt;<span class="hljs-name">RecipeCard</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{recipe.id}</span> <span class="hljs-attr">recipe</span>=<span class="hljs-string">{recipe}</span> /&gt;</span>
    ))}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);
</code></pre>
<p>Now, when you submit a search term, the UI will display a list of recipe cards containing the images and titles of the recipes returned from the backend.</p>
<h3 id="heading-step-6-test-your-ui">Step 6: Test Your UI</h3>
<p>Run your React app, enter a search term such as "pasta" or "burgers", and submit the form. You should see a list of recipe cards displaying the relevant recipes from the backend.</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>Navigate to <code>http://localhost:3000</code> in your browser and try out your new recipe search UI!</p>
<h2 id="heading-how-to-build-the-pagination-and-view-more-functionality">How to Build the Pagination and View More Functionality</h2>
<h3 id="heading-step-1-backend-pagination">Step 1: Backend Pagination</h3>
<p>We added a <code>page</code> query parameter in the search endpoint. The <code>page</code> value is used to calculate the offset for the recipe data fetched from the database or external API.</p>
<h3 id="heading-step-2-add-view-more-button-to-the-ui">Step 2: Add "View More" Button to the UI</h3>
<p>Navigate to your <code>App.tsx</code> file. Scroll down to the JSX code where you map through the <code>recipes</code> array and add a "View More" button below it.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// ... other code</span>
{
  recipes.map(<span class="hljs-function">(<span class="hljs-params">recipe</span>) =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">RecipeCard</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{recipe.id}</span> <span class="hljs-attr">recipe</span>=<span class="hljs-string">{recipe}</span> /&gt;</span></span>);
}
&lt;button className=<span class="hljs-string">"view-more"</span> onClick={handleViewMoreClick}&gt;
  View More
&lt;/button&gt;;
<span class="hljs-comment">// ... other code</span>
</code></pre>
<h3 id="heading-step-3-create-a-useref-hook-to-store-the-current-page-number">Step 3: Create a <code>useRef</code> Hook to Store the Current Page Number</h3>
<p>Above your component's return statement, create a <code>useRef</code> hook to keep track of the current page number without causing re-renders.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// ... other code</span>
<span class="hljs-keyword">const</span> pageNumber = useRef(<span class="hljs-number">1</span>);
<span class="hljs-comment">// ... other code</span>
</code></pre>
<h3 id="heading-step-4-implement-the-handleviewmoreclick-function">Step 4: Implement the <code>handleViewMoreClick</code> Function</h3>
<p>Define a function called <code>handleViewMoreClick</code> to handle the logic for loading more recipes.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// ... other code</span>
<span class="hljs-keyword">const</span> handleViewMoreClick = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> nextPage = pageNumber.current + <span class="hljs-number">1</span>;
    <span class="hljs-keyword">const</span> nextRecipes = <span class="hljs-keyword">await</span> api.searchRecipes(searchTerm, nextPage);
    setRecipes(<span class="hljs-function">(<span class="hljs-params">prevRecipes</span>) =&gt;</span> [...prevRecipes, ...nextRecipes.results]);
    pageNumber.current = nextPage;
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
  }
};
<span class="hljs-comment">// ... other code</span>
</code></pre>
<h3 id="heading-step-5-reset-page-number-on-new-search">Step 5: Reset Page Number on New Search</h3>
<p>Modify your <code>handleSearchSubmit</code> function to reset the page number back to 1 whenever a new search term is entered.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// ... other code</span>
<span class="hljs-keyword">const</span> handleSearchSubmit = <span class="hljs-keyword">async</span> (event: FormEvent) =&gt; {
  <span class="hljs-comment">// ... other code</span>
  setRecipes(recipes.results);
  pageNumber.current = <span class="hljs-number">1</span>;
};
<span class="hljs-comment">// ... other code</span>
</code></pre>
<h3 id="heading-step-6-test-your-implementation">Step 6: Test Your Implementation</h3>
<p>Run your app and perform a search. Click the "View More" button to load more results. Change the search term and ensure that the page number resets, and you get a fresh list of recipes.</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>Now, as you search for recipes and click "View More," you should see additional recipes being loaded and displayed in the UI.</p>
<h2 id="heading-how-to-build-the-recipe-summary-modal-component">How to Build the Recipe Summary Modal Component</h2>
<p>I'll walk you through this process step-by-step. We'll create a model that displays a recipe summary using a specific endpoint from the provided API.</p>
<h3 id="heading-step-1-understanding-the-recipe-summary-endpoint">Step 1: Understanding the Recipe Summary Endpoint</h3>
<p>You can understand where the summary data comes from by looking at your API documentation. The endpoint you need is called <code>Summarize Recipe</code>. This endpoint requires a recipe ID to generate a summary.</p>
<h3 id="heading-step-2-setup-backend-endpoint">Step 2: Setup Backend Endpoint</h3>
<p>Create a backend endpoint that interfaces with the <code>Summarize Recipe</code> endpoint.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// In your backend, create an endpoint to fetch recipe summary</span>
<span class="hljs-comment">// File: recipe-api.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getRecipeSummary = <span class="hljs-keyword">async</span> (recipeId: <span class="hljs-built_in">string</span>) =&gt; {
  <span class="hljs-keyword">if</span> (!apiKey) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"API key not found"</span>);
  }

  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(
    <span class="hljs-string">`https://api.spoonacular.com/recipes/<span class="hljs-subst">${recipeId}</span>/summary`</span>
  );
  <span class="hljs-keyword">const</span> params = { apiKey: apiKey };
  url.search = <span class="hljs-keyword">new</span> URLSearchParams(params).toString();

  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url.toString());
  <span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> response.json();
  <span class="hljs-keyword">return</span> json;
};
</code></pre>
<h3 id="heading-step-3-create-a-backend-route">Step 3: Create a Backend Route</h3>
<p>Create a route in your backend to handle requests to your new endpoint.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// File: index.ts</span>
app.get(<span class="hljs-string">"/api/recipe/:recipeId/summary"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> recipeId = req.params.recipeId;
  <span class="hljs-keyword">const</span> result = <span class="hljs-keyword">await</span> recipeSummary(recipeId);
  res.json(result);
});
</code></pre>
<h3 id="heading-step-4-create-the-recipe-modal-component">Step 4: Create the Recipe Modal Component</h3>
<p>Create a React component for the recipe modal. We will use the useEffect hook to call the backend endpoint we just created, and store the Recipe Summary data in state.</p>
<p>First add a type for <code>RecipeSummary</code> to <code>types.ts</code></p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> RecipeSummary {
  id: <span class="hljs-built_in">number</span>;
  title: <span class="hljs-built_in">string</span>;
  summary: <span class="hljs-built_in">string</span>;
}
</code></pre>
<pre><code class="lang-jsx"><span class="hljs-comment">// File: RecipeModal.tsx</span>
<span class="hljs-keyword">import</span> React, { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { RecipeSummary } <span class="hljs-keyword">from</span> <span class="hljs-string">"../types"</span>;

interface Props {
  <span class="hljs-attr">recipeId</span>: string;
  onClose: <span class="hljs-function">() =&gt;</span> <span class="hljs-keyword">void</span>;
}

<span class="hljs-keyword">const</span> RecipeModal: React.FC&lt;Props&gt; = <span class="hljs-function">(<span class="hljs-params">{ recipeId, onClose }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [recipeSummary, setRecipeSummary] =
    (useState &lt; RecipeSummary) | (<span class="hljs-literal">null</span> &gt; <span class="hljs-literal">null</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchRecipeSummary = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> summary = <span class="hljs-keyword">await</span> getRecipeSummary(recipeId);
        setRecipeSummary(summary);
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
      }
    };
    fetchRecipeSummary();
  }, [recipeId]);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"overlay"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"modal"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"modal-content"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"modal-header"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{recipeSummary?.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"close-button"</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{onClose}</span>&gt;</span>
              <span class="hljs-symbol">&amp;times;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">span</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">p</span> <span class="hljs-attr">dangerouslySetInnerHTML</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">__html:</span> <span class="hljs-attr">recipeSummary</span>?<span class="hljs-attr">.summary</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">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> RecipeModal;
</code></pre>
<h3 id="heading-step-5-style-the-modal">Step 5: Style the Modal</h3>
<p>Add the following CSS to style the modal:</p>
<pre><code class="lang-css"><span class="hljs-comment">/* File: app.css */</span>
<span class="hljs-selector-class">.overlay</span> {
  <span class="hljs-attribute">position</span>: fixed;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.7</span>);
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1</span>;
}

<span class="hljs-selector-class">.modal</span> {
  <span class="hljs-attribute">position</span>: fixed;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">50%</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">50%</span>;
  <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate</span>(-<span class="hljs-number">50%</span>, -<span class="hljs-number">50%</span>);
  <span class="hljs-attribute">z-index</span>: <span class="hljs-number">2</span>;
  <span class="hljs-attribute">background-color</span>: white;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">2em</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">4px</span>;
  <span class="hljs-attribute">max-width</span>: <span class="hljs-number">500px</span>;
}

<span class="hljs-selector-class">.modal-header</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: row;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">justify-content</span>: space-between;
}
</code></pre>
<h3 id="heading-step-6-render-and-handle-modal-interactions">Step 6: Render and Handle Modal Interactions</h3>
<p>Modify your main component to handle rendering and interactions with the modal.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// File: App.tsx</span>
<span class="hljs-keyword">const</span> App: React.FC = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> [selectedRecipe, setSelectedRecipe] =
    (useState &lt; Recipe) | (<span class="hljs-literal">undefined</span> &gt; <span class="hljs-literal">undefined</span>);

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"App"</span>&gt;</span>
      {/* Other components and logic */}
      {selectedRecipe &amp;&amp; (
        <span class="hljs-tag">&lt;<span class="hljs-name">RecipeModal</span>
          <span class="hljs-attr">recipeId</span>=<span class="hljs-string">{selectedRecipe.id.toString()}</span>
          <span class="hljs-attr">onClose</span>=<span class="hljs-string">{()</span> =&gt;</span> setSelectedRecipe(undefined)}
        /&gt;
      )}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<p>Now, when a user clicks on a recipe, the modal will appear displaying the summary of the selected recipe. The modal can be closed by clicking the close button, which will set the <code>selectedRecipe</code> back to undefined, hiding the modal.</p>
<h2 id="heading-how-to-create-endpoints-to-getcreatedelete-favorite-recipes">How to Create Endpoints to Get/Create/Delete Favorite Recipes</h2>
<h3 id="heading-step-1-setup-the-database">Step 1: Setup the Database</h3>
<p>First, we need to extend our database schema to include a table for storing favorite recipes by their IDs.</p>
<p>First, navigate to the <code>Prisma</code> folder within the <code>backend</code> directory of your project. Then open the <code>schema.prisma</code> file.</p>
<p>Define a new model for favorite recipes as follows:</p>
<pre><code class="lang-prisma">model FavoriteRecipe {
  id        Int    @id @default(autoincrement())
  recipeId  Int    @unique
}
</code></pre>
<h3 id="heading-step-2-synchronize-the-database-schema">Step 2: Synchronize the Database Schema</h3>
<p>Now, let's synchronize the updated schema with our database.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> backend
npx prisma db push
</code></pre>
<h3 id="heading-step-3-setup-the-endpoints">Step 3: Setup the Endpoints</h3>
<p>We need to set up endpoints in our Node backend to handle creating, viewing, and deleting favorites. We'll use the prismaClient to help us perform crud operations on the database.</p>
<p>First, we'll create a new post endpoint like this:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// In backend/index.ts</span>
<span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@prisma/client"</span>;

<span class="hljs-keyword">const</span> prismaClient = <span class="hljs-keyword">new</span> PrismaClient();

app.post(<span class="hljs-string">"/api/recipes/favorite"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { recipeId } = req.body;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> favoriteRecipe = <span class="hljs-keyword">await</span> prismaClient.favoriteRecipe.create({
      <span class="hljs-attr">data</span>: { recipeId },
    });
    res.status(<span class="hljs-number">201</span>).json(favoriteRecipe);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Oops, something went wrong."</span> });
  }
});
</code></pre>
<p>Next, we'll create the View endpoint. To do that, create a utility function to fetch recipe details by IDs:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// In backend/src/recipe-api.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getFavoriteRecipesByIds = <span class="hljs-keyword">async</span> (ids: string[]) =&gt; {
  <span class="hljs-keyword">if</span> (!apiKey) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"API Key not found"</span>);
  }
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">"https://api.spoonacular.com/recipes/informationBulk"</span>);
  url.search = <span class="hljs-keyword">new</span> URLSearchParams({
    <span class="hljs-attr">apiKey</span>: apiKey,
    <span class="hljs-attr">ids</span>: ids.join(<span class="hljs-string">","</span>),
  }).toString();

  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url);
  <span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> response.json();
  <span class="hljs-keyword">return</span> { <span class="hljs-attr">results</span>: json };
};
</code></pre>
<p>Now, create the endpoint to fetch all favorite recipes:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// In backend/index.ts</span>
<span class="hljs-keyword">import</span> { getFavoriteRecipesByIds } <span class="hljs-keyword">from</span> <span class="hljs-string">"./src/recipe-api"</span>;

app.get(<span class="hljs-string">"/api/recipes/favorite"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> favoriteRecipes = <span class="hljs-keyword">await</span> prismaClient.favoriteRecipe.findMany();
    <span class="hljs-keyword">const</span> recipeIds = favoriteRecipes.map(<span class="hljs-function">(<span class="hljs-params">recipe</span>) =&gt;</span>
      recipe.recipeId.toString()
    );
    <span class="hljs-keyword">const</span> favorites = <span class="hljs-keyword">await</span> getFavoriteRecipesByIds(recipeIds);
    res.json(favorites);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Oops, something went wrong."</span> });
  }
});
</code></pre>
<p>Next up is the Delete endpoint:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// In backend/index.ts</span>
app.delete(<span class="hljs-string">"/api/recipes/favorite"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { recipeId } = req.body;
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> prismaClient.favoriteRecipe.delete({
      <span class="hljs-attr">where</span>: { recipeId },
    });
    res.status(<span class="hljs-number">204</span>).send();
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.error(error);
    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Oops, something went wrong."</span> });
  }
});
</code></pre>
<h3 id="heading-step-4-test-the-endpoints">Step 4: Test the Endpoints</h3>
<p>You can use tools like Postman or Thunder Client to test the endpoints. Make sure to adjust the request method and URL accordingly, and provide the necessary request body or parameters.</p>
<ul>
<li><strong>Creating a Favorite:</strong> POST request to <code>/api/recipes/favorite</code> with <code>recipeId</code> in the body.</li>
<li><strong>Viewing Favorites:</strong> GET request to <code>/api/recipes/favorite</code>.</li>
<li><strong>Deleting a Favorite:</strong> DELETE request to <code>/api/recipes/favorite</code> with <code>recipeId</code> in the body.</li>
</ul>
<h3 id="heading-step-5-verify-the-database">Step 5: Verify the Database</h3>
<p>Check the <code>favoriteRecipes</code> table in your ElephantSQL database to verify the actions performed through the endpoints.</p>
<h2 id="heading-how-to-add-favorites-functionality-to-the-frontend">How to Add Favorites Functionality to the Frontend</h2>
<h3 id="heading-step-1-setup-the-tab-functionality">Step 1: Setup the Tab Functionality</h3>
<p>Next, we'll look at how to integrate these endpoints on the frontend. We'll start by setting up tabs for 'Search' and 'Favorites' in our app.</p>
<p>First, define a new state to keep track of the selected tab.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

type Tabs = <span class="hljs-string">"search"</span> | <span class="hljs-string">"favorites"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [selectedTab, setSelectedTab] = useState &lt; Tabs &gt; <span class="hljs-string">"search"</span>;

  <span class="hljs-comment">// Rest of your code...</span>
}
</code></pre>
<h3 id="heading-step-2-render-tabs">Step 2: Render Tabs</h3>
<p>Now you'll render tabs in your JSX, and handle tab switching with the <code>onClick</code> event. This makes each <code>&lt;h1&gt;</code> element is clickable, and saves the tab the user clicked on in state. This helps conditionally render different UI elements depending on their selection.</p>
<pre><code class="lang-jsx"><span class="hljs-comment">// Inside your JSX...</span>
&lt;div className=<span class="hljs-string">"tabs"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setSelectedTab("search")}&gt;Recipe Search<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setSelectedTab("favorites")}&gt;Favorites<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
&lt;/div&gt;
</code></pre>
<h3 id="heading-step-3-conditional-rendering">Step 3: Conditional Rendering</h3>
<p>Based on the selected tab, you want to conditionally render either the search component or the favorites component. This will show/hide either the "search section" or the "favourites" section depending on the <code>selectedTab</code> state variable.</p>
<pre><code class="lang-jsx">{selectedTab === <span class="hljs-string">'search'</span> &amp;&amp; (
  <span class="hljs-comment">// search component code...</span>
)}
{selectedTab === <span class="hljs-string">'favorites'</span> &amp;&amp; (
  <span class="hljs-comment">// favorites component code...</span>
)}
</code></pre>
<h3 id="heading-step-4-fetch-favorite-recipes">Step 4: Fetch Favorite Recipes</h3>
<p>Next we need to populate the Favorite recipes tab with our favourite recipes. We want to do this when the App loads, for a quick user experience.</p>
<p>To do this, fetch the favorite recipes from the backend when the app loads using the <code>useEffect</code> hook, and store the fetched favorite recipes in a new state.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// api.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getFavouriteRecipes = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">"http://localhost:5000/api/recipes/favourite"</span>);
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url);

  <span class="hljs-keyword">if</span> (!response.ok) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`HTTP error! Status: <span class="hljs-subst">${response.status}</span>`</span>);
  }
  <span class="hljs-keyword">return</span> response.json();
};
</code></pre>
<pre><code class="lang-javascript"><span class="hljs-comment">//App.tsx</span>
<span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-comment">// ... Rest of your imports and code</span>

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">App</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// ... Rest of your state declarations</span>

  <span class="hljs-keyword">const</span> [favoriteRecipes, setFavoriteRecipes] = useState([]);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchFavoriteRecipes = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> favouriteRecipes = <span class="hljs-keyword">await</span> api.getFavouriteRecipes();
        setFavouriteRecipes(favouriteRecipes.results);
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(error);
      }
    };

    fetchFavoriteRecipes();
  }, []);

  <span class="hljs-comment">// ... Rest of your code</span>
}
</code></pre>
<h3 id="heading-step-5-render-favorite-recipes">Step 5: Render Favorite Recipes</h3>
<p>Now you need to render the favorite recipes in the 'Favorites' tab.</p>
<pre><code class="lang-jsx">{selectedTab === <span class="hljs-string">'favorites'</span> &amp;&amp; (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    {favoriteRecipes.map(recipe =&gt; (
      // Render each favorite recipe card...
    ))}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
)}
</code></pre>
<h3 id="heading-step-6-add-a-heart-icon">Step 6: Add a Heart Icon</h3>
<p>Next we'll add a way for the user to add and remove favorites. We'll do this by adding a "heart" icon to each card. </p>
<p>Before diving into the code, ensure you are in the correct directory by navigating to your project's front-end directory in your terminal. Install the necessary package for icons by running:</p>
<pre><code class="lang-bash">npm install react-icons
</code></pre>
<h3 id="heading-step-7-import-the-icon">Step 7: Import the Icon</h3>
<p>Open the <code>RecipeCard</code> component, and import the heart icon at the top of your file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { AiOutlineHeart } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-icons/ai"</span>;
</code></pre>
<h3 id="heading-step-8-insert-the-icon">Step 8: Insert the icon</h3>
<p>In the <code>RecipeCard</code> component, add the heart icon within a <code>span</code> element just above the <code>h3</code> tag:</p>
<pre><code class="lang-javascript">&lt;span onClick={handleFavoriteClick}&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AiOutlineHeart</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{25}</span> /&gt;</span></span>
&lt;/span&gt;
</code></pre>
<h3 id="heading-step-9-add-css-styling">Step 9: Add CSS Styling</h3>
<p>In your <code>App.css</code> file, add the following CSS to style the icon and ensure it appears on the same line as the title. Using <code>flex</code> and <code>align-items</code> means the icon and title will be aligned nicely beneath the image:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.recipe-card-title</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">0.5rem</span>;
}
</code></pre>
<h3 id="heading-step-10-create-an-add-favourite-event-handler">Step 10: Create an Add Favourite Event Handler</h3>
<p>In <code>App.tsx</code>, create an event handler for favoriting a recipe. This is what will get called when the user clicks the heart icon on an recipe that hasn't yet been favorited:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> addfavoriteRecipe = <span class="hljs-keyword">async</span> (recipe) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> API.addFavoriteRecipe(recipe);
    setFavoriteRecipes([...favoriteRecipes, recipe]);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.log(error);
  }
};
</code></pre>
<h3 id="heading-step-11-api-logic">Step 11: API Logic</h3>
<p>In a new file called <code>API.ts</code>, create a function to handle the API call to save a favorite recipe. This will call our endpoint which we created earlier in the backend:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> addFavoriteRecipe = <span class="hljs-keyword">async</span> (recipe) =&gt; {
  <span class="hljs-keyword">const</span> body = {
    <span class="hljs-attr">recipeId</span>: recipe.id,
  };
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"http://localhost:5000/api/recipes/favourite"</span>, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
    },
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(body),
  });
  <span class="hljs-keyword">if</span> (!response.ok) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to save favorite"</span>);
  }
};
</code></pre>
<h3 id="heading-step-12-hook-up-the-event-handler">Step 12: Hook Up the Event Handler</h3>
<p>Pass the event handler to the <code>RecipeCard</code> component:</p>
<pre><code class="lang-javascript">&lt;RecipeCard
  <span class="hljs-comment">//.. other props</span>
  onFavoriteButtonClick={favoriteRecipe}
/&gt;
</code></pre>
<h3 id="heading-step-13-create-the-remove-favorite-event-handler">Step 13: Create the Remove Favorite Event Handler</h3>
<p>Similarly, create an event handler for un-favoriting a recipe in <code>App.tsx</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> removeFavoriteRecipe = <span class="hljs-keyword">async</span> (recipe) =&gt; {
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> API.removeFavoriteRecipe(recipe);
    <span class="hljs-keyword">const</span> updatedRecipes = favoriteRecipes.filter(
      <span class="hljs-function">(<span class="hljs-params">favRecipe</span>) =&gt;</span> favRecipe.id !== recipe.id
    );
    setFavoriteRecipes(updatedRecipes);
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-built_in">console</span>.log(error);
  }
};
</code></pre>
<h3 id="heading-step-14-api-logic">Step 14: API Logic</h3>
<p>In <code>API.ts</code>, create a function to handle the API call to remove a favorite recipe. Again, this will call the backend API to remove a recipe which we created earlier:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> removeFavoriteRecipe = <span class="hljs-keyword">async</span> (recipe) =&gt; {
  <span class="hljs-keyword">const</span> body = {
    <span class="hljs-attr">recipeID</span>: recipe.id,
  };
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"http://localhost:5000/api/recipes/favourite"</span>, {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"DELETE"</span>,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
    },
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify(body),
  });
  <span class="hljs-keyword">if</span> (!response.ok) {
    <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Failed to remove favorite"</span>);
  }
};
</code></pre>
<h3 id="heading-step-15-conditional-event-handler">Step 15: Conditional Event Handler</h3>
<p>Depending on if the user is "favoriting" or "unfavoriting" a recipe, we want to conditionally call either <code>addFavoriteRecipe</code> or <code>removeFavoriteRecipe</code> based on the favorited state:</p>
<pre><code class="lang-javascript">&lt;RecipeCard
  <span class="hljs-comment">//.. other props</span>
  onFavoriteButtonClick={isFavorite ? removeFavoriteRecipe : favoriteRecipe}
/&gt;
</code></pre>
<h3 id="heading-step-16-determine-the-favorited-state">Step 16: Determine the Favorited State</h3>
<p>Before we can display the heart icon a favorited/non-favorited state, we need to know if the recipe is already a or not.   </p>
<p>To do this, we determine whether a recipe is favorited by checking if it exists in the <code>favoriteRecipes</code> state array. Pass this information to <code>RecipeCard</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> isFavorite = favoriteRecipes.some(
  <span class="hljs-function">(<span class="hljs-params">favRecipe</span>) =&gt;</span> favRecipe.id === recipe.id
);
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">RecipeCard</span>
  // <span class="hljs-attr">...other</span> <span class="hljs-attr">props</span>
  <span class="hljs-attr">isFavorite</span>=<span class="hljs-string">{isFavorite}</span>
/&gt;</span></span>;
</code></pre>
<h3 id="heading-step-17-display-the-favorited-state">Step 17: Display the Favorited State</h3>
<p>In <code>RecipeCard</code>, conditionally render a filled or outlined heart icon based on the <code>isFavorite</code> prop:</p>
<pre><code class="lang-javascript">{
  isFavorite ? (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AiFillHeart</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{25}</span> <span class="hljs-attr">color</span>=<span class="hljs-string">"red"</span> /&gt;</span></span>
  ) : (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AiOutlineHeart</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{25}</span> /&gt;</span></span>
  );
}
</code></pre>
<h2 id="heading-how-to-add-cssstyling">How to Add CSS/Styling</h2>
<h3 id="heading-step-1-prepare-the-hero-image">Step 1: Prepare the Hero Image</h3>
<p>We've added some basic styling so far, so lets complete the CSS so that our app looks polished!</p>
<p>Firstly obtain an image from a source like <a target="_blank" href="https://www.pexels.com">Pexels</a> or any other image repository. This will be used in the Hero section of our app at the top, and will have our title overlaid on it. Ensure the image has a horizontal orientation for better handling of aspect ratios.</p>
<p>Place the image in the <code>public</code> folder of your project.</p>
<pre><code class="lang-plaintext">project-folder
│
└───public
    │   hero-image.jpeg
</code></pre>
<h3 id="heading-step-2-structure-the-header">Step 2: Structure the Header</h3>
<p>Open <code>app.tsx</code> and locate the JSX markup. Add a <code>className</code> of <code>app-container</code> to the top <code>div</code> element.</p>
<p>Inside the <code>app-container</code> div, add a new <code>div</code> with a <code>className</code> of <code>header</code>. Within the <code>header</code> div, add an <code>img</code> element with a <code>src</code> attribute pointing to your image, and a <code>div</code> element with a <code>className</code> of <code>title</code> containing the app's title.</p>
<pre><code class="lang-jsx">&lt;div className=<span class="hljs-string">"app-container"</span>&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"header"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/hero-image.jpeg"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Hero"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"title"</span>&gt;</span>My Recipe App<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  {<span class="hljs-comment">/* ...rest of your code */</span>}
&lt;/div&gt;
</code></pre>
<h3 id="heading-step-3-style-the-header">Step 3: Style the Header</h3>
<p>Open <code>app.css</code> and scroll to the top. Add the following CSS to style the <code>app-container</code>, <code>header</code>, <code>img</code>, and <code>title</code> elements. This makes the <code>title</code> appear on top of the image, with a translucent background:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.app-container</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">2em</span>;
}

<span class="hljs-selector-class">.header</span> {
  <span class="hljs-attribute">position</span>: relative;
}

<span class="hljs-selector-class">.header</span> <span class="hljs-selector-tag">img</span> {
  <span class="hljs-attribute">width</span>: <span class="hljs-number">100%</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">500px</span>;
  <span class="hljs-attribute">object-fit</span>: cover;
  <span class="hljs-attribute">object-position</span>: center;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.5</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">1em</span>;
}

<span class="hljs-selector-class">.title</span> {
  <span class="hljs-attribute">position</span>: absolute;
  <span class="hljs-attribute">top</span>: <span class="hljs-number">50%</span>;
  <span class="hljs-attribute">left</span>: <span class="hljs-number">50%</span>;
  <span class="hljs-attribute">transform</span>: <span class="hljs-built_in">translate</span>(-<span class="hljs-number">50%</span>, -<span class="hljs-number">50%</span>);
  <span class="hljs-attribute">color</span>: white;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2em</span>;
  <span class="hljs-attribute">text-align</span>: center;
  <span class="hljs-attribute">background-color</span>: black;
  <span class="hljs-attribute">opacity</span>: <span class="hljs-number">0.8</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5em</span> <span class="hljs-number">1.5em</span> <span class="hljs-number">0.5em</span> <span class="hljs-number">1.5em</span>;
}
</code></pre>
<h3 id="heading-step-4-adjust-the-layout">Step 4: Adjust the Layout</h3>
<p>Add padding to the <code>body</code> element and use a media query to add margins on larger screens. We do this so our app doesn't appear to narrow on mobile devices. When the screen size reaches <code>768px</code>, the media query will kick in and add margin to the left and right of our app, so that the app doesn't appear too wide.</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">5em</span> <span class="hljs-number">0</span>;
  <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f0f0f0</span>; <span class="hljs-comment">/* or any color you prefer */</span>
}

<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">min-width:</span> <span class="hljs-number">768px</span>) {
  <span class="hljs-selector-tag">body</span> {
    <span class="hljs-attribute">margin-left</span>: <span class="hljs-number">10em</span>;
    <span class="hljs-attribute">margin-right</span>: <span class="hljs-number">10em</span>;
  }
}
</code></pre>
<h3 id="heading-step-5-style-tabs-underline">Step 5: Style Tabs Underline</h3>
<p>Currently its not clear which tab the user has selected. What we want to do is add an orange underline to the selected tab. To do this, we can use a combination of CSS classes and conditional rendering.</p>
<p>Within <code>app.tsx</code>, locate your <code>h1</code> elements representing tabs, and dynamically apply a <code>className</code> of <code>tab-active</code> based on the selected tab.</p>
<pre><code class="lang-jsx">&lt;h1 className={selectedTab === <span class="hljs-string">'search'</span> ? <span class="hljs-string">'tab-active'</span> : <span class="hljs-string">''</span>}&gt;Search&lt;/h1&gt;
<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{selectedTab</span> === <span class="hljs-string">'favorites'</span> ? '<span class="hljs-attr">tab-active</span>' <span class="hljs-attr">:</span> ''}&gt;</span>Favorites<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span></span>
</code></pre>
<p>In <code>app.css</code>, define the <code>tab-active</code> class:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.tab-active</span> {
  <span class="hljs-attribute">border-bottom</span>: <span class="hljs-number">4px</span> solid orange; <span class="hljs-comment">/* or any color you prefer */</span>
  <span class="hljs-attribute">padding-bottom</span>: <span class="hljs-number">0.5em</span>;
}
</code></pre>
<h3 id="heading-step-6-style-the-search-bar">Step 6: Style the Search Bar</h3>
<p>We want our search bar to take up the width of the container, and we want to add an icon instead of the search button, which makes our UI more interesting. </p>
<p>In <code>app.tsx</code>, locate the <code>form</code> element within the <code>Search</code> tab. Replace the text "Submit" in the <code>button</code> element with an icon from a library like React Icons.</p>
<pre><code class="lang-jsx">&lt;button&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AiOutlineSearch</span> <span class="hljs-attr">size</span>=<span class="hljs-string">{40}</span> /&gt;</span></span>
&lt;/button&gt;
</code></pre>
<p>In <code>app.css</code>, style the <code>form</code>, <code>input</code>, and <code>button</code> elements:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">form</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">background-color</span>: white;
  <span class="hljs-attribute">align-items</span>: center;
}

<span class="hljs-selector-tag">input</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0.5em</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">2em</span>;
  <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
  <span class="hljs-attribute">border</span>: none;
}

<span class="hljs-selector-tag">input</span><span class="hljs-selector-pseudo">:focus</span> {
  <span class="hljs-attribute">outline</span>: none;
}

<span class="hljs-selector-tag">button</span> {
  <span class="hljs-attribute">background-color</span>: white;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">cursor</span>: pointer;
}
</code></pre>
<h3 id="heading-step-7-implement-a-responsive-recipe-card-grid">Step 7: Implement a Responsive Recipe Card Grid</h3>
<p>Currently our Recipe Cards are stacked horizontally. We'll use CSS grid to make the recipe cards appear in a grid layout, which will also make things more responsive. </p>
<p>Within <code>app.tsx</code>, create a new <code>div</code> with a <code>className</code> of <code>recipe-grid</code> just above where you map over your recipes, and place the logic to rendering the recipes inside this <code>div</code>.</p>
<pre><code class="lang-jsx">&lt;div className=<span class="hljs-string">"recipe-grid"</span>&gt;
  {recipes.map(<span class="hljs-function">(<span class="hljs-params">recipe</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> isFavourite = favouriteRecipes.some(<span class="hljs-function">(<span class="hljs-params">favRecipe</span>) =&gt;</span> favRecipe.id === recipe.id);

    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">RecipeCard</span>
        <span class="hljs-attr">key</span>=<span class="hljs-string">{recipe.id}</span>
        <span class="hljs-attr">recipe</span>=<span class="hljs-string">{recipe}</span>
        <span class="hljs-attr">onFavouriteButtonClick</span>=<span class="hljs-string">{isFavourite</span> ? <span class="hljs-attr">removeFavouriteRecipe</span> <span class="hljs-attr">:</span> <span class="hljs-attr">addFavouriteRecipe</span>}
        <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> setSelectedRecipe(recipe)}
        isFavourite={isFavourite}
      /&gt;</span>
    );
  })}
&lt;/div&gt;
</code></pre>
<p>In <code>app.css</code>, style the <code>recipe-grid</code> and <code>recipe-card</code> elements:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.recipe-grid</span> {
  <span class="hljs-attribute">display</span>: grid;
  <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-built_in">repeat</span>(auto-fill, minmax(<span class="hljs-number">400px</span>, <span class="hljs-number">1</span>fr));
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">2em</span>;
}

<span class="hljs-selector-class">.recipe-card</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">justify-content</span>: space-evenly;
  <span class="hljs-attribute">background-color</span>: white;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1em</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">4px</span> <span class="hljs-number">12px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
  <span class="hljs-attribute">position</span>: relative;
  <span class="hljs-attribute">cursor</span>: pointer;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">1.5em</span>;
}

<span class="hljs-selector-class">.recipe-card</span> <span class="hljs-selector-tag">h3</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5em</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">white-space</span>: nowrap;
  <span class="hljs-attribute">overflow</span>: hidden;
  <span class="hljs-attribute">text-overflow</span>: ellipsis;
}
</code></pre>
<h3 id="heading-step-8-final-touches">Step 8: Final Touches</h3>
<p>Style the "View More" button to make sure it matches the style of our app, and is centered beneath our recipe grid:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.view-more-button</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">1.5em</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">1em</span>;
  <span class="hljs-attribute">font-weight</span>: bold;
  <span class="hljs-attribute">margin</span>: auto;
}
</code></pre>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Congrats on making it to the end! Hopefully you've learned a few things about full stack development using React and Node. </p>
<p>If you enjoyed this project, you can find more at <a target="_blank" href="https://www.codecoyotes.com/">CodeCoyotes.com</a>, where you can also send me a message if you need to get in contact.   </p>
<p>Thanks for reading, see you in the next one!  </p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ PostgreSQL Course for Beginners ]]>
                </title>
                <description>
                    <![CDATA[ In the realm of databases, making the right decision about which system to adopt can make or break the fluidity and efficiency of your applications. PostgreSQL, an open-source relational database, has increasingly become the go-to choice for develope... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/posgresql-course-for-beginners/</link>
                <guid isPermaLink="false">66b2060bf31aa965000e5876</guid>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Fri, 13 Oct 2023 14:02:48 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/10/postgresql.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In the realm of databases, making the right decision about which system to adopt can make or break the fluidity and efficiency of your applications. PostgreSQL, an open-source relational database, has increasingly become the go-to choice for developers worldwide. </p>
<p>We just posted a full PostgreSQL course on the freeCodeCamp.org YouTube channel. It covers PostgreSQL from the ground up. Alexandru Cristian created this course. He has created courses on Udemy and the freeCodeCamp channel.</p>
<p>Here is what is covered in this course:</p>
<p><strong>PostgreSQL Introduction</strong></p>
<ul>
<li>A deep dive into what PostgreSQL is and its distinct advantages.</li>
</ul>
<p><strong>Windows Installation - PostgreSQL and PgAdmin with Database Setup</strong></p>
<ul>
<li>Step-by-step installation guide.</li>
<li>Setting up your first database with PgAdmin.</li>
</ul>
<p><strong>Diving into the SELECT statement</strong></p>
<ul>
<li>Fundamentals and use-cases of the SELECT statement.</li>
</ul>
<p><strong>SELECT Challenge</strong></p>
<ul>
<li>Put your knowledge to the test with hands-on challenges.</li>
</ul>
<p><strong>Exploring SELECT DISTINCT and Challenges</strong></p>
<ul>
<li>Understanding the role of DISTINCT in queries.</li>
<li>Engaging challenges to solidify your knowledge.</li>
</ul>
<p><strong>Delving into WHERE Clauses</strong></p>
<ul>
<li>SELECT WHERE fundamentals and real-world examples.</li>
<li>Engaging challenges to ensure you've grasped the concept.</li>
</ul>
<p><strong>Diving Deeper: Advanced Query Structures</strong></p>
<ul>
<li>Introduction to COUNT, ORDER BY, LIMIT, and the BETWEEN Statement.</li>
<li>Exploring the IN Statement and understanding the LIKE and ILIKE clauses.</li>
</ul>
<p><strong>General Challenge</strong></p>
<ul>
<li>A comprehensive challenge covering all the topics so far.</li>
</ul>
<p><strong>Aggregate Functions and Beyond</strong></p>
<ul>
<li>Introduction to functions that allow data analysis.</li>
<li>Deep dive into GROUP BY - from basic introduction to practical examples and challenges.</li>
<li>Unveiling the HAVING command and the AS Statement.</li>
</ul>
<p>This course is ideally suited for a diverse range of people. Developers interested in enhancing their database skills will find it particularly beneficial, as will aspiring Database Administrators looking to specialize in PostgreSQL. Also, computer science students eager for hands-on experience and tech enthusiasts curious about the intricacies of relational databases will also greatly benefit from the content provided.</p>
<p>Watch the full course on <a target="_blank" href="https://youtu.be/SpfIwlAYaKk">the freeCodeCamp.org YouTube channel</a> (3-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/SpfIwlAYaKk" 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>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Full Stack Project Tutorial – Create A Notes App Using React and Node.js ]]>
                </title>
                <description>
                    <![CDATA[ Hey there! Ready to build something cool? In this tutorial, we're going to create a full stack notes app using React, Node.js, and PostgreSQL.  We'll start from scratch and end up with a fully functioning app where you can create, edit, and delete no... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/full-stack-project-tutorial-create-a-notes-app-using-react-and-node-js/</link>
                <guid isPermaLink="false">66c8c8d7fe21816c4cb75d17</guid>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chris Blakely ]]>
                </dc:creator>
                <pubDate>Thu, 28 Sep 2023 14:10:44 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/09/react-node-notes-app-screenshot-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Hey there! Ready to build something cool? In this tutorial, we're going to create a full stack notes app using React, Node.js, and PostgreSQL. </p>
<p>We'll start from scratch and end up with a fully functioning app where you can create, edit, and delete notes. Plus, we're adding validation on both the UI and the backend to keep things in check!</p>
<p>This guide is all about giving you the real-deal experience of building a web app. You'll get to learn how each piece of the puzzle fits together – from the front-end magic with React, to the server-side wonders with Node.js, and storing all the good stuff in a PostgreSQL database. And hey, we're making sure it looks good and works well on mobile screens too!</p>
<p>By the end of this, you’ll have a good grip on full stack development with React and Node, which you can carry with you into future projects. It’s all about learning by doing, and getting the skills to make your ideas come to life. So, grab a cup of coffee, roll up your sleeves, and let’s get coding!</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Since we will be focusing on how to build a project, there are a few prerequisites that will be needed to get the most out of this tutorial:</p>
<ul>
<li>Some knowledge about web development concepts (frontend, backend, databases, API's, REST).</li>
<li>Some knowledge of JavaScript (variables, functions, objects, arrays, and so on).</li>
<li>Basic understanding on React (how to create components, add styles, work with state).</li>
<li>Basic understanding on Node.js/Express (working with APIs).</li>
</ul>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-well-build">What We'll Build</a></li>
<li><a class="post-section-overview" href="#heading-challenge-try-it-yourself-first">Challenge: Try it Yourself First!</a></li>
<li><a class="post-section-overview" href="#heading-video-tutorial">Video Tutorial</a></li>
<li><a class="post-section-overview" href="#heading-part-1-create-the-ui">PART 1 – Create the UI</a></li>
<li><a class="post-section-overview" href="#heading-part-2-create-the-backend">PART 2 - Create the Backend</a></li>
<li><a class="post-section-overview" href="#heading-part-3-connect-ui-to-backend">PART 3 - Connect UI to Backend</a></li>
<li><a class="post-section-overview" href="#heading-the-end-why-not-try-the-bonus-challenges">The End - Why not try the bonus challenges?</a></li>
</ul>
<h2 id="heading-what-well-build">What We'll Build</h2>
<p>In this tutorial, we'll build a full stack notes app from scratch, using React, Node.js and PostgreSQL, with the following features:</p>
<ul>
<li>Create/Edit/Delete Notes</li>
<li>Validation on the UI and Backend</li>
<li>Responsive on mobile screens</li>
</ul>
<h2 id="heading-challenge-try-it-yourself-first">Challenge: Try it Yourself First!</h2>
<p>If you would like to attempt this project yourself first without looking at the tutorial, here's a few hints:</p>
<ul>
<li>Tackle one little piece at a time. For example, you would focus on getting the UI working on the UI first, and tackle the APIs later.</li>
<li>Think about your data – What do you need to store? What data structure (for example, arrays) will you use to return the data via the API?  How will you render this data on the UI?</li>
<li>Don't forget about validation and error handling. What will happen if the user tries to save a note without a title field? How will you prevent this? (Hint: Forms and the <code>required</code> field will be your friend here)</li>
<li>Remember there is no perfect way to complete a project. The tutorial below is one way to tackle the problem. You can choose to go a different way, putting your own unique style on things. The main thing is that you start!</li>
</ul>
<p>If you need more help on getting started yourself, you can find more hints and tips, starter code, and completed code you can reference over at <a target="_blank" href="https://www.codecoyotes.com/projects/react-node-notes-app">codecoyotes.com</a>.</p>
<h2 id="heading-video-tutorial">Video Tutorial</h2>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/2MoSzSlAuNk" 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-part-1-create-the-ui">PART 1 – Create the UI</h2>
<p>We'll start this tutorial by creating the UI using some mock data. This lets us focus on our styling, and how things to look, without having to worry about creating a backend right away.</p>
<h3 id="heading-create-a-new-react-app">Create a New React App</h3>
<p>Okay, first things first: let's set up our project structure. Open your terminal and navigate to your desktop. Our next step is to create a new folder that will hold both our UI and backend code. Let's name it <code>notes-app</code>:</p>
<pre><code class="lang-bash">mkdir notes-app
</code></pre>
<p>Once that's done, navigate into the newly created <code>notes-app</code> directory:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> notes-app
</code></pre>
<p>Next, we'll create a new React app using TypeScript as our template. We'll use the <code>npx create-react-app</code> command for this, specifying TypeScript as the template:</p>
<pre><code class="lang-bash">npx create-react-app notes-app --template typescript
</code></pre>
<p>After you hit Enter, the process may take a few minutes to install all necessary packages. Once it's completed, open the <code>notes-app</code> folder in Visual Studio Code or your preferred IDE.</p>
<p>In Visual Studio Code, you should see that the <code>notes-app</code> is at the top level of your directory. As the course progresses, we will add a <code>notes-app-server</code> directory as well to keep all the code together in one place.</p>
<p>Now open a new terminal within your IDE and navigate to your React app's directory (let's assume you named it <code>notes-app</code>):</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> notes-app
</code></pre>
<p>Then, run the following command to start the front-end development server:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>If all goes well, your browser will automatically open and display your new React app. You should see a spinning React logo, among other default assets.</p>
<p>Finally, let's clear out the boilerplate code to have a clean starting point for our app. Open <code>src/App.tsx</code> in your IDE and delete its content. This will be our new starting point for building the app.</p>
<h3 id="heading-add-ui-elements">Add UI Elements</h3>
<p>Okay, the first thing we'll do is put some of our UI components in place. This will consist of the general markup and CSS, without involving any JavaScript. This gives us a glimpse of how we envision the layout, without having to concern ourselves with API calls or database interactions at this stage.</p>
<p>We'll navigate to <code>.App.tsx</code> and create a new component. Make sure to import our stylesheet from <code>App.css</code>. The first thing to add is a <code>div</code> with a class name of <code>AppContainer</code>. This will help position our form and the CSS grid for our notes.</p>
<p>Within this <code>div</code>, we'll include our form tags. Here, we'll add an input field for the title — this is where the user can enter the note title. We'll also include a textarea for the note content. Both of these fields will be set to <code>required</code>, enabling browser-native validation messages if the user tries to submit an incomplete form. </p>
<p>At the bottom of the form, we'll include a button of type <code>submit</code>, which will handle form submissions when clicked.</p>
<p>Off to the right, we'll add a <code>div</code> to contain our notes. This will be structured as a CSS grid. Initially, we'll populate this grid with a single note to see how it looks.</p>
<p>For our note, we'll have a header containing a delete button situated on the right-hand side. We'll display the user-entered title – for demonstration purposes, we're using a dummy title. We'll also include the content that the user entered.</p>
<p>Finally, we'll export our component at the very bottom.</p>
<h3 id="heading-completed-code-for-this-section">Completed code for this section</h3>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> <span class="hljs-string">"./App.css"</span>;

<span class="hljs-keyword">const</span> App = <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"app-container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"note-form"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Title"</span> <span class="hljs-attr">required</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Content"</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">{10}</span> <span class="hljs-attr">required</span> /&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Add Note<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"notes-grid"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"note-item"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"notes-header"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>x<span class="hljs-tag">&lt;/<span class="hljs-name">button</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">h2</span>&gt;</span>Note Title<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Note content<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">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h3 id="heading-add-css">Add CSS</h3>
<h4 id="heading-start-the-app">Start the App</h4>
<p>First, let's open a terminal and type <code>npm start</code>. This will launch the app in the browser. As you can see on the right-hand side, the appearance isn't great yet. This is because we haven't applied any styles. To fix this, we'll navigate to <code>App.css</code> and style the classes we added earlier.</p>
<p>You can download the styles from the link in the description if you prefer to copy and paste. Alternatively, feel free to follow along with the video and pause as needed. Remember, these styles are just examples for learning – they don't have to be perfect.</p>
<h4 id="heading-style-the-body-and-app-container">Style the Body and App Container</h4>
<p>In <code>App.css</code>, the first thing we'll do is add some styles to the <code>body</code>. We'll give it a gray background and a margin to prevent the app from touching the browser window edges. Next, we'll style our <code>App Container</code>.</p>
<p>We're designing this mobile-first, meaning the default styles will target mobile screens. We'll use media queries for larger displays. This approach is optional, but often it's easier to start with mobile designs.</p>
<p>For mobile screens, we want our <code>App Container</code> to default to a single column layout, stacking our form and notes grid on top of each other.</p>
<h4 id="heading-use-media-queries">Use Media Queries</h4>
<p>We'll add a media query specifying that for screens larger than 600 pixels, we'll use a two-column layout. We'll define this using <code>grid-template-columns</code>. </p>
<p>The first column will be 200 pixels wide, accommodating the form. The second column will use <code>1fr</code>, filling the remaining space. A 20-pixel gap will separate the two columns.</p>
<h4 id="heading-style-the-notes-grid">Style the Notes Grid</h4>
<p>Next, let's style our notes grid. We'll use CSS grid and define <code>grid-template-columns</code>. </p>
<p>Each grid item will have a minimum width of 250 pixels and can expand to fill available space. Don't worry if this sounds confusing – it will become clear soon.</p>
<p>We'll also set <code>grid-auto-rows</code> to ensure each row is a minimum of 250 pixels tall, accommodating notes of different sizes while maintaining a consistent row height.</p>
<h4 id="heading-style-individual-notes">Style Individual Notes</h4>
<p>For each note, we'll use Flexbox and set <code>flex-direction</code> to column, stacking the header, title, and content vertically. We'll also add some basic styles like border, padding, and background color. A box shadow will provide a finishing touch.</p>
<h4 id="heading-style-the-header-and-delete-button">Style the Header and Delete Button</h4>
<p>The header will also use Flexbox, and we'll set <code>justify-content</code> to <code>flex-end</code> to align the delete button to the right. The button will receive custom styles for a polished look.</p>
<h4 id="heading-style-the-form">Style the Form</h4>
<p>Lastly, we'll style the form on the left column. Again, we'll use Flexbox with a column layout and a 20-pixel gap between elements. The text area and input fields will get borders, padding, and resized fonts. We'll also style the submit button and add hover effects.</p>
<h4 id="heading-completed-code-for-this-section-1">Completed code for this section</h4>
<pre><code class="lang-css"><span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">20px</span>;
  <span class="hljs-attribute">background-color</span>: lightgrey;
}

<span class="hljs-selector-class">.app-container</span> {
  <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-number">1</span>fr;
}

<span class="hljs-keyword">@media</span> (<span class="hljs-attribute">min-width:</span> <span class="hljs-number">600px</span>) {
  <span class="hljs-selector-class">.app-container</span> {
    <span class="hljs-attribute">display</span>: grid;
    <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-number">200px</span> <span class="hljs-number">1</span>fr;
    <span class="hljs-attribute">gap</span>: <span class="hljs-number">20px</span>;
  }
}

<span class="hljs-selector-class">.notes-grid</span> {
  <span class="hljs-attribute">display</span>: grid;
  <span class="hljs-attribute">grid-template-columns</span>: <span class="hljs-built_in">repeat</span>(auto-fill, minmax(<span class="hljs-number">250px</span>, <span class="hljs-number">1</span>fr));
  <span class="hljs-attribute">grid-auto-rows</span>: <span class="hljs-built_in">minmax</span>(<span class="hljs-number">250px</span>, auto);
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">20px</span>;
}

<span class="hljs-selector-class">.note-item</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid <span class="hljs-number">#ccc</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>;
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-number">#f9f9f9</span>;
  <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0px</span> <span class="hljs-number">0px</span> <span class="hljs-number">10px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.1</span>);
  <span class="hljs-attribute">cursor</span>: pointer;
}

<span class="hljs-selector-class">.notes-header</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: flex-end;
}

<span class="hljs-selector-class">.notes-header</span> <span class="hljs-selector-tag">button</span> {
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">16px</span>;
  <span class="hljs-attribute">background</span>: transparent;
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">cursor</span>: pointer;
  <span class="hljs-attribute">max-width</span>: fit-content;
}

<span class="hljs-selector-tag">h2</span> {
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
}

<span class="hljs-selector-class">.note-form</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">20px</span>;
}

<span class="hljs-selector-tag">textarea</span>,
<span class="hljs-selector-tag">input</span> {
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>;
  <span class="hljs-attribute">border</span>: <span class="hljs-number">1px</span> solid black;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">16px</span>;
}

<span class="hljs-selector-class">.note-form</span> <span class="hljs-selector-tag">button</span> {
  <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">5px</span>;
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">64</span>, <span class="hljs-number">154</span>, <span class="hljs-number">184</span>);
  <span class="hljs-attribute">border</span>: none;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-number">16px</span>;
  <span class="hljs-attribute">color</span>: white;
}

<span class="hljs-selector-class">.note-form</span> <span class="hljs-selector-tag">button</span><span class="hljs-selector-pseudo">:hover</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">106</span>, <span class="hljs-number">175</span>, <span class="hljs-number">198</span>);
  <span class="hljs-attribute">cursor</span>: pointer;
}

<span class="hljs-selector-class">.edit-buttons</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">justify-content</span>: space-evenly;
  <span class="hljs-attribute">gap</span>: <span class="hljs-number">5px</span>;
}

<span class="hljs-selector-class">.edit-buttons</span> <span class="hljs-selector-tag">button</span> {
  <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
}

<span class="hljs-selector-class">.edit-buttons</span> <span class="hljs-selector-tag">button</span><span class="hljs-selector-pseudo">:last-of-type</span> {
  <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgb</span>(<span class="hljs-number">220</span>, <span class="hljs-number">89</span>, <span class="hljs-number">89</span>);
  <span class="hljs-attribute">color</span>: white;
}
</code></pre>
<h3 id="heading-add-dummy-notes">Add Dummy Notes</h3>
<h4 id="heading-add-dummy-notes-for-css-grid-testing">Add Dummy Notes for CSS Grid Testing</h4>
<p>Now that we have our CSS in place, the next step is to add dummy notes to our <code>App</code> component to test the responsiveness of our CSS grid. To achieve this, we'll navigate to <code>App.tsx</code> and import the <code>useState</code> hook from React.</p>
<p>Inside our <code>App</code> component, we'll store the notes within the <code>useState</code> hook. Whenever you have UI elements that can change, it's a good idea to manage them in the state. We'll initialize this with a dummy array of notes, where each note has an <code>id</code>, a <code>title</code>, and <code>content</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [notes, setNotes] = useState&lt;Note[]&gt;([
{
  <span class="hljs-attr">id</span>: <span class="hljs-number">1</span>,
  <span class="hljs-attr">title</span>: <span class="hljs-string">"test note 1"</span>,
  <span class="hljs-attr">content</span>: <span class="hljs-string">"bla bla note1"</span>,
},
{
  <span class="hljs-attr">id</span>: <span class="hljs-number">2</span>,
  <span class="hljs-attr">title</span>: <span class="hljs-string">"test note 2 "</span>,
  <span class="hljs-attr">content</span>: <span class="hljs-string">"bla bla note2"</span>,
},
{
  <span class="hljs-attr">id</span>: <span class="hljs-number">3</span>,
  <span class="hljs-attr">title</span>: <span class="hljs-string">"test note 3"</span>,
  <span class="hljs-attr">content</span>: <span class="hljs-string">"bla bla note3"</span>,
},
{
  <span class="hljs-attr">id</span>: <span class="hljs-number">4</span>,
  <span class="hljs-attr">title</span>: <span class="hljs-string">"test note 4 "</span>,
  <span class="hljs-attr">content</span>: <span class="hljs-string">"bla bla note4"</span>,
},
{
  <span class="hljs-attr">id</span>: <span class="hljs-number">5</span>,
  <span class="hljs-attr">title</span>: <span class="hljs-string">"test note 5"</span>,
  <span class="hljs-attr">content</span>: <span class="hljs-string">"bla bla note5"</span>,
},
{
  <span class="hljs-attr">id</span>: <span class="hljs-number">6</span>,
  <span class="hljs-attr">title</span>: <span class="hljs-string">"test note 6"</span>,
  <span class="hljs-attr">content</span>: <span class="hljs-string">"bla bla note6"</span>,
},
]);
</code></pre>
<p>Think of this as simulating an API call and storing the returned data in the state. The structure of this data will be similar to what we'll receive from our API requests when we eventually build out our backend.</p>
<h4 id="heading-map-notes-to-components">Map Notes to Components</h4>
<p>With our array of notes in state, we can now use the <code>map</code> function within our notes grid to display the markup for each note. The <code>map</code> function will run as many times as there are notes in the array. Instead of hardcoding the <code>title</code> and <code>content</code>, we'll pull these values from each <code>note</code> object:</p>
<pre><code class="lang-jsx">&lt;div className=<span class="hljs-string">"notes-grid"</span>&gt;
  {notes.map(<span class="hljs-function">(<span class="hljs-params">note</span>) =&gt;</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"note-item"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"notes-header"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>x<span class="hljs-tag">&lt;/<span class="hljs-name">button</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">h2</span>&gt;</span>{note.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{note.content}<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>
  ))}
&lt;/div&gt;
</code></pre>
<h4 id="heading-verify-responsiveness">Verify Responsiveness</h4>
<p>After these steps, you should see four notes displayed in the browser, populated with the values from the objects in the array.</p>
<p>To verify that our layout is responsive, you can change the window size. You'll see that the notes adjust based on the window size. When the window is at its smallest—simulating a mobile screen—the form will stack vertically above the notes grid.</p>
<h3 id="heading-save-note-form">Save Note Form</h3>
<p>Now that we have our UI set up, let's focus on adding functionality to the form that allows us to create a new note. Initially, we'll implement this for the UI. Later, we'll make the data persistent by linking it to the backend, which we'll build separately.</p>
<h4 id="heading-use-state-for-form-inputs-in-react">Use State for Form Inputs in React</h4>
<p>In React, when working with forms, it's a best practice to maintain a state variable for each form input. This enables React to control those inputs, making it easier to capture their values and use them programmatically.</p>
<p>In our code, we have two form inputs: one for the title and another for the content. For these, we'll set up two state variables called <code>title</code> and <code>content</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [title, setTitle] = useState(<span class="hljs-string">""</span>);
<span class="hljs-keyword">const</span> [content, setContent] = useState(<span class="hljs-string">""</span>);
</code></pre>
<p>For the title input, we bind its value to the <code>title</code> state variable and update this state whenever the user types into the field:</p>
<pre><code class="lang-javascript">&lt;input
  value={title}
  onChange={<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> setTitle(event.target.value)}
  placeholder=<span class="hljs-string">"Title"</span>
  required
&gt;&lt;/input&gt;
</code></pre>
<p>Similarly, we'll handle the <code>textarea</code> for content:</p>
<pre><code class="lang-javascript">&lt;textarea
  value={content}
  onChange={<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> setContent(event.target.value)}
  placeholder=<span class="hljs-string">"Content"</span>
  rows={<span class="hljs-number">10</span>}
  required
&gt;&lt;/textarea&gt;
</code></pre>
<h4 id="heading-handle-form-submission">Handle Form Submission</h4>
<p>After binding our form inputs to state variables, the next step is to add a function that handles the form submission. We'll name this function <code>handleAddNote</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> handleAddNote = <span class="hljs-function">(<span class="hljs-params">event: React.FormEvent</span>) =&gt;</span> {
  event.preventDefault();
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"title: "</span>, title);
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"content: "</span>, content);
};
</code></pre>
<p>In this function, we specify the parameter type as <code>React.FormEvent</code> to satisfy TypeScript's typing requirement. We also call <code>event.preventDefault()</code> to prevent the form from submitting and refreshing the page, which is its default behavior. Following that, we log the <code>title</code> and <code>content</code> state variables to the console.</p>
<p>Finally, we'll connect this function to the <code>onSubmit</code> event in our form:</p>
<pre><code class="lang-jsx">&lt;form onSubmit={handleAddNote}&gt;{<span class="hljs-comment">/* ...form inputs here... */</span>}&lt;/form&gt;
</code></pre>
<h4 id="heading-test-the-form">Test the Form</h4>
<p>To test this setup, open the browser console, input a title and some content, and click the "Add Note" button. You should see the title and content values logged in the console, confirming that our form is capturing input as expected.</p>
<h3 id="heading-handle-the-add-note-functionality">Handle the "Add Note" Functionality</h3>
<p>Now that we've set up our state variables for the title and content, we can proceed to implement the function that handles adding a new note. This function will create a new note object and add it to our <code>notes</code> array, thereby updating the UI.</p>
<h4 id="heading-create-a-new-note-object">Create a New Note Object</h4>
<p>First, let's create a new note object and specify its type as <code>Note</code>, taking advantage of TypeScript's type system:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> newNote: Note = {
  <span class="hljs-attr">id</span>: notes.length + <span class="hljs-number">1</span>,
  <span class="hljs-attr">title</span>: title,
  <span class="hljs-attr">content</span>: content,
};
</code></pre>
<p>Because we've explicitly typed our object, TypeScript's IntelliSense will assist us in populating the object, ensuring that we don't miss any required properties. For now, we'll set the <code>id</code> to the length of the current <code>notes</code> array plus one, although this <code>id</code> will eventually be generated by our backend database.</p>
<h4 id="heading-update-state-with-new-note">Update State with New Note</h4>
<p>Once we have our new note object, we need to update our <code>notes</code> state array. We'll use the <code>setNotes</code> function for this purpose:</p>
<pre><code class="lang-javascript">setNotes([newNote, ...notes]);
</code></pre>
<p>The new note object will be the first item in the new <code>notes</code> array, followed by the existing notes, which we'll spread into the new array using the spread operator. This effectively makes a copy of the old <code>notes</code> array and inserts it into the new one.</p>
<h4 id="heading-clear-the-form-inputs">Clear the Form Inputs</h4>
<p>Lastly, let's reset the <code>title</code> and <code>content</code> state variables to empty strings, improving the user experience by clearing the form once a note is added:</p>
<pre><code class="lang-javascript">setTitle(<span class="hljs-string">""</span>);
setContent(<span class="hljs-string">""</span>);
</code></pre>
<h4 id="heading-test-the-functionality">Test the Functionality</h4>
<p>And that's it! If you now go to the browser, input a title and some content, and then click "Add Note," you'll see your new note appear at the top of the list, and the form fields will be cleared, ready for a new entry.</p>
<h3 id="heading-handle-the-update-note-functionality">Handle the "Update Note" Functionality</h3>
<p>In this section, we'll focus on implementing the feature that allows users to update an existing note. When a user clicks on a note, we want to populate the <code>title</code> and <code>content</code> fields in our form with the note's existing values. We'll also add a "Save" and "Cancel" button.</p>
<h4 id="heading-cleanup-and-initial-setup">Cleanup and Initial Setup</h4>
<p>First, let's clean up our code by removing any <code>console.log</code> statements – they are no longer needed:</p>
<pre><code class="lang-tsx">const [selectedNote, setSelectedNote] = useState&lt;Note | null&gt;(null);
</code></pre>
<h4 id="heading-track-the-selected-note">Track the Selected Note</h4>
<p>To track which note the user has clicked on, we'll create a new state variable called <code>selectedNote</code>. This state variable will have a type of <code>Note</code> or <code>null</code> to account for the possibility that no note is selected. We'll initialize this state to <code>null</code>.</p>
<h4 id="heading-create-the-click-handler">Create the Click Handler</h4>
<p>Next, let's create a function named <code>handleNoteClick</code> to handle the user's click event on a note. This function will take a <code>note</code> object as its argument:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> handleNoteClick = <span class="hljs-function">(<span class="hljs-params">note: Note</span>) =&gt;</span> {
  setSelectedNote(note);
  setTitle(note.title);
  setContent(note.content);
};
</code></pre>
<p>Within this function, we'll use <code>setSelectedNote</code> to save the clicked note to our <code>selectedNote</code> state. Additionally, we'll populate the <code>title</code> and <code>content</code> state variables with the values from the clicked note.</p>
<h4 id="heading-update-the-ui">Update the UI</h4>
<p>In the JSX for rendering each note, add an <code>onClick</code> event to the top-level <code>div</code> element for each note. Call the <code>handleNoteClick</code> function and pass the <code>note</code> object to it:</p>
<pre><code class="lang-jsx">&lt;div key={note.id} className=<span class="hljs-string">"note-item"</span> onClick={<span class="hljs-function">() =&gt;</span> handleNoteClick(note)}&gt;
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"notes-header"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>x<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{note.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{note.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
&lt;/div&gt;
</code></pre>
<p>Since we are iterating over notes using the <code>map</code> function, this <code>onClick</code> handler will be added to each note automatically.</p>
<h4 id="heading-save-user-changes">Save user changes</h4>
<p>Now that we have the capability for the user to edit a note, we'll implement the functionality to save the changes they make to both the <code>title</code> and <code>content</code> of a note into our state.</p>
<h4 id="heading-the-handleupdatenote-function">The <code>handleUpdateNote</code> Function</h4>
<p>Let's create a new function called <code>handleUpdateNote</code>:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> handleUpdateNote = <span class="hljs-function">(<span class="hljs-params">event: React.FormEvent</span>) =&gt;</span> {
  event.preventDefault();

  <span class="hljs-keyword">if</span> (!selectedNote) {
    <span class="hljs-keyword">return</span>;
  }

  <span class="hljs-keyword">const</span> updatedNote: Note = {
    <span class="hljs-attr">id</span>: selectedNote.id,
    <span class="hljs-attr">title</span>: title,
    <span class="hljs-attr">content</span>: content,
  };

  <span class="hljs-keyword">const</span> updatedNotesList = notes.map(<span class="hljs-function">(<span class="hljs-params">note</span>) =&gt;</span> (note.id === selectedNote.id ? updatedNote : note));

  setNotes(updatedNotesList);
  setTitle(<span class="hljs-string">""</span>);
  setContent(<span class="hljs-string">""</span>);
  setSelectedNote(<span class="hljs-literal">null</span>);
};
</code></pre>
<p>Within this function, we use <code>event.preventDefault()</code> to prevent the form from automatically submitting when the "Save" button is clicked. We also validate if a note is selected. If not, we exit the function early to prevent potential errors.</p>
<p>Next, we form an updated note object based on the selected note's <code>id</code> and the updated <code>title</code> and <code>content</code>. After that, we utilize the <code>map</code> function to generate a new array of notes, replacing the selected note with our updated note where the <code>id</code> matches. The updated array is then set to our state using the <code>setNotes</code> function. Finally, we reset our <code>title</code>, <code>content</code>, and <code>selectedNote</code> state values to their initial states.</p>
<h4 id="heading-the-handlecancel-function">The <code>handleCancel</code> Function</h4>
<p>We'll also implement a simple <code>handleCancel</code> function to reset our form and selected note when the user decides not to proceed with an update:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> handleCancel = <span class="hljs-function">() =&gt;</span> {
  setTitle(<span class="hljs-string">""</span>);
  setContent(<span class="hljs-string">""</span>);
  setSelectedNote(<span class="hljs-literal">null</span>);
};
</code></pre>
<h4 id="heading-update-the-jsx">Update the JSX</h4>
<p>Let's introduce conditional rendering in our JSX to display the appropriate buttons based on whether a note is selected for editing or not:</p>
<pre><code class="lang-jsx">&lt;form
  className=<span class="hljs-string">"note-form"</span>
  onSubmit={<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> (selectedNote ? handleUpdateNote(event) : handleAddNote(event))}
&gt;
  {<span class="hljs-comment">/* ... other form elements ... */</span>}
  {selectedNote ? (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"edit-buttons"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Save<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleCancel}</span>&gt;</span>Cancel<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  ) : (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span>&gt;</span>Add Note<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span></span>
  )}
&lt;/form&gt;
</code></pre>
<p>Within our form's <code>onSubmit</code> event, we've added a conditional. If a note is selected, we'll trigger the <code>handleUpdateNote</code> function. Otherwise, the <code>handleAddNote</code> function will be executed.</p>
<h4 id="heading-test-the-implementation">Test the Implementation</h4>
<p>After incorporating these changes, run your application. When you select a note, make modifications, and click on "Save", you'll observe the note gets updated.</p>
<h3 id="heading-delete-notes-from-the-ui">Delete Notes from the UI</h3>
<p>The last piece of functionality we need on the frontend before moving to the backend development is the ability to delete notes. You'll recall that we added a small "X" button to each note for this purpose. Clicking this button should remove the note from the UI. Let's jump back into the <code>App.tsx</code> file and implement this.</p>
<h4 id="heading-the-deletenote-function">The <code>deleteNote</code> Function</h4>
<p>First, create a function named <code>deleteNote</code> as follows:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> deleteNote = <span class="hljs-function">(<span class="hljs-params">event: React.MouseEvent, noteId: number</span>) =&gt;</span> {
  event.stopPropagation();

  <span class="hljs-keyword">const</span> updatedNotes = notes.filter(<span class="hljs-function">(<span class="hljs-params">note</span>) =&gt;</span> note.id !== noteId);

  setNotes(updatedNotes);
};
</code></pre>
<p>This function takes in two parameters: the <code>event</code> object and the <code>noteId</code>. The <code>event.stopPropagation()</code> line is crucial here because the delete button is nested within a clickable note. It prevents the <code>deleteNote</code> event from interfering with the click event on the note itself. This is especially important when dealing with nested <code>onClick</code> events.</p>
<h4 id="heading-the-filtering-logic">The Filtering Logic</h4>
<p>The core of the delete functionality lies in the <code>filter</code> method applied to the <code>notes</code> array. This method loops through the array and applies a function to each element, much like the <code>map</code> method. It will only return the notes whose IDs do not match the <code>noteId</code> provided, effectively removing the selected note.</p>
<p>We save this newly filtered array into a variable called <code>updatedNotes</code> and then update our state with it by calling <code>setNotes(updatedNotes)</code>.</p>
<h4 id="heading-add-the-onclick-event">Add the <code>onClick</code> Event</h4>
<p>After defining the <code>deleteNote</code> function, attach it to the delete button within the note. Pass in the event and the note ID, like so:</p>
<pre><code class="lang-jsx">&lt;button onClick={<span class="hljs-function">(<span class="hljs-params">event</span>) =&gt;</span> deleteNote(event, note.id)}&gt;x&lt;/button&gt;
</code></pre>
<h4 id="heading-test-the-functionality-1">Test the Functionality</h4>
<p>Now, if you run your app and click the delete button on a given note, you will observe that the note disappears from the UI.</p>
<h2 id="heading-part-2-create-the-backend">PART 2 - Create the Backend</h2>
<p>After implementing UI functionalities, it's time to set up a backend that allows us to persist notes when the user adds, edits, or deletes them. For this, create a new folder in your project at the top level and name it <code>notes-app-server</code>. Even though it might seem like the server code is in the same directory as the UI, they are entirely separate and will run independently.</p>
<h3 id="heading-initial-setup">Initial Setup</h3>
<ol>
<li>Open your terminal and navigate to the <code>notes-app-server</code> folder you just created.</li>
<li>Run the following commands:</li>
</ol>
<pre><code class="lang-bash">npm init
npm i ts-node typescript nodemon @types/cors @types/express @types/node --save-dev
npm i @prisma/client cors express prisma
npx tsc --init
</code></pre>
<ul>
<li><code>npm init</code>: Initializes a new npm module and gives you access to npm packages.</li>
<li><code>npm i ... --save-dev</code>: Installs development dependencies like TypeScript and type definitions.</li>
<li><code>npm i ...</code>: Installs production dependencies like Express and Prisma.</li>
</ul>
<h3 id="heading-modify-packagejson">Modify <code>package.json</code></h3>
<p>After running the above commands, navigate to your <code>package.json</code> and update the <code>scripts</code> section with:</p>
<pre><code class="lang-json"><span class="hljs-string">"start"</span>: <span class="hljs-string">"npx nodemon"</span>
</code></pre>
<p>This script uses nodemon for hot-reloading.</p>
<h3 id="heading-implement-the-server">Implement the Server</h3>
<p>Now, within the <code>notes-app-server</code> directory, create a <code>src</code> folder and within it, an <code>index.ts</code> file. Insert the following code:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> express <span class="hljs-keyword">from</span> <span class="hljs-string">"express"</span>;
<span class="hljs-keyword">import</span> cors <span class="hljs-keyword">from</span> <span class="hljs-string">"cors"</span>;

<span class="hljs-keyword">const</span> app = express();

app.use(express.json());
app.use(cors());

app.get(<span class="hljs-string">"/api/notes"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  res.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"success!"</span> });
});

app.listen(<span class="hljs-number">5000</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"server running on localhost:5000"</span>);
});
</code></pre>
<ol>
<li><code>import express and cors</code>: We import the required libraries for our server.</li>
<li><code>const app = express();</code>: Initializes a new Express application.</li>
<li><code>app.use(express.json());</code>: Parses the JSON body from incoming API requests.</li>
<li><code>app.use(cors());</code>: Adds CORS support.</li>
<li><code>app.listen(5000, ...)</code>: This starts the server listening on port 5000.</li>
</ol>
<h3 id="heading-test">Test</h3>
<p>Finally, you can test the server by navigating to the <code>notes-app-server</code> directory in your terminal and running:</p>
<pre><code class="lang-bash">npm start
</code></pre>
<p>You should see the console log: <code>server running on localhost:5000</code>. To further test, you can use a curl command to hit the <code>/api/notes</code> endpoint. If everything is set up correctly, you'll get a JSON object back.</p>
<h3 id="heading-create-a-postgres-database">Create a Postgres Database</h3>
<p>ElephantSQL is a PostgreSQL database hosting service that makes it easy to set up, maintain, and scale your PostgreSQL database. Here's how to get started with creating a database using ElephantSQL.</p>
<h4 id="heading-step-1-sign-up-log-in">Step 1: Sign Up / Log In</h4>
<ol>
<li>Navigate to the <a target="_blank" href="https://www.elephantsql.com/">ElephantSQL website</a>.</li>
<li>If you don't have an account, you can sign up for free. If you already have one, go ahead and log in.</li>
</ol>
<h4 id="heading-step-2-create-a-new-instance">Step 2: Create a New Instance</h4>
<ol>
<li>Once logged in, you'll find yourself on the "Dashboard" page.</li>
<li>Click on the "Create New Instance" button.</li>
<li>You'll be taken to a page where you can set the details for your new PostgreSQL database instance.</li>
</ol>
<h4 id="heading-step-3-choose-a-plan">Step 3: Choose a Plan</h4>
<ol>
<li>You can start with a free "Tiny Turtle" plan, which is perfect for small projects and testing.</li>
<li>Select the plan that best suits your needs and click "Select".</li>
</ol>
<h4 id="heading-step-4-configure-your-instance">Step 4: Configure Your Instance</h4>
<ol>
<li>You'll be asked to name your instance. Choose a name that you'll remember and that describes the purpose of the database.</li>
<li>You can also select the data center that is geographically closest to you or your users for better performance.</li>
<li>Click on "Review" and then "Create instance" to finalize the creation.</li>
</ol>
<h4 id="heading-step-5-access-your-database">Step 5: Access Your Database</h4>
<ol>
<li>Once the instance is created, click on it in the Dashboard.</li>
<li>Here, you'll see the "Details" tab which includes all the information you need to connect to your database: <code>URL</code>, <code>User &amp; Default database</code>, <code>Password</code>, and more.</li>
</ol>
<h3 id="heading-populate-the-db">Populate the DB</h3>
<h4 id="heading-step-1-login-to-elephantsql">Step 1: Login to ElephantSQL</h4>
<p>Open your web browser and navigate to the ElephantSQL website. Log<br>in to your account.</p>
<h4 id="heading-step-2-open-your-instance">Step 2: Open your Instance</h4>
<p>Once logged in, click on the name of the database instance you've set up.</p>
<h4 id="heading-step-3-navigate-to-the-sql-browser">Step 3: Navigate to the SQL Browser</h4>
<p>In the left sidebar, find and click on "SQL Browser" or something similar (it might say "Browser").</p>
<h4 id="heading-step-4-run-sql-query">Step 4: Run SQL Query</h4>
<p>In the SQL Query editor that appears, you can type or paste in your SQL command:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> <span class="hljs-string">"public"</span>.<span class="hljs-string">"Note"</span> (title, <span class="hljs-keyword">content</span>)
<span class="hljs-keyword">VALUES</span> (<span class="hljs-string">'test title'</span>, <span class="hljs-string">'test content bla bla'</span>);
</code></pre>
<p>After entering the SQL, click on the "Execute" or "Run" button.</p>
<p>That should insert a new row into your <code>Note</code> table with the title 'test title' and content 'test content bla bla'.</p>
<h4 id="heading-optional-verify-the-insert">Optional: Verify the Insert</h4>
<p>You may also want to verify if the data has been inserted correctly. For that, you could use:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> <span class="hljs-string">"public"</span>.<span class="hljs-string">"Note"</span>;
</code></pre>
<p>Run this SQL query in the same SQL browser, and it should return all rows from the <code>Note</code> table, including the one you've just inserted.</p>
<p>And that's it! You've inserted a new row into your table through the ElephantSQL web console.</p>
<h3 id="heading-connect-to-db-from-nodejs-backend-using-prisma">Connect to DB from Node.js backend using Prisma</h3>
<h4 id="heading-step-1-copy-the-elephantsql-connection-url">Step 1: Copy the ElephantSQL Connection URL</h4>
<p>Once you've set up your ElephantSQL database, make sure to copy the connection URL that appears on your dashboard. This URL includes your username and password to the database, so keep it secure.</p>
<h4 id="heading-step-2-create-an-env-file">Step 2: Create an <code>.env</code> File</h4>
<p>Navigate to your <code>notes-app-server</code> directory and create a new <code>.env</code> file:</p>
<pre><code class="lang-bash">touch .env
</code></pre>
<p>Open this file and add the following line to specify the database connection URL:</p>
<pre><code class="lang-bash">DATABASE_URL=<span class="hljs-string">"your_connection_url_here"</span>
</code></pre>
<p>Make sure not to commit this <code>.env</code> file to your Git repository to keep your credentials secure.</p>
<h4 id="heading-step-3-initialize-prisma">Step 3: Initialize Prisma</h4>
<p>If you haven't installed Prisma yet, install it first:</p>
<pre><code class="lang-bash">npm install prisma --save-dev
</code></pre>
<p>Now, initialize Prisma in the <code>notes-app-server</code> directory:</p>
<pre><code class="lang-bash">npx prisma init
</code></pre>
<p>This command will create a new <code>prisma</code> folder containing a <code>schema.prisma</code> file.</p>
<h4 id="heading-step-4-configure-schemaprisma">Step 4: Configure <code>schema.prisma</code></h4>
<p>Open <code>schema.prisma</code> in your text editor. You'll see that Prisma has already generated some configurations for you. Update the <code>datasource</code> block to use the environment variable:</p>
<pre><code class="lang-javascript">datasource db {
  provider = <span class="hljs-string">"postgresql"</span>
  url      = env(<span class="hljs-string">"DATABASE_URL"</span>)
}
</code></pre>
<h4 id="heading-step-5-create-the-note-model">Step 5: Create the Note Model</h4>
<p>Below the <code>datasource</code> block, add a new <code>model</code> block to represent a <code>Note</code>:</p>
<pre><code class="lang-javascript">model Note {
  id      Int     @id @<span class="hljs-keyword">default</span>(autoincrement())
  title   <span class="hljs-built_in">String</span>
  content <span class="hljs-built_in">String</span>
}
</code></pre>
<h4 id="heading-step-6-generate-prisma-client-and-database-table">Step 6: Generate Prisma Client and Database Table</h4>
<p>Run the following command to generate your Prisma client and create the database tables:</p>
<pre><code class="lang-bash">npx prisma db push
</code></pre>
<h4 id="heading-step-7-add-prisma-to-your-application">Step 7: Add Prisma to Your Application</h4>
<p>First, import Prisma at the top of your <code>index.ts</code>:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { PrismaClient } <span class="hljs-keyword">from</span> <span class="hljs-string">"@prisma/client"</span>;
</code></pre>
<p>Then, initialize the Prisma client:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">const</span> prisma = <span class="hljs-keyword">new</span> PrismaClient();
</code></pre>
<h4 id="heading-step-8-query-your-database">Step 8: Query Your Database</h4>
<p>Now you can use Prisma in your application to query the database. For example, in a <code>GET</code> endpoint:</p>
<pre><code class="lang-typescript">app.get(<span class="hljs-string">"/notes"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> notes = <span class="hljs-keyword">await</span> prisma.note.findMany();
  res.json(notes);
});
</code></pre>
<h3 id="heading-optional-install-thunder-client-in-vs-code">Optional: Install Thunder Client in VS Code</h3>
<p>(Feel free to skip this step if you already have a preferred API client)</p>
<p>Using <code>curl</code> is useful for quickly testing APIs, but it becomes cumbersome when you need to build out more complex requests. For instance, handling POST requests with custom bodies and headers can be more complicated. </p>
<p>To make API requests more straightforward, we'll install a client designed for this purpose.</p>
<p>While there are several options like Postman, we're going to focus on installing Thunder Client within VS Code, which makes it simple to execute requests right from your IDE.</p>
<p>To install Thunder Client, navigate to the Extensions section in VS Code and type "Thunder Client" in the search bar. You'll find it in the list of available extensions, identifiable by its purple logo. Click "Install," and upon completion, you'll see a Thunder Client option appear on the left-hand taskbar of your IDE.</p>
<p>Once you've clicked on Thunder Client, a list of your past requests will display. To initiate a new request, click the "New Request" button at the top. This action opens a new tab within Visual Studio Code.</p>
<p>Before proceeding, ensure that your server is running. Open the terminal and verify this. We will use Thunder Client to test our GET endpoint and get familiar with the request-making process. In the URL bar, enter the address of your 'notes' endpoint and specify that it's a GET request.</p>
<p>Click "Send," and you'll see a small window displaying the response. If the status code is 200 and you see an array containing your note, you've successfully made a GET request. Thunder Client will be our tool of choice for testing subsequent create, update, and delete requests. Of course, feel free to use any other tool you're comfortable with for this purpose.</p>
<h3 id="heading-create-post-endpoint">Create POST Endpoint</h3>
<p>In this section, we'll add an endpoint to our Express application that allows us to create a new note. Locate the <code>index.ts</code> file and insert the following code below your existing GET endpoint:</p>
<pre><code class="lang-typescript">app.post(<span class="hljs-string">"/api/notes"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { title, content } = req.body;

  <span class="hljs-keyword">if</span> (!title || !content) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">"title and content fields required"</span>);
  }

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> note = <span class="hljs-keyword">await</span> prisma.note.create({
      data: { title, content },
    });
    res.json(note);
  } <span class="hljs-keyword">catch</span> (error) {
    res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">"Oops, something went wrong"</span>);
  }
});
</code></pre>
<p>The structure is similar to the GET endpoint, but we're using <code>app.post</code> this time. We specify the URL for this POST endpoint and then define our function.</p>
<p>Inside the function, the first task is to extract <code>title</code> and <code>content</code> from the <code>req.body</code>. This is what the UI will send when a user submits the "Add Note" form.</p>
<p>After obtaining <code>title</code> and <code>content</code>, we utilize the Prisma client that we set up earlier to create a new note. We pass the <code>title</code> and <code>content</code> to the <code>prisma.note.create()</code> method, which returns a new note object complete with an ID. This object is then sent back as a JSON response.</p>
<p>To test the endpoint, go to the Thunder Client tab in VS Code. Switch the HTTP method from GET to POST while keeping the URL the same. Click the "Body" tab, which should default to JSON, and input some test values for <code>title</code> and <code>content</code>. After hitting "Send," you should receive a 200 OK status along with the created note, containing an ID, title, and content.</p>
<p>For robustness, we've added validation and error-handling. If either <code>title</code> or <code>content</code> is missing, the server returns a 400 Bad Request status with an appropriate error message. To test this, remove either <code>title</code> or <code>content</code> from the request body and resend it. You should now see a 400 status code along with your error message.</p>
<p>Additionally, we use a try-catch block to handle any errors thrown by the Prisma client. This helps in cases of database connection issues or other unforeseen errors, preventing the backend from crashing.</p>
<p>Finally, you can test the GET endpoint again. It should now return two notes: the first one manually added to the database, and the second one just created through Thunder Client. Switch the method back to GET in Thunder Client and hit "Send"; you should see two notes in the response.</p>
<h3 id="heading-create-put-endpoint">Create PUT Endpoint</h3>
<p>In this segment of the tutorial, we'll focus on adding the ability to update a note. Add the following code snippet below the code for your previous POST endpoint:</p>
<pre><code class="lang-javascript">app.put(<span class="hljs-string">"/api/notes/:id"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> { title, content } = req.body;
  <span class="hljs-keyword">const</span> id = <span class="hljs-built_in">parseInt</span>(req.params.id);

  <span class="hljs-keyword">if</span> (!title || !content) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">"title and content fields required"</span>);
  }

  <span class="hljs-keyword">if</span> (!id || <span class="hljs-built_in">isNaN</span>(id)) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">"ID must be a valid number"</span>);
  }

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> updatedNote = <span class="hljs-keyword">await</span> prisma.note.update({
      <span class="hljs-attr">where</span>: { id },
      <span class="hljs-attr">data</span>: { title, content },
    });
    res.json(updatedNote);
  } <span class="hljs-keyword">catch</span> (error) {
    res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">"Oops, something went wrong"</span>);
  }
});
</code></pre>
<p>The structure of this <code>app.put</code> function is similar to the GET and POST endpoints you've already created. The major difference is the <code>:id</code> parameter in the URL. This acts as a placeholder, allowing you to specify the ID of the note you wish to update.</p>
<p>Inside the function, you'll notice we extract <code>title</code> and <code>content</code> from <code>req.body</code>, just like before. Additionally, we retrieve the ID from <code>req.params</code> and convert it to an integer using <code>parseInt()</code>, as our database stores IDs as integers.</p>
<p>We've added validation checks to ensure that the <code>id</code> exists and is a valid number. If either <code>id</code>, <code>title</code>, or <code>content</code> is missing or invalid, the API returns a 400 status code along with an error message.</p>
<p>Next, we use a try-catch block to attempt the update operation. Within the <code>try</code> section, we call the <code>prisma.note.update()</code> function. We specify the <code>id</code> in a <code>where</code> object and provide the new <code>title</code> and <code>content</code> via a <code>data</code> object. If the operation succeeds, the updated note is sent back in the response. In case of an error, the <code>catch</code> block will return a 500 status and an error message.</p>
<p>To test this, switch to your Thunder Client tab in VS Code. Update the method to PUT and set the URL to include the ID of the note you want to update, for example, <code>/api/notes/3</code>. In the request body, send JSON data with the new <code>title</code> and <code>content</code>. Upon hitting "Send", a 200 status should confirm the update. The returned note should reflect your changes.</p>
<p>To double-check, perform a GET request on the <code>/api/notes</code> endpoint. You should see the updated note in the list.</p>
<p>Lastly, test the validation by supplying an invalid ID, like a random string. The API should return an error message stating that the ID must be a valid number.</p>
<h3 id="heading-create-delete-endpoint">Create DELETE Endpoint</h3>
<p>In addition to our existing endpoints, it's crucial to add validation for empty <code>title</code> or <code>content</code> fields in our <code>app.put</code> function, since these fields are required by our database. Revisit your <code>app.put</code> function in <code>index.ts</code> and add similar validation to what we added for the POST request. Specifically, if either <code>title</code> or <code>content</code> is empty, return a 400 status code along with an error message.</p>
<p>With that in place, let's move on to the DELETE endpoint. Add the following code just after your PUT endpoint:</p>
<pre><code class="lang-javascript">app.delete(<span class="hljs-string">"/api/notes/:id"</span>, <span class="hljs-keyword">async</span> (req, res) =&gt; {
  <span class="hljs-keyword">const</span> id = <span class="hljs-built_in">parseInt</span>(req.params.id);

  <span class="hljs-keyword">if</span> (!id || <span class="hljs-built_in">isNaN</span>(id)) {
    <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).send(<span class="hljs-string">"ID field required"</span>);
  }

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">await</span> prisma.note.delete({
      <span class="hljs-attr">where</span>: { id },
    });
    res.status(<span class="hljs-number">204</span>).send();
  } <span class="hljs-keyword">catch</span> (error) {
    res.status(<span class="hljs-number">500</span>).send(<span class="hljs-string">"Oops, something went wrong"</span>);
  }
});
</code></pre>
<p>This <code>app.delete</code> function works similarly to the update (<code>app.put</code>) endpoint. It also accepts an ID as part of the URL parameters (<code>query params</code> should be <code>URL parameters</code> or <code>route parameters</code>).</p>
<p>First, we validate that the provided ID is a valid number. If it isn't, we return a 400 status code and an accompanying error message.</p>
<p>Once the ID is validated, we proceed to delete the note using Prisma's <code>delete</code> method. In the <code>try</code> block, we specify which note to delete by its ID in the <code>where</code> object. Upon successful deletion, we return a 204 status code, which indicates 'No Content.' This is a standard way to signal to the frontend or API consumers that the deletion was successful.</p>
<p>If an error occurs during the deletion, the <code>catch</code> block returns a 500 status code along with a generic error message.</p>
<p>To test the new DELETE endpoint, switch your HTTP method to <code>DELETE</code> in your testing tool (like Thunder Client or Postman). Use the ID of the note you wish to delete, such as <code>/api/notes/3</code>, and hit 'Send'. You should receive a 204 status code, indicating the operation was successful. To confirm, perform a GET request on your <code>/api/notes</code> endpoint and observe that the note with the specified ID has indeed been removed.</p>
<h2 id="heading-part-3-connect-ui-to-backend">PART 3 - Connect UI to Backend</h2>
<p>Now that we have our backend and UI ready, it's time to connect them. We will do this by using the built in <code>fetch</code> function to call our backend from our UI.</p>
<h3 id="heading-get-and-display-notes">Get and Display Notes</h3>
<p>Let's dive back into our frontend code. Just below our state declarations at the top of our component, we're going to introduce a <code>useEffect</code> hook:</p>
<pre><code class="lang-jsx">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-comment">// ...</span>
}, []);
</code></pre>
<p>Inside this <code>useEffect</code>, we'll define an asynchronous function named <code>fetchNotes</code>. We need to put this in a separate function because React does not support making the <code>useEffect</code> hook asynchronous directly:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> fetchNotes = <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>To handle any potential errors from the API, we'll wrap our API logic inside a <code>try-catch</code> block:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// ...</span>
} <span class="hljs-keyword">catch</span> (e) {
  <span class="hljs-built_in">console</span>.log(e);
}
</code></pre>
<p>Inside the <code>try</code> block, we use the native <code>fetch</code> function to make an API call. Our API is running at <code>http://localhost:5000/api/notes</code>. By default, <code>fetch</code> performs a GET request, which is what we need:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"http://localhost:5000/api/notes"</span>);
</code></pre>
<p>After making the request, we'll process the response and convert it to JSON. The API returns an array of notes, which we'll capture in a variable named <code>notes</code> of type <code>Note[]</code>:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> notes: Note[] = <span class="hljs-keyword">await</span> response.json();
</code></pre>
<p>If everything goes smoothly, the next step is to update our state with the notes fetched from the API:</p>
<pre><code class="lang-jsx">setNotes(notes);
</code></pre>
<p>In the <code>catch</code> block, we'll log any errors that may occur:</p>
<pre><code class="lang-jsx"><span class="hljs-built_in">console</span>.log(e);
</code></pre>
<p>We've defined <code>fetchNotes</code>, but haven't called it yet. To invoke this function, add a call to <code>fetchNotes()</code> at the end of the <code>useEffect</code> block:</p>
<pre><code class="lang-jsx">fetchNotes();
</code></pre>
<p>Lastly, add an empty dependency array to ensure that this code only runs once when the component is first mounted:</p>
<pre><code class="lang-jsx">}, []);
</code></pre>
<p>After saving your changes, you should see the notes from your database displayed in the browser. If you've added or deleted notes directly through the database, those changes should be reflected here.</p>
<p>To wrap things up, you can remove any hardcoded array that you initially added to your <code>notes</code> state variable. Instead, populate it with the data fetched from the API:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> [notes, setNotes] = useState&lt;Note[]&gt;([]);
</code></pre>
<p>This ensures that the <code>notes</code> state is initially empty, then populated by the <code>useEffect</code> through the <code>fetchNotes</code> function.</p>
<h3 id="heading-completed-code-for-this-section-2">Completed Code for this Section</h3>
<pre><code class="lang-jsx">  <span class="hljs-keyword">const</span> [notes, setNotes] = useState&lt;Note[]&gt;([]);


  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> fetchNotes = <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
          <span class="hljs-string">"http://localhost:5000/api/notes"</span>
        );

        <span class="hljs-keyword">const</span> notes: Note[] =
          <span class="hljs-keyword">await</span> response.json();

        setNotes(notes);
      } <span class="hljs-keyword">catch</span> (e) {
        <span class="hljs-built_in">console</span>.log(e);
      }
    };

    fetchNotes();
  }, []);
</code></pre>
<h3 id="heading-save-new-note">Save New Note</h3>
<p>Next, let's explore how to save a note to our backend. We already have a function called <code>handleAddNote</code> that deals with adding a note to the UI:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> handleAddNote = <span class="hljs-keyword">async</span> (
  event: React.FormEvent
) =&gt; {
  <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>To start, remove any code that manually creates a new note object on the frontend. This is because our backend will return this object with all its properties once the note has been saved to the database.</p>
<p>As in our previous example, we'll use a <code>try-catch</code> block to handle the API logic and error handling:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// API logic here</span>
} <span class="hljs-keyword">catch</span> (e) {
  <span class="hljs-built_in">console</span>.log(e);
}
</code></pre>
<p>Place your existing state-changing function calls (<code>setNotes</code>, <code>setTitle</code>, and <code>setContent</code>) inside the <code>try</code> block. These will be executed after the API successfully saves the note:</p>
<pre><code class="lang-jsx">setNotes([newNote, ...notes]);
setTitle(<span class="hljs-string">""</span>);
setContent(<span class="hljs-string">""</span>);
</code></pre>
<p>To call the API, we'll use the <code>fetch</code> function, similar to how we fetched notes. The difference is that this time, we need to pass a second argument to <code>fetch</code> to specify the HTTP method and payload:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
  <span class="hljs-string">"http://localhost:5000/api/notes"</span>,
  {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
    },
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
      title,
      content,
    }),
  }
);
</code></pre>
<p>Don't forget to add the <code>async</code> keyword to the <code>handleAddNote</code> function signature if you haven't already, as we are using the <code>await</code> keyword inside the function.</p>
<p>The server will respond with the newly created note object, which we can then add to our UI. Convert the response to JSON and store it in a variable named <code>newNote</code>:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> newNote = <span class="hljs-keyword">await</span> response.json();
</code></pre>
<p>Finally, in the <code>catch</code> block, we log any errors that might occur:</p>
<pre><code class="lang-jsx"><span class="hljs-built_in">console</span>.log(e);
</code></pre>
<p>Also, make sure to add headers to specify the content type of the data we are sending:</p>
<pre><code class="lang-jsx">headers: {
  <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
}
</code></pre>
<p>Save your changes and test the functionality in the browser. Use the form to add a new note and click "Add Note." If everything is set up correctly, your new note should appear in the list.</p>
<h3 id="heading-completed-code-for-this-section-3">Completed Code for this Section</h3>
<pre><code class="lang-jsx">  <span class="hljs-keyword">const</span> handleAddNote = <span class="hljs-keyword">async</span> (
    event: React.FormEvent
  ) =&gt; {
    event.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
        <span class="hljs-string">"http://localhost:5000/api/notes"</span>,
        {
          <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
          <span class="hljs-attr">headers</span>: {
            <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
          },
          <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
            title,
            content,
          }),
        }
      );

      <span class="hljs-keyword">const</span> newNote = <span class="hljs-keyword">await</span> response.json();

      setNotes([newNote, ...notes]);
      setTitle(<span class="hljs-string">""</span>);
      setContent(<span class="hljs-string">""</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">console</span>.log(e);
    }
  };
</code></pre>
<h3 id="heading-save-updated-note">Save Updated Note</h3>
<p>Next, let's explore how to save a note to our backend. We already have a function called <code>handleAddNote</code> that deals with adding a note to the UI:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> handleAddNote = <span class="hljs-keyword">async</span> (
  event: React.FormEvent
) =&gt; {
  <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>To start, remove any code that manually creates a new note object on the frontend. This is because our backend will return this object with all its properties once the note has been saved to the database.</p>
<p>As in our previous example, we'll use a <code>try-catch</code> block to handle the API logic and error handling:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// API logic here</span>
} <span class="hljs-keyword">catch</span> (e) {
  <span class="hljs-built_in">console</span>.log(e);
}
</code></pre>
<p>Place your existing state-changing function calls (<code>setNotes</code>, <code>setTitle</code>, and <code>setContent</code>) inside the <code>try</code> block. These will be executed after the API successfully saves the note:</p>
<pre><code class="lang-jsx">setNotes([newNote, ...notes]);
setTitle(<span class="hljs-string">""</span>);
setContent(<span class="hljs-string">""</span>);
</code></pre>
<p>To call the API, we'll use the <code>fetch</code> function, similar to how we fetched notes. The difference is that this time, we need to pass a second argument to <code>fetch</code> to specify the HTTP method and payload:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
  <span class="hljs-string">"http://localhost:5000/api/notes"</span>,
  {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
    <span class="hljs-attr">headers</span>: {
      <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
    },
    <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
      title,
      content,
    }),
  }
);
</code></pre>
<p>Don't forget to add the <code>async</code> keyword to the <code>handleAddNote</code> function signature if you haven't already, as we are using the <code>await</code> keyword inside the function.</p>
<p>The server will respond with the newly created note object, which we can then add to our UI. Convert the response to JSON and store it in a variable named <code>newNote</code>:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> newNote = <span class="hljs-keyword">await</span> response.json();
</code></pre>
<p>Finally, in the <code>catch</code> block, we log any errors that might occur:</p>
<pre><code class="lang-jsx"><span class="hljs-built_in">console</span>.log(e);
</code></pre>
<p>Also, make sure to add headers to specify the content type of the data we are sending:</p>
<pre><code class="lang-jsx">headers: {
  <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
}
</code></pre>
<p>Save your changes and test the functionality in the browser. Use the form to add a new note and click "Add Note." If everything is set up correctly, your new note should appear in the list.</p>
<h3 id="heading-completed-code-for-this-section-4">Completed Code for this Section</h3>
<pre><code class="lang-jsx">  <span class="hljs-keyword">const</span> handleAddNote = <span class="hljs-keyword">async</span> (
    event: React.FormEvent
  ) =&gt; {
    event.preventDefault();
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(
        <span class="hljs-string">"http://localhost:5000/api/notes"</span>,
        {
          <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
          <span class="hljs-attr">headers</span>: {
            <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
          },
          <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
            title,
            content,
          }),
        }
      );

      <span class="hljs-keyword">const</span> newNote = <span class="hljs-keyword">await</span> response.json();

      setNotes([newNote, ...notes]);
      setTitle(<span class="hljs-string">""</span>);
      setContent(<span class="hljs-string">""</span>);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">console</span>.log(e);
    }
  };
</code></pre>
<h3 id="heading-save-deleted-note">Save Deleted Note</h3>
<p>In this section, we'll discuss how to delete a note by invoking an API endpoint. We'll focus on the <code>deleteNote</code> function for this functionality:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> deleteNote = <span class="hljs-keyword">async</span> (
  event: React.MouseEvent,
  <span class="hljs-attr">noteId</span>: number
) =&gt; {
  <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>First, we need to make our function asynchronous to handle API calls. So, add the <code>async</code> keyword to the function declaration like this:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> deleteNote = <span class="hljs-keyword">async</span> (
  event: React.MouseEvent,
  <span class="hljs-attr">noteId</span>: number
) =&gt; {
  <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>Next, let's add a <code>try-catch</code> block to manage the API call. The <code>catch</code> block is essential for logging errors, which prevents the application from crashing unexpectedly:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">try</span> {
  <span class="hljs-comment">// API logic here</span>
} <span class="hljs-keyword">catch</span> (e) {
  <span class="hljs-built_in">console</span>.log(e);
}
</code></pre>
<p>Copy the existing UI-update logic you have and paste it into the <code>try</code> block, right after the API call. This ensures that the UI only updates if the API call is successful.</p>
<p>Now, let's get to the main part—making the API call to delete the note. To do so, we'll use the <code>fetch</code> API:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">await</span> fetch(
  <span class="hljs-string">`http://localhost:5000/api/notes/<span class="hljs-subst">${noteId}</span>`</span>,
  {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"DELETE"</span>,
  }
);
</code></pre>
<p>Note that the URL is a template string. It allows us to inject the ID of the note (<code>noteId</code>) that we want to delete. This <code>noteId</code> is passed into our <code>deleteNote</code> function when the user clicks the delete button corresponding to a specific note.</p>
<p>We specify the HTTP method as "DELETE" to indicate that we're requesting to delete a note:</p>
<pre><code class="lang-jsx">method: <span class="hljs-string">"DELETE"</span>,
</code></pre>
<p>Unlike the 'add' or 'update' operations, there's no need to assign the API response to a variable, as we're not expecting any data to be returned:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">await</span> fetch(
  <span class="hljs-string">`http://localhost:5000/api/notes/<span class="hljs-subst">${noteId}</span>`</span>,
  {
    <span class="hljs-attr">method</span>: <span class="hljs-string">"DELETE"</span>,
  }
);
</code></pre>
<p>After successfully deleting the note, we filter out the deleted note from our local notes state:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">const</span> updatedNotes = notes.filter(
  <span class="hljs-function">(<span class="hljs-params">note</span>) =&gt;</span> note.id !== noteId
);
setNotes(updatedNotes);
</code></pre>
<p>Finally, if everything goes smoothly and you save your changes, try running the application in the browser. Click the delete button for a specific note, and then refresh the page. You'll see that the note has been removed successfully.</p>
<h3 id="heading-completed-code-for-this-section-5">Completed Code for this Section</h3>
<pre><code class="lang-jsx">  <span class="hljs-keyword">const</span> deleteNote = <span class="hljs-keyword">async</span> (
    event: React.MouseEvent,
    <span class="hljs-attr">noteId</span>: number
  ) =&gt; {
    event.stopPropagation();

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> fetch(
        <span class="hljs-string">`http://localhost:5000/api/notes/<span class="hljs-subst">${noteId}</span>`</span>,
        {
          <span class="hljs-attr">method</span>: <span class="hljs-string">"DELETE"</span>,
        }
      );
      <span class="hljs-keyword">const</span> updatedNotes = notes.filter(
        <span class="hljs-function">(<span class="hljs-params">note</span>) =&gt;</span> note.id !== noteId
      );

      setNotes(updatedNotes);
    } <span class="hljs-keyword">catch</span> (e) {
      <span class="hljs-built_in">console</span>.log(e);
    }
  };
</code></pre>
<h2 id="heading-the-end-why-not-try-the-bonus-challenges">The End - Why not try the bonus challenges?</h2>
<p>Congratulations on making it to the end! If you enjoyed this project, I have created <a target="_blank" href="https://www.codecoyotes.com/projects/react-node-notes-app">a list of additional challenges to try over at codecoyotes.com</a>. </p>
<p>If you have any questions or suggestions feel free to <a target="_blank" href="https://www.codecoyotes.com/contact">drop me a message here.</a> See you in the next one!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Advanced Indexing Strategies in PostgreSQL ]]>
                </title>
                <description>
                    <![CDATA[ By Faith Oyama Indexing in PostgreSQL is a process that involves creating data structures that are optimized to efficiently search and retrieve data from tables.  An index is a copy of a portion of a table, arranged in a way that enables PostgreSQL t... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/postgresql-indexing-strategies/</link>
                <guid isPermaLink="false">66d45ee93a8352b6c5a2aa57</guid>
                
                    <category>
                        <![CDATA[ data structures ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Fri, 12 May 2023 20:46:41 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/postresql-indexing.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Faith Oyama</p>
<p>Indexing in PostgreSQL is a process that involves creating data structures that are optimized to efficiently search and retrieve data from tables. </p>
<p>An index is a copy of a portion of a table, arranged in a way that enables PostgreSQL to quickly locate and retrieve rows that match a particular query condition.</p>
<p>When a query is executed, PostgreSQL looks at the indexes available to determine if any of them can be used in satisfying the query condition. If it finds a relevant index, PostgreSQL employs it to quickly identify the corresponding rows in the table. This results in significantly speedy queries, especially in situations where tables are large or the conditions are complex.</p>
<p>PostgreSQL provides support for several index types, including B-tree, hash, GiST, SP-GiST, and BRIN. Each index type is tailored to cater to distinct query types and data access patterns.</p>
<p>Apart from the standard index types, PostgreSQL permits users to define custom indexes utilizing user-defined functions.</p>
<p>It's important to note that creating an index requires additional disk space and can impact the performance of write operations, such as INSERT, UPDATE, and DELETE. Because of this, it's essential to consider the trade-offs and carefully choose which columns to index based on the queries you frequently execute and the access patterns of your data.</p>
<h2 id="heading-b-tree-index">B-tree Index</h2>
<p>B-tree index is the most commonly used type of index to efficiently store and retrieve data in PostgreSQL. It's the default index type. Whenever we use the <code>CREATE INDEX</code> command without specifying the type of index we want, PostgreSQL will create a B-tree index for the table or column. </p>
<p>A B-tree index is organized in a tree-like structure. The index starts with a root node, with pointers to child nodes. Each node in the tree typically contains multiple key-value pairs, where the keys are used for indexing, and the values point to the corresponding data in the table.</p>
<p>To create a B-tree index in PostgreSQL, use the <code>CREATE INDEX</code> statement. Here’s the syntax:</p>
<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;
</code></pre>
<h3 id="heading-single-column-indexing">Single-column indexing</h3>
<p>To create a single B-tree index based on one table column instead of creating an index on the entire table, the syntax is as follows.</p>
<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><code>index_name</code> is the name you want to give to the index.</p>
<p><code>table_name</code> is the name of the table on which you want to create the index.</p>
<p><code>column_name</code> is the name of the column(s) on which you want to create the index.</p>
<p>Example:</p>
<p>Let’s create a table called “sales_info” and insert some dummy data.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> sales_info (
  sales_id <span class="hljs-built_in">integer</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>, email <span class="hljs-built_in">VARCHAR</span>, 
  location <span class="hljs-built_in">VARCHAR</span>, item_purchased <span class="hljs-built_in">VARCHAR</span>, 
  price <span class="hljs-built_in">VARCHAR</span>
);
</code></pre>
<p>Insert values into the table using the <code>INSERT</code> statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> sales_info (
  sales_id, email, location, item_purchased, 
  price
) 
<span class="hljs-keyword">VALUES</span> 
  (
    <span class="hljs-number">1</span>, <span class="hljs-string">'halie46@gmail.com'</span>, <span class="hljs-string">'London'</span>, 
    <span class="hljs-string">'Headphone'</span>, <span class="hljs-string">'$50'</span>
  ), 
  (
    <span class="hljs-number">2</span>, <span class="hljs-string">'romaine21@gmail.com'</span>, <span class="hljs-string">'Australia'</span>, 
    <span class="hljs-string">'Webcam'</span>, <span class="hljs-string">'$50'</span>
  ), 
  (
    <span class="hljs-number">3</span>, <span class="hljs-string">'frederique19@gmail.com'</span>, <span class="hljs-string">'Canada'</span>, 
    <span class="hljs-string">'iPhone 14 pro'</span>, <span class="hljs-string">'$1259'</span>
  ), 
  (
    <span class="hljs-number">4</span>, <span class="hljs-string">'kenton_macejkovic80@hotmail.com'</span>, 
    <span class="hljs-string">'London'</span>, <span class="hljs-string">'Wireless Mouse'</span>, <span class="hljs-string">'$20'</span>
  ), 
  (
    <span class="hljs-number">5</span>, <span class="hljs-string">'alexis62@hotmail.com'</span>, <span class="hljs-string">'Switzerland'</span>, 
    <span class="hljs-string">'Dell Charger'</span>, <span class="hljs-string">'$15'</span>
  ), 
  (
    <span class="hljs-number">6</span>, <span class="hljs-string">'concepcion_kiehn@hotmail.com'</span>, 
    <span class="hljs-string">'Canada'</span>, <span class="hljs-string">'Longitech Keyboard'</span>, 
    <span class="hljs-string">'$499'</span>
  );
</code></pre>
<p>If we create a B-tree index on the sales_id column by running this statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> idx_sales_id <span class="hljs-keyword">ON</span> sales_info (sales_id);
</code></pre>
<p>When we run the <code>SELECT</code> statement, we get the total query runtime below.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/select.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The time displayed might look insignificant, as the table we are working with is small. But when working with a large amount of data, this will significantly improve the performance of your queries.</p>
<p>To learn more about the B-Tree index and the behaviors of B-Tree operator classes, see the official documentation <a target="_blank" href="https://www.postgresql.org/docs/11/btree-behavior.html">here.</a></p>
<h2 id="heading-hash-indexes">Hash Indexes</h2>
<p>Hash indexes are designed for fast key-value lookups. When a query condition requires equality checks on indexed columns, hash indexes can provide extremely fast retrieval, as the hash function directly determines the location of the desired data. Hash indexes are most suitable for equality comparisons, such as <code>=</code> or <code>IN</code> operations.</p>
<p>Like other index types, hash indexes need to be maintained during data modifications (inserts, updates, and deletes) to ensure data consistency. But hash index maintenance can be more expensive than B-tree indexes due to the need to resolve collisions and rehash data.</p>
<p>To create a hash index in PostgreSQL, you can use the <code>CREATE INDEX</code> statement with the <code>USING HASH</code> clause. For example:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> hash_name <span class="hljs-keyword">ON</span> table_name <span class="hljs-keyword">USING</span> <span class="hljs-keyword">HASH</span> (column_name);
</code></pre>
<p>This statement creates a hash index named "hash_name" on the specified column of the table.</p>
<p>Point to note here: while hash indexes are available in PostgreSQL, they are not suitable for range queries or sorting. B-tree indexes are typically preferred for such scenarios. Again, B-tree indexes are the default and commonly used index type. </p>
<p>Hash indexes have specific use cases and limitations, and it's essential to assess your requirements and query patterns before deciding on the appropriate index type for your PostgreSQL database.</p>
<p>Example:</p>
<p>Create a Hash index on the table <code>sales_info</code> using HASH for the column <code>sales_id</code>:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> idx_sales_id <span class="hljs-keyword">ON</span> sales_info <span class="hljs-keyword">USING</span> <span class="hljs-keyword">HASH</span>(sales_id);
</code></pre>
<p>Select and filter the data using the <code>WHERE</code> clause:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">EXPLAIN</span> (<span class="hljs-keyword">ANALYZE</span>) 
<span class="hljs-keyword">Select</span> 
  * 
<span class="hljs-keyword">from</span> 
  sales_info 
<span class="hljs-keyword">WHERE</span> 
  sales_id = <span class="hljs-number">5</span>;
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/where-hash.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Check out the <a target="_blank" href="https://www.postgresql.org/docs/11/hash-intro.html">official documentation</a> if you want to dive deeper into Hash Indexes.</p>
<h2 id="heading-gist-and-sp-gist-indexes">GiST and SP-GiST Indexes</h2>
<p>GiST (Generalized Search Tree) and SP-GiST (Space-Partitioned Generalized Search Tree) indexes are advanced index types in PostgreSQL that provide support for a wide range of data types and search operations. </p>
<p>They are particularly useful for handling complex data structures and spatial data, GiST indexes are what you use if you want to speed up full-text searches.</p>
<h3 id="heading-creating-gist-and-sp-gist-indexes">Creating GiST and SP-GiST Indexes:</h3>
<p>To create a GiST or SP-GiST index in PostgreSQL, you can use the <code>CREATE INDEX</code> statement with the <code>USING GIST</code> or <code>USING SPGIST</code> clause, respectively.</p>
<p>Here's an example of creating a GiST index on a geometry column:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> index_geometry <span class="hljs-keyword">ON</span> table_name <span class="hljs-keyword">USING</span> GIST (geometry_column);
</code></pre>
<p>And here's an example of creating an SP-GiST index on a tsvector column:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> index_text_search <span class="hljs-keyword">ON</span> table_name <span class="hljs-keyword">USING</span> SPGIST (tsvector_column);
</code></pre>
<p>Here's an overview of GiST and SP-GiST indexes in PostgreSQL:</p>
<h3 id="heading-gist-index">GiST Index:</h3>
<ul>
<li>Generalized Search Tree (GiST) indexes are versatile index structures that support various data types beyond simple scalar values.</li>
<li>GiST indexes enable efficient searching and retrieval for complex data structures such as geometric objects, text documents, arrays, and more.</li>
<li>They are based on the concept of multidimensional trees, allowing for flexible search operations.</li>
<li>GiST indexes can handle different search predicates, including equality, range, and spatial operations like overlaps, containment, and distance-based searches.</li>
</ul>
<h3 id="heading-sp-gist-index">SP-GiST Index:</h3>
<ul>
<li>Space-Partitioned Generalized Search Tree (SP-GiST) indexes are an extension of GiST indexes that further enhance indexing capabilities.</li>
<li>SP-GiST indexes are designed for data types with space-filling characteristics, such as multi-dimensional data, time-series data, and network data.</li>
<li>They partition the index space into non-overlapping regions, optimizing search performance for specific access patterns.</li>
<li>SP-GiST indexes provide support for various data types, including geometric objects, text search, and more.</li>
<li>They are particularly efficient for spatial indexing and can handle complex spatial queries, including intersection, nearest-neighbor, and clustering operations.</li>
</ul>
<p>See the official documentation <a target="_blank" href="https://www.postgresql.org/docs/12/textsearch-indexes.html">GiST</a> and <a target="_blank" href="https://www.postgresql.org/docs/9.2/spgist.html">SP-GiST</a> indexes for more information.</p>
<h2 id="heading-brin-indexes">BRIN Indexes</h2>
<p>BRIN, or Block Range Index, is an index type in PostgreSQL designed to provide efficient indexing for large tables with sorted data. BRIN index contains the minimum and maximum in a group of database pages. </p>
<p>BRIN index makes is the easiest way to optimize for speed. It is particularly useful for data that exhibits sequential or sorted characteristics, such as time series data or data with a natural ordering.</p>
<p>Here’s an overview of the BRIN Index:</p>
<ul>
<li>BRIN indexes divide the table into logical blocks and store summary information about each block.</li>
<li>Each block contains a range of values, and the index stores the minimum and maximum values within each block.</li>
<li>Instead of storing individual index entries for each row, BRIN indexes store block-level summaries, making them smaller in size compared to other index types.</li>
<li>BRIN indexes work well when the data is sorted or when sequential scans are more efficient than index scans.</li>
</ul>
<p>To create a BRIN index in PostgreSQL, you use the <code>CREATE INDEX</code> statement with the <code>USING BRIN</code> clause.    </p>
<p>Here's an example of creating a BRIN index on a timestamp column:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">INDEX</span> <span class="hljs-built_in">timestamp</span> <span class="hljs-keyword">ON</span> table_name <span class="hljs-keyword">USING</span> BRIN (<span class="hljs-keyword">column</span>);
</code></pre>
<p>The above statement creates a BRIN index on the specified timestamp column of the table.</p>
<p>Here are some things to consider when creating a BRIN Index:</p>
<ul>
<li>BRIN indexes are most effective when the data is sorted or exhibit natural ordering.</li>
<li>They may not be suitable for tables with highly unsorted or non-sequential data.</li>
<li>BRIN indexes are generally used for read-intensive workloads where sequential scans are prevalent.</li>
<li>Regular maintenance and periodic reindexing may be necessary to ensure optimal performance.</li>
</ul>
<p>To read more on BRIN Indexes, you can check out the <a target="_blank" href="https://www.postgresql.org/docs/11/brin-intro.html">official documentation</a>.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In this quick guide, we have seen other types of indexes supported by PostgreSQL other than the B-Tree index. </p>
<p>It is not recommended to create an index on the fly just before running a one-off query. Creating a well-designed index requires careful planning and testing. </p>
<p>It's important to consider that indexes consume disk space. Also, whenever new data rows are inserted or existing rows are updated, the database automatically updates the corresponding index entries.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ PostgreSQL and JSON – How to Use JSON Data in PostgreSQL ]]>
                </title>
                <description>
                    <![CDATA[ By Faith Oyama PostgreSQL is a powerful open-source relational database management system (RDBMS). It was initially created as a successor to the Ingres database system and was later named "PostgreSQL" (short for "Post-Ingres SQL"). PostgreSQL is kno... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/postgresql-and-json-use-json-data-in-postgresql/</link>
                <guid isPermaLink="false">66d45ee4787a2a3b05af43ac</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ json ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Wed, 10 May 2023 20:02:00 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2023/05/PostgreSQL-and-JSON-Using-JSON-Data-in-PostgreSQL.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Faith Oyama</p>
<p>PostgreSQL is a powerful open-source relational database management system (RDBMS). It was initially created as a successor to the Ingres database system and was later named "PostgreSQL" (short for "Post-Ingres SQL").</p>
<p>PostgreSQL is known for its robustness, reliability, and scalability, making it a popular choice for large and complex database applications. It offers advanced features such as support for JSON and other non-relational data types as well as support for spatial data.</p>
<p>JSON file support was first introduced in PostgreSQL v9.2, and with every new release, steady improvements are being made.</p>
<p>In this comprehensive guide, you will learn about JSON functions and operators in PostgreSQL. We’ll also go into the basics of storing JSON data in PostgreSQL, how to query JSON data in PostgreSQL to make it readily accessible, and finally, you’ll learn about working with JSON arrays.</p>
<h2 id="heading-what-is-json">What is JSON?</h2>
<p>JSON stands for JavaScript Object Notation. It is a common way to store data, especially in web applications. It is pretty similar to HTML or XML and was made for applications to easily read JSON files.</p>
<p><strong>Key-Value Pairs</strong>: JSON data is written in key-value pairs surrounded by quotes. Here’s an example of a key-value pair “email”: “<a target="_blank" href="mailto:jsonlearning@gmail.com">jsonlearning@gmail.com</a>”. “Email” here is the key, while “<a target="_blank" href="mailto:jsonlearning@gmail.com">jsonlearning@gmail.c</a>om” represents the value. The two are separated by a colon “:”.</p>
<p><strong>Objects:</strong> An object is a key-value pair or pairs enclosed in curly brackets. Whenever a key-value pair is enclosed in curly brackets it becomes an object and can be treated as a single unit. Multiple key-value pairs can be added in an object, separated with a comma.</p>
<p>Example of a JSON object:</p>
<pre><code>{“email” : “jsonlearning@gmail.com”, 
“country” : “United Kingdom”}
</code></pre><p><strong>Arrays:</strong>  Arrays in JSON are a way to store a collection of values within a single JSON object. An array in JSON is represented by square brackets <code>[ ]</code> containing a comma-separated list of values.</p>
<p>Here’s an example of an array in JSON: <code>[ "apple",  "banana",  "cherry"]</code>.</p>
<p>Arrays in JSON can also be nested, meaning that an array can contain other arrays or objects as values. Here's an example of a nested array:</p>
<pre><code>{ <span class="hljs-string">"firstname"</span> : <span class="hljs-string">"Claire"</span>, 
<span class="hljs-string">"location"</span> : <span class="hljs-string">"United Kingdom"</span>, 
<span class="hljs-string">"blog"</span> : [{ <span class="hljs-string">"id"</span> : <span class="hljs-string">"1"</span>, 
<span class="hljs-string">"title"</span> : <span class="hljs-string">"Welcome to my blog"</span> }, 
{ <span class="hljs-string">"id"</span> : <span class="hljs-string">"2"</span>, 
<span class="hljs-string">"title"</span> : <span class="hljs-string">"My first programming language"</span> }]}
</code></pre><p>In this example of nested Arrays, you can see, “blog” is contained in an array, and the array also contains several objects. </p>
<h2 id="heading-jsonb-in-postgresql">JSONB in PostgreSQL</h2>
<h3 id="heading-what-is-the-jsonb-data-type-and-how-is-it-different-from-json">What is the JSONB data type? And how is it different from JSON?</h3>
<p>JSONB (JSON Binary) is a data type in PostgreSQL that allows you to store and manipulate JSON data in a more effective and efficient way than the regular JSON data type.</p>
<p>JSONB stores JSON data in a binary format, which enables faster indexing and query performance compared to the regular JSON data type. This is because the binary format allows for more efficient storage and retrieval of JSON data, particularly when dealing with large or complex JSON objects.</p>
<p>In addition, JSONB supports additional indexing options, such as the ability to index specific keys within a JSON object, which allows for even faster queries.</p>
<p>The regular JSON data type in PostgreSQL stores JSON data as plain text, without any binary encoding or special indexing support. This makes it simpler to use but can result in slower query performance when dealing with large or complex JSON objects.</p>
<h3 id="heading-how-to-create-a-table-with-the-jsonb-data-type">How to create a table with the JSONB data type</h3>
<p>You can create a table and give a column a data type of JSON or JSONB, just like you give a column the data type of Int, VARCHAR, or Double. You can just simply give the column a data type of either JSON or JSONB.</p>
<p>Here’s an example of creating a Table Journal and giving the column “diary_information” the data type JSONB. </p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> journal (
  <span class="hljs-keyword">id</span> <span class="hljs-built_in">Int</span> <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span> PRIMARY <span class="hljs-keyword">KEY</span>, <span class="hljs-keyword">day</span> <span class="hljs-built_in">VARCHAR</span>, 
  diary_information JSONB
);
</code></pre>
<p>Because we specified the data type to be of type JSONB, any data held in that column must be a valid JSON.</p>
<h3 id="heading-how-to-insert-json-data-into-tables">How to insert JSON data into tables</h3>
<p>After creating a table and giving our column the data type JSONB, how do we insert values into the column? Remember the data must be in a valid JSON format. </p>
<p>To insert data to our table, we use this statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> journal (<span class="hljs-keyword">id</span>, <span class="hljs-keyword">day</span>, diary_information) 
<span class="hljs-keyword">VALUES</span> 
  (
    <span class="hljs-number">1</span>, “Tuesday”, <span class="hljs-string">'{"title": "My first day at work", "Feeling": "Mixed                   feeling"}'</span>
  );
</code></pre>
<p>If we try to retrieve the information using a select statement <code>SELECT * FROM journal</code> we get the following output, meaning the records have been inserted.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/select-from.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>In the next section, we’ll take a look at some Functions and Operators.</p>
<h2 id="heading-overview-of-json-functions-and-operators">Overview of JSON Functions and Operators</h2>
<p>Functions and operators allow you to store, manipulate, and query data in JSON format in PostgreSQL.</p>
<p>Here are some commonly used PostgreSQL Functions and operators used in working with JSON files:</p>
<ul>
<li><code>-&gt;</code>: This operator allows you to extract a specific value from a JSON object, you specify the key as a “child” to the “parent”. </li>
</ul>
<p>For example:</p>
<p>To retrieve a specific value from a JSON object using the <code>-&gt;</code> operator, use it in a SELECT statement as seen below: </p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> 
  <span class="hljs-keyword">Id</span>, 
  <span class="hljs-keyword">day</span>, 
  diary_information -&gt; <span class="hljs-string">'Feeling'</span> <span class="hljs-keyword">AS</span> Feeling 
<span class="hljs-keyword">FROM</span> 
  journal;
</code></pre>
<p>Something to note here is that this operator extracts the field name, with the quote around it.</p>
<ul>
<li><code>-&gt;&gt;</code>: This operator allows you to extract a JSON object field as text without the quotes around it from a JSON object.</li>
</ul>
<p>For example:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> 
  <span class="hljs-keyword">id</span>, 
  <span class="hljs-keyword">day</span>, 
  dairy_information -&gt;&gt; <span class="hljs-string">'Feeling'</span> <span class="hljs-keyword">as</span> Feeling 
<span class="hljs-keyword">FROM</span> 
  products;
</code></pre>
<p>This will extract the value of the "material" key as text from the "features" column in the "products" table.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/04/material-products.png" alt="Image" width="600" height="400" loading="lazy"></p>
<ul>
<li><code>json_agg</code>: This function aggregates JSON values into a JSON array.</li>
</ul>
<p>For example, <code>SELECT json_agg(my_column) FROM my_table;</code> will return a JSON array containing the values in the "my_column" column of the "my_table" table.</p>
<ul>
<li><code>jsonb_set</code>: This function updates a JSON object field with a new value. For example:</li>
</ul>
<pre><code class="lang-sql"><span class="hljs-keyword">UPDATE</span> 
  my_table 
<span class="hljs-keyword">SET</span> 
  json_column = jsonb_set(
    json_column, <span class="hljs-string">'{field_name}'</span>, <span class="hljs-string">'"new_value"'</span>
  ) 
<span class="hljs-keyword">WHERE</span> 
  <span class="hljs-keyword">id</span> = <span class="hljs-number">1</span>;
</code></pre>
<p>To update an existing JSON record, we use the function <code>jsonb_set() ()</code> in an update statement. </p>
<p>For example, to update the record in the table we created earlier, you can run the following code:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">UPDATE</span> 
  journal 
<span class="hljs-keyword">SET</span> 
  diary_information = jsonb_set(
    diary_information, <span class="hljs-string">'{Feeling}'</span>, <span class="hljs-string">'"Excited"'</span>
  ) 
<span class="hljs-keyword">WHERE</span> 
  <span class="hljs-keyword">id</span> = <span class="hljs-number">1</span>;
</code></pre>
<p>This will update the "Feeling" key in the "diary_information" column of the "journal" table with the new value "Excited".</p>
<ul>
<li><code>JSONB_BUILD_OBJECT</code>: Manually inserting JSON values can lead to errors, especially if it’s your first time working with JSON data. But with this function, you can input values without having to worry about curly brackets, colons, and the rest of them.</li>
</ul>
<p>You can use a <code>JSONB_BUILD_OBJECT</code> function to insert a plain text record and this will convert it to JSON data. For example if you run the code:</p>
<pre><code class="lang-sql">JSONB_BUILD_OBJECT('Morning', 'Everybody is annoying today', 'Evening', 'Cannot wait to go home’)
</code></pre>
<p>This will create a value that looks like this:</p>
<pre><code class="lang-sql">{“Morning”: “Everybody is annoying today”, “Evening”: “Cannot wait to go home”}
</code></pre>
<p>Using this function in an insert statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> journal (<span class="hljs-keyword">id</span>, <span class="hljs-keyword">day</span>, feeling) 
<span class="hljs-keyword">VALUES</span> 
  (
    <span class="hljs-number">2</span>, 
    <span class="hljs-string">'Wednesday'</span>, 
    JSONB_BUILD_OBJECT(
      <span class="hljs-string">'Tired'</span>, 
      <span class="hljs-string">'Everybody is annoying today'</span>, 
      <span class="hljs-string">'Hungry'</span>, 
      <span class="hljs-string">'Cannot wait to go home’));</span>
</code></pre>
<p>The new record will be added to the table and because we used the <code>JSONB_BUILD__OBJECT</code> function, the values that follow will be in JSON format.</p>
<p>These are the few functions and operators we can cover in this article. You can read more on JSON functions and Operators in PostgreSQL in the official documentation <a target="_blank" href="https://www.postgresql.org/docs/9.5/functions-json.html">here</a>.</p>
<h2 id="heading-how-to-work-with-json-arrays-in-postgresql">How to Work with JSON Arrays in PostgreSQL</h2>
<p>In PostgreSQL, you can store JSON data as a column value in a table, and you can use JSON arrays to store a collection of JSON objects in a single column. </p>
<p>Working with JSON arrays in PostgreSQL involves various operations, such as inserting, querying, and manipulating JSON data. Let's see how those work.</p>
<h3 id="heading-how-to-insert-json-arrays-into-tables">How to insert JSON arrays into tables</h3>
<p>To insert JSON arrays into a table in PostgreSQL, you can use the INSERT INTO statement along with the VALUES clause to specify the JSON array as a string value.</p>
<p>Here's an example:</p>
<p>Suppose you have a table called employees with columns id, name, and skills. The skills column stores an array of JSON objects representing the skills of each employee.</p>
<p>To insert a new employee record with the following details:</p>
<ul>
<li>id: 1</li>
<li>name: John</li>
<li>skills: [{"name": "Python", "level": "Intermediate"}, {"name": "JavaScript", "level": "Expert"}]</li>
</ul>
<p>You can use the following SQL 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>, skills) 
<span class="hljs-keyword">VALUES</span> 
  (
    <span class="hljs-number">1</span>, <span class="hljs-string">'John'</span>, <span class="hljs-string">'[{"name": "Python", "level": "Intermediate"}, {"name":   "JavaScript", "level": "Expert"}]'</span>
  );
</code></pre>
<h3 id="heading-how-to-query-json-arrays-using-json-operators">How to query JSON arrays using JSON operators</h3>
<p>To query JSON arrays in PostgreSQL, you can use the various JSON functions and operators provided by PostgreSQL. These functions allow you to extract specific values or elements from the JSON array and perform various operations on them. Let's look at an example.</p>
<h4 id="heading-how-to-extract-values-from-a-json-array">How to extract values from a JSON array</h4>
<p>Suppose you have a table called employees with a skills column that stores an array of JSON objects representing the skills of each employee.</p>
<p>To extract the names of all employees who have "Python" as one of their skills, you can use the <code>-&gt;&gt;</code> operator to extract the "name" property of each skill object, and the <code>@&gt;</code> operator to check if the resulting array contains the value "Python":</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> 
  <span class="hljs-keyword">name</span> 
<span class="hljs-keyword">FROM</span> 
  employees 
<span class="hljs-keyword">WHERE</span> 
  skills @ &gt; <span class="hljs-string">'[{"name": "Python"}]'</span> :: jsonb
</code></pre>
<p>This is just an example of the many ways in which you can query and manipulate JSON arrays using the JSON operators provided by PostgreSQL.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In conclusion, PostgreSQL support for JSON provides developers with the ability to simplify data models, enhance application performance, and so much more. This also provides a seamless relationship between relational and non-relational data structures. </p>
<p>You have learned about the JSON and JSONB data types, and what key-value pairs, objects, and arrays are in JSON. You also learned about some operators and functions in PostgreSQL to query data in JSON format.</p>
<p>If you learned a thing or two from this article, please share it with others.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Use PostgreSQL in Python ]]>
                </title>
                <description>
                    <![CDATA[ By Shittu Olumide There are many different types of databases in use today. We have centralized databases, commercial databases, cloud databases, distributed databases, end-user databases, NoSQL databases, relational databases, and lots more.   This ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/postgresql-in-python/</link>
                <guid isPermaLink="false">66d4611c230dff0166905871</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Python ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 14 Nov 2022 15:16:10 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/11/postgresql-in-python.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Shittu Olumide</p>
<p>There are many different types of databases in use today. We have centralized databases, commercial databases, cloud databases, distributed databases, end-user databases, NoSQL databases, relational databases, and lots more.  </p>
<p>This article will focus on an example of a relational database (PostgreSQL) and how to query data from it. Other examples of relational databases include MySQL, MariaDB, and SQLite. </p>
<p>In this tutorial, you will learn how to install, connect, and finally query a PostgreSQL database with Python.</p>
<p>To get started, let's ease into it by learning a bit more about PostgreSQL.</p>
<h2 id="heading-what-is-postgresql">What is PostgreSQL?</h2>
<p>One of the most well-known open-source relational databases is PostgreSQL. It is used by developers and businesses of all sizes worldwide. </p>
<p>In terms of global popularity, PostgreSQL is <a target="_blank" href="https://db-engines.com/en/ranking">ranked fourth</a> by DB-Engines, and its popularity is growing. This shouldn't come as a surprise, given that many web and mobile applications, as well as analytical tools, use PostgreSQL databases.</p>
<p>PostgreSQL also has a robust ecosystem with a huge selection of add-ons and extensions that work well with the main database. For these reasons, PostgreSQL is a fantastic option whether you want to create your own custom database solution or need a transactional or analytical database.</p>
<p>Now that you know what PostgreSQL is, let's discuss how to connect to the database using Python.</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>We must use a database connector library to connect to a PostgreSQL database instance from our Python script. We can pick from a variety of alternatives in Python, but <a target="_blank" href="https://www.psycopg.org/docs/">Psycopg2</a> is the most well-known and widely-used one.</p>
<p>There are alternative libraries built entirely in Python, such as <a target="_blank" href="https://github.com/tlocke/pg8000">pg8000</a> and <a target="_blank" href="https://github.com/python-postgres/fe">py-postgresql</a>, but we'll use Psycopg2 here.</p>
<h3 id="heading-what-is-psycopg2">What is Psycopg2?</h3>
<p>The Psycopg2 library uses the C programming language as a wrapper around the <a target="_blank" href="https://www.postgresql.org/docs/current/libpq.html">libpq</a> PostgreSQL library to support the Python DB API 2.0 standards. The C implementation of Psycopg2 makes it incredibly quick and effective.</p>
<p>Using a SQL query, we can utilize Psycopg2 to get one or more rows from the database. With this library, we can also insert data into the database using a variety of single or batch inserting methods.</p>
<p>The library is like SQL (Structured Query Language) and it performs all the tasks and operations a query language can do. It is both Unicode and Python 3 friendly, and it also has thread safety (the same connection is shared by multiple threads).</p>
<p>It is made to run highly multi-threaded programs, which frequently produce and delete a lot of cursors and do a lot of simultaneous INSERTS or UPDATES. Psycopg2's features include client-side and server-side cursors, asynchronous communication, and notifications.</p>
<h2 id="heading-how-to-install-psycopg2">How to Install Psycopg2</h2>
<p>We must first install Psycopg2 in order to use it. We can install it via the terminal or command prompt using <code>pip</code>.</p>
<pre><code class="lang-python"><span class="hljs-comment">#installation</span>

pip install psycopg2
pip3 install psycopg2
</code></pre>
<p>If we also decide to install the connector library in a virtual environment, you can do so using this code:</p>
<pre><code class="lang-python">virtualenv env &amp;&amp; source env/bin/activate
pip install psycopg2-binary
</code></pre>
<p>The Psycopg2 library and all of its dependencies will be installed into our Python virtual environment with this code snippet.</p>
<p>We have installed our connector, so let's start typing some queries.</p>
<h2 id="heading-how-to-query-postgresql-using-python">How to Query PostgreSQL using Python</h2>
<p>First, you'll need to create a new file and name it whatever you want. Then open it up in your IDE and start writing the code. </p>
<p>The first thing to do is to import the library (this is very important). We will make use of two Psycogp2 objects:</p>
<ul>
<li><strong>Conection object</strong>: The connection to a PostgreSQL database instance is managed by the connection object. It encapsulates a database session, created using the function <code>connect()</code>.</li>
<li><strong>Cursor object</strong>: The cursor object makes it possible for Python scripts to run PostgreSQL commands within a database session. The connection generates cursors, then the <code>cursor()</code> method ties them permanently to the connection. All commands are carried out within the framework of the connection-enclosed database session.</li>
</ul>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> psycopg2

conn = psycopg2.connect(database=<span class="hljs-string">"db_name"</span>,
                        host=<span class="hljs-string">"db_host"</span>,
                        user=<span class="hljs-string">"db_user"</span>,
                        password=<span class="hljs-string">"db_pass"</span>,
                        port=<span class="hljs-string">"db_port"</span>)
</code></pre>
<p>We have to specify those arguments in order to be able to connect to the database. Let's have a quick look into there arguments.</p>
<ul>
<li><strong>database</strong>: the name of the database we wish to access or connect to. Note that we can only connect to one database with one connection object.</li>
<li><strong>host</strong>: this most likely refers to the database server's IP address or URL.</li>
<li><strong>user</strong>: as the name implies, this refers to the name of the PostgreSQL user.</li>
<li><strong>password</strong>: this is the password that matches the PostgreSQL user.</li>
<li><strong>port</strong>: the PostgreSQL server's port number on localhost – it is usually 5432.</li>
</ul>
<p>If our database credentials were entered correctly, we will receive a live database connection object that we can use to build a cursor object. We can go ahead and run any database queries and retrieve data with the aid of a cursor object.</p>
<pre><code class="lang-python">cursor = conn.cursor()
</code></pre>
<p> Let's write a simple query:</p>
<pre><code class="lang-python">cursor.execute(<span class="hljs-string">"SELECT * FROM DB_table WHERE id = 1"</span>)
</code></pre>
<p>We apply the <code>execute()</code> function and supply a query string as its parameter. Then the database will be queried using the query that we entered.</p>
<p>After we have successfully achieved this, in order to be able to retrieve data from the database using Pyscopg2, we have to use any of these functions: <code>fetchone()</code> <code>fetchall()</code>, or <code>fetchmany()</code>.</p>
<h3 id="heading-how-to-use-fetchone">How to use <code>fetchone()</code>:</h3>
<p>After running the SQL query, this function will only return the first row. It is the simplest method of getting data out of a database.</p>
<pre><code class="lang-python"><span class="hljs-comment">#code</span>
print(cursor.fetchone())

<span class="hljs-comment">#output</span>
(<span class="hljs-number">1</span>, <span class="hljs-string">'A-CLASS'</span>, <span class="hljs-string">'2018'</span>, <span class="hljs-string">'Subcompact executive hatchback'</span>)
</code></pre>
<p>The <code>fetchone()</code> function returns a single row in the form of a tuple, with the information arranged in the order specified by the query's supplied columns.</p>
<p>When constructing the query string, it's crucial to provide the column orders precisely in order to distinguish which data in the tuple belongs to which.</p>
<h3 id="heading-how-to-use-fetchall">How to use <code>fetchall()</code>:</h3>
<p>The <code>fetchall()</code> function works the same way as <code>fetchone()</code> except that it returns not just one row but all the rows. So in case we want 20-200 rows or more, we make use of Psycopg2 <code>fetchall()</code>.</p>
<pre><code class="lang-python"><span class="hljs-comment">#code</span>
print(cursor.fetchall())

<span class="hljs-comment">#output</span>
[(<span class="hljs-number">1</span>, <span class="hljs-string">'A-CLASS'</span>, <span class="hljs-string">'2018'</span>, <span class="hljs-string">'Subcompact executive hatchback'</span>),
 (<span class="hljs-number">2</span>, <span class="hljs-string">'C-CLASS'</span>, <span class="hljs-string">'2021'</span>, <span class="hljs-string">'D-segment/compact executive sedan'</span>),
 (<span class="hljs-number">3</span>, <span class="hljs-string">'CLA'</span>, <span class="hljs-string">'2019'</span>, <span class="hljs-string">'Subcompact executive fastback sedan'</span>),
 (<span class="hljs-number">4</span>, <span class="hljs-string">'CLS'</span>, <span class="hljs-string">'2018'</span>, <span class="hljs-string">'E-segment/executive fastback sedan'</span>),
 (<span class="hljs-number">5</span>, <span class="hljs-string">'E-CLASS'</span>, <span class="hljs-string">'2017'</span>, <span class="hljs-string">'E-segment/executive sedan'</span>),
 (<span class="hljs-number">6</span>, <span class="hljs-string">'EQE'</span>, <span class="hljs-string">'2022'</span>, <span class="hljs-string">'All-electric E-segment fastback'</span>),
 (<span class="hljs-number">7</span>, <span class="hljs-string">'EQS'</span>, <span class="hljs-string">'2021'</span>, <span class="hljs-string">'All-electric full-size luxury liftback'</span>),
 (<span class="hljs-number">8</span>, <span class="hljs-string">'S-CLASS'</span>, <span class="hljs-string">'2020'</span>, <span class="hljs-string">'F-segment/full-size luxury sedan.'</span>),
 (<span class="hljs-number">9</span>, <span class="hljs-string">'G-CLASS'</span>, <span class="hljs-string">'2018'</span>, <span class="hljs-string">'Mid-size luxury SUV, known as the G-Wagen'</span>),
 (<span class="hljs-number">10</span>, <span class="hljs-string">'GLE'</span>, <span class="hljs-string">'2019'</span>, <span class="hljs-string">'Mid-size luxury crossover SUV'</span>)]
[...]
</code></pre>
<h3 id="heading-how-to-use-fetchmany">How to use <code>fetchmany()</code>:</h3>
<p>The <code>fetchmany()</code> function allows us to get a number of records out of the database and gives us additional control over the precise number of rows we get.</p>
<pre><code class="lang-python"><span class="hljs-comment">#code</span>
print(cursor.fetchmany(size=<span class="hljs-number">3</span>))

<span class="hljs-comment">#output</span>
[(<span class="hljs-number">1</span>, <span class="hljs-string">'A-CLASS'</span>, <span class="hljs-string">'2018'</span>, <span class="hljs-string">'Subcompact executive hatchback'</span>),
 (<span class="hljs-number">2</span>, <span class="hljs-string">'C-CLASS'</span>, <span class="hljs-string">'2021'</span>, <span class="hljs-string">'D-segment/compact executive sedan'</span>),
 (<span class="hljs-number">3</span>, <span class="hljs-string">'CLA'</span>, <span class="hljs-string">'2019'</span>, <span class="hljs-string">'Subcompact executive fastback sedan'</span>)]
</code></pre>
<p>Because we set the argument to 3, we only received three rows. </p>
<p>When we are done querying our database we need to close the connection with <code>conn.close()</code>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>That was pretty easy, right? We were able to perform all these tasks from a single Python script and it worked really well.</p>
<p>I hope this article was helpful and you can now work with PostgreSQL using Python. </p>
<p>For more information, do check out the Psycopg2 <a target="_blank" href="https://www.psycopg.org/docs/">documentation</a> to learn more.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ SQL Aggregate Functions – How to GROUP BY in MySQL and PostgreSQL ]]>
                </title>
                <description>
                    <![CDATA[ In SQL, aggregate functions let you perform a calculation on multiple data and return a single value. That’s why they are called “aggregate” functions.  Those aggregate functions are AVG(), COUNT(), SUM(), MIN(), and MAX().  While making queries with... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/sql-aggregate-functions-how-to-group-by-in-mysql-and-postgresql/</link>
                <guid isPermaLink="false">66adf222db5636c0b30cba91</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MySQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ SQL ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Kolade Chris ]]>
                </dc:creator>
                <pubDate>Thu, 01 Sep 2022 15:57:13 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/09/agg.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In SQL, aggregate functions let you perform a calculation on multiple data and return a single value. That’s why they are called “aggregate” functions. </p>
<p>Those aggregate functions are <code>AVG()</code>, <code>COUNT()</code>, <code>SUM()</code>, <code>MIN()</code>, and <code>MAX()</code>. </p>
<p>While making queries with the aggregate functions, you can also use them in combination with the <code>GROUP BY</code> clause and <code>HAVING</code> statement in any relational database – MySQL PostgreSQL, and others. </p>
<p>In this article, you will learn how to use aggregate functions on their own and with the <code>GROUP BY</code> clause and <code>HAVING</code> statement.</p>
<h2 id="heading-what-well-cover">What We'll Cover</h2>
<ul>
<li><a class="post-section-overview" href="#heading-how-to-use-aggregate-functions">How to Use Aggregate Functions</a></li>
<li><a class="post-section-overview" href="#heading-syntax-of-aggregate-functions">Syntax of Aggregate Functions</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-the-avg-aggregate-function">How to Use the <code>AVG()</code> Aggregate Function</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-use-the-avg-function-with-group-by-and-having">How to use the <code>AVG()</code> Function with <code>GROUP BY</code> and <code>HAVING</code></a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-use-the-count-aggregate-function">How to Use the <code>COUNT()</code> Aggregate Function</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-use-count-with-group-by-and-having">How to Use <code>COUNT()</code> with <code>GROUP BY</code> and <code>HAVING</code></a>  </li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-use-the-max-aggregate-function">How to Use the <code>MAX()</code> Aggregate Function</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-use-max-with-group-by-and-having">How to Use <code>MAX()</code> with <code>GROUP BY</code> and <code>HAVING</code></a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-use-the-min-aggregate-function">How to Use the <code>MIN()</code> Aggregate Function</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-use-min-with-group-by-and-having">How to Use <code>MIN()</code> with <code>GROUP BY</code> and <code>HAVING</code> </a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-use-the-sum-aggregate-function">How to Use the <code>SUM()</code> Aggregate Function</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-use-sum-with-group-by-and-having">How to Use <code>SUM()</code> with <code>GROUP BY</code> and <code>HAVING</code></a>  </li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ul>
<h2 id="heading-how-to-use-aggregate-functions">How to Use Aggregate Functions</h2>
<p>To show you how the aggregate functions work, I’ll be working with an <code>employees</code> table in an <code>employees_data</code> database.</p>
<p>Running <code>SELECT * FROM employees</code> got me the following:
<img src="https://www.freecodecamp.org/news/content/images/2022/09/ss1.png" alt="ss1" width="600" height="400" loading="lazy"></p>
<h2 id="heading-syntax-of-aggregate-functions">Syntax of Aggregate Functions</h2>
<p>The syntax for working with aggregate functions looks like this:</p>
<pre><code class="lang-sql">aggregate_function(MODIFIER | expression)
</code></pre>
<ul>
<li>the aggregate function could be <code>AVG</code>, <code>COUNT</code>, <code>MAX</code>, <code>MIN</code>, or <code>SUM</code></li>
<li>the modifier could be all the values or the values in a particular column</li>
</ul>
<p>This syntax would make more sense in practice, so let’s get to use it with the aggregate functions.</p>
<h2 id="heading-how-to-use-the-avg-aggregate-function">How to Use the <code>AVG()</code> Aggregate Function</h2>
<p>The <code>AVG()</code> aggregate function gets the total number of data and calculates their average.</p>
<p>I was able to get the average wage paid to the employees this way:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">AVG</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss2.png" alt="ss2" width="600" height="400" loading="lazy"> </p>
<p>The query below gets the average wage of junior developers:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">AVG</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">role</span> = <span class="hljs-string">"Junior dev"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss3.png" alt="ss3" width="600" height="400" loading="lazy"> </p>
<h3 id="heading-how-to-use-the-avg-function-with-group-by-and-having">How to use the <code>AVG()</code> Function with <code>GROUP BY</code> and <code>HAVING</code></h3>
<p>You can get the average number of entries (rows) in a particular column with the <code>GROUP BY</code> clause and <code>HAVING</code> statement. This means you have to combine those two with <code>AVG()</code>.</p>
<p>For instance, I was able to get the average wage paid to employees in each row with this query:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">AVG</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss4.png" alt="ss4" width="600" height="400" loading="lazy"> </p>
<p>I was also able to get the average wage of senior developers by using the HAVING statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">AVG</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
<span class="hljs-keyword">HAVING</span> <span class="hljs-keyword">role</span> = <span class="hljs-string">"Senior dev"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss5.png" alt="ss5" width="600" height="400" loading="lazy"> </p>
<h2 id="heading-how-to-use-the-count-aggregate-function">How to Use the <code>COUNT()</code> Aggregate Function</h2>
<p><code>COUNT()</code> returns the number of rows in a table based on the condition (or filter) you apply.</p>
<p>For example, to get the total number of rows, I ran the query below:</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>And I got 20:
<img src="https://www.freecodecamp.org/news/content/images/2022/09/ss6.png" alt="ss6" width="600" height="400" loading="lazy"> </p>
<p>To get the total number of employees from the USA, I ran the query below:</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
<span class="hljs-keyword">WHERE</span> country = <span class="hljs-string">"USA"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss7.png" alt="ss7" width="600" height="400" loading="lazy"> </p>
<p>And to get the employees who are technical writers, I did this:</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
<span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">role</span> = <span class="hljs-string">"Tech Writer"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss8.png" alt="ss8" width="600" height="400" loading="lazy"> </p>
<h3 id="heading-how-to-use-count-with-group-by-and-having">How to Use <code>COUNT()</code> with <code>GROUP BY</code> and <code>HAVING</code></h3>
<p>In a large database, you can use the <code>GROUP BY</code> clause and <code>HAVING</code> statement in combination with COUNT() to get the total number of entries (rows) in a particular column.</p>
<p>In the database I’m using in this article, I was able to get the total number of employees in each row with the GROUP BY clause:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">COUNT</span>(*) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss9.png" alt="ss9" width="600" height="400" loading="lazy"> </p>
<p>To get the number of only the employees that are senior developers, I attached <code>HAVING role = "Senior dev"</code> to the query:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">COUNT</span>(*) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
<span class="hljs-keyword">HAVING</span> <span class="hljs-keyword">role</span> = <span class="hljs-string">"Senior dev"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss10.png" alt="ss10" width="600" height="400" loading="lazy"> </p>
<h2 id="heading-how-to-use-the-max-aggregate-function">How to Use the <code>MAX()</code> Aggregate Function</h2>
<p>The <code>MAX()</code> function returns the maximum value within non-NULL values. This means it would ignore fields that are empty and return the highest value within those that are not empty. </p>
<p>For example, to get the highest wage in the <code>employees</code> table, I used the <code>MAX()</code> function like this:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">MAX</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss11.png" alt="ss11" width="600" height="400" loading="lazy"> </p>
<p>To get the maximum wage for mid-level developers, I used the <code>WHERE</code> statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">MAX</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">WHERE</span> <span class="hljs-keyword">role</span> = <span class="hljs-string">"Mid level dev"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss12.png" alt="ss12" width="600" height="400" loading="lazy"> </p>
<h3 id="heading-how-to-use-max-with-group-by-and-having">How to Use <code>MAX()</code> with <code>GROUP BY</code> and <code>HAVING</code></h3>
<p>To get the maximum wage in each role, the <code>GROUP BY</code> clause comes in handy:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">MAX</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss13.png" alt="ss13" width="600" height="400" loading="lazy"> </p>
<p>And to get the maximum wage in a particular role, combining the HAVING statement with the <code>GROUP BY</code> clause gets it done: </p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">MAX</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
<span class="hljs-keyword">HAVING</span> <span class="hljs-keyword">role</span> = <span class="hljs-string">"Tech writer"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss14.png" alt="ss14" width="600" height="400" loading="lazy"> </p>
<h2 id="heading-how-to-use-the-min-aggregate-function">How to Use the <code>MIN()</code> Aggregate Function</h2>
<p>The <code>MIN()</code> function is the opposite of the <code>MAX()</code> function – it returns the minimum value within non-NULL values. </p>
<p>For example, I got the lowest wage on the <code>employees</code> table this way:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">MIN</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss15.png" alt="ss15" width="600" height="400" loading="lazy"> </p>
<h3 id="heading-how-to-use-min-with-group-by-and-having">How to Use <code>MIN()</code> with <code>GROUP BY</code> and <code>HAVING</code></h3>
<p>Again, to get the minimum wage in each role, the <code>GROUP BY</code> clause can get it done:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">MIN</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss16.png" alt="ss16" width="600" height="400" loading="lazy"> </p>
<p>And to get the minimum wage of a particular role, the <code>HAVING</code> statement and <code>GROUP BY</code> clause are what to use:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">MIN</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
<span class="hljs-keyword">HAVING</span> <span class="hljs-keyword">role</span> = <span class="hljs-string">"Junior dev"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss17.png" alt="ss17" width="600" height="400" loading="lazy"> </p>
<h2 id="heading-how-to-use-the-sum-aggregate-function">How to Use the <code>SUM()</code> Aggregate Function</h2>
<p>The SUM() aggregate function adds the number of entries in a column based on the filter applied.</p>
<p>The query below gets the total number of wages paid to employees:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">SUM</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss18.png" alt="ss18" width="600" height="400" loading="lazy"> </p>
<h3 id="heading-how-to-use-sum-with-group-by-and-having">How to Use SUM() with <code>GROUP BY</code> and <code>HAVING</code></h3>
<p>To get the sum of the total wages paid for employees in each role, I selected the role, used <code>SUM()</code> on the wages, and grouped them by the role:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">SUM</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss19.png" alt="ss19" width="600" height="400" loading="lazy"> </p>
<p>To get the total wages paid to technical writers only, I used the <code>HAVING</code> statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> <span class="hljs-keyword">role</span>, <span class="hljs-keyword">SUM</span>(wage) 
<span class="hljs-keyword">FROM</span> employees
<span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-keyword">role</span>
<span class="hljs-keyword">HAVING</span> <span class="hljs-keyword">role</span> = <span class="hljs-string">"Tech Writer"</span>
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/09/ss20.png" alt="ss20" width="600" height="400" loading="lazy"> </p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This article took you through what aggregate functions are in SQL, their syntax, and how to use them. </p>
<p>In addition, you also learned how to use aggregate functions with the <code>GROUP BY</code> clause, <code>HAVING</code>, and <code>WHERE</code> statements.</p>
<p>If you want to learn how the <code>HAVING</code> statement works, you should read <a target="_blank" href="https://www.freecodecamp.org/news/sql-having-how-to-group-and-count-with-a-having-statement/">this article</a> I wrote on it.</p>
<p>Thank you for reading.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Upgrade Postgres Major Versions with Near-Zero Downtime – a Practical Guide ]]>
                </title>
                <description>
                    <![CDATA[ By Wenbin Fang It's impossible to create zero-downtime Postgres upgrades across major versions – right? Please, correct me if I’m wrong :)  But at least we’ve found a way to get close to zero downtime. PostgreSQL Upgrades are hard! How Retool upgrad... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-upgrade-postgres-major-versions-with-near-zero-downtime/</link>
                <guid isPermaLink="false">66d46178d14641365a05098b</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Mon, 15 Aug 2022 21:42:14 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/08/Listen-Score-loves-postgres.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Wenbin Fang</p>
<p>It's impossible to create zero-downtime Postgres upgrades across major versions – right? Please, correct me if I’m wrong :) </p>
<p>But at least we’ve found a way to get close to zero downtime.</p>
<ul>
<li><a target="_blank" href="https://andreas.scherbaum.la/blog/archives/1116-PostgreSQL-Upgrades-are-hard!.html">PostgreSQL Upgrades are hard!</a></li>
<li><a target="_blank" href="https://retool.com/blog/how-we-upgraded-postgresql-database/">How Retool upgraded our 4 TB main application PostgreSQL database</a></li>
</ul>
<p>At <a target="_blank" href="https://www.listennotes.com/">Listen Notes</a>, we've performed Postgres major version upgrades twice since 2017, the year Listen Notes was founded. During these upgrades, we experienced zero downtime for “read” operations, and less than 1 minute downtime for “write” operations.</p>
<p>Let’s walk through the process we went through of upgrading Postgres at Listen Notes.</p>
<h3 id="heading-tldr"><strong>TL;DR</strong></h3>
<ol>
<li>Provision a new replica DB (DB_A) with the old version of Postgres.</li>
<li>Change DB hosts’ IP addresses in “/etc/hosts” of online servers to use read-only replica DB (not DB_A). By this moment, all write operations will fail.</li>
<li>Run <a target="_blank" href="https://www.postgresql.org/docs/current/pgupgrade.html">pg_upgrade</a> (with “--link”) on DB_A to upgrade to the new version of Postgres, and promote DB_A to be a primary.</li>
<li>Replace all DB hosts’ IP addresses in “/etc/hosts” of online servers to use DB_A. By this moment, write operations would resume.</li>
<li>Re-provision new replica nodes with the new version of Postgres.</li>
</ol>
<h2 id="heading-how-we-use-postgres-in-listen-notes">How We Use Postgres in Listen Notes</h2>
<p>Listen Notes is a popular podcast search engine. We provide a website (<a target="_blank" href="https://www.listennotes.com/">ListenNotes.com</a>) with millions of monthly pageviews, and a solid <a target="_blank" href="https://www.listennotes.com/api/">Podcast API</a> used by thousands of 3rd-party apps and websites.</p>
<p>We use Postgres as our main database, which stores all podcasts, episode metadata, and user data. </p>
<p>We run a self-hosted Postgres cluster on AWS EC2, consisting of one primary (db1) for write and read operations, and two replicas (db2 &amp; db3) for read-only operations. The database size is a bit smaller than 1TB.</p>
<h3 id="heading-postgres-progress">Postgres Progress</h3>
<p>When Listen Notes was started in 2017, we ran Postgres 9.6.</p>
<p><a target="_blank" href="https://www.listennotes.fm/p/monthly-update-for-may-2019-19-05-31">Then we upgraded to Postgres 11.0 in 2019</a>.</p>
<p><a target="_blank" href="https://www.listennotes.fm/p/monthly-update-for-september-2021">By 2021, we were running Postgres 13.0</a>.</p>
<p>Generally speaking, we are not comfortable using the latest version of any infrastructure software, be it Postgres, Django, Redis, or others.</p>
<p>We trust the quality of Postgres, but there may be fewer documents and online discussions for the latest version, which might make troubleshooting difficult when version-specific issues occur.</p>
<p>Listen Notes servers that talk to Postgres DB are using hostnames like db1.internal.ln, db2.internal.ln, db3.internal.ln, and so on.</p>
<p>These hostnames are in “/etc/hosts,” so it’s easy to change the actual IP address while keeping the hostnames unchanged.</p>
<p>There are both online and offline workloads for our Postgres cluster. The online workload is to serve our website (ListenNotes.com) and API endpoints (PodcastAPI.com), which can’t have long downtime (for example, over 5 minutes).</p>
<p>The offline workload runs Celery tasks and other scripts that can be stopped for a relatively long time (for example, 2 hours). You can read our past blog posts to learn details of the Listen Notes architecture:</p>
<ul>
<li><a target="_blank" href="https://www.listennotes.com/blog/the-boring-technology-behind-a-one-person-23/">The boring technology behind a one-person Internet company</a></li>
<li><a target="_blank" href="https://www.listennotes.com/blog/good-enough-engineering-to-start-an-internet-27/">Good enough engineering to start an Internet company</a></li>
<li><a target="_blank" href="https://www.listennotes.com/blog/how-i-accidentally-built-a-podcast-api-business-46/">How I accidentally built a Podcast API business</a></li>
</ul>
<p>For upgrading Postgres across major versions, our goal is to achieve zero downtime for read operations and minimal downtime (less than 5 minutes) for write operations. This will ensure most of our users won’t be affected during the time of upgrading.</p>
<h2 id="heading-how-to-prepare-for-postgres-upgrades"><strong>How to Prepare </strong>for Postgres Upgrades<em>**</em></h2>
<p>The actual upgrade may take only 30 minutes, but we typically spend a few workdays preparing, which increases the odds of success during the upgrade.</p>
<h3 id="heading-prep-step-1">Prep Step 1:</h3>
<p>We must make sure the new major version of Postgres works well with our code base. So we test the new version of Postgres on dev and staging. </p>
<p>In addition to automatic unit tests, we have to manually test all major product features.</p>
<h3 id="heading-prep-step-2">Prep Step 2:</h3>
<p>Online services (<a target="_blank" href="https://www.listennotes.com/">ListenNotes.com</a> and <a target="_blank" href="https://podcastapi.com/">PodcastAPI.com</a>) should work for most users even when Postgres write operations are disabled.</p>
<p>For ListenNotes.com, a majority of users are conducting “read-only” tasks, such as searching podcasts, browsing podcast details, and similar harmless actions. This means that “write” failures should affect only a tiny fraction of users.</p>
<p>For PodcastAPI.com, all API endpoints are read-only or offloading writes to async offline tasks, so write operations can be temporarily disabled.</p>
<p>We would spend some time testing on staging to make sure online services can still be functional when database writes are disabled.</p>
<h3 id="heading-prep-step-3">Prep Step 3:</h3>
<p>We spend the most time <em>rehearsing</em> the process of upgrading Postgres. Basically, we provision the entire fleet of Listen Notes and practice all necessary steps to upgrade Postgres.</p>
<p>We try to codify some steps in Ansible or Bash scripts to automate a bit. We document and time each step. By the time we perform it in the production environment, we know how many minutes (or even seconds) each step will take.</p>
<h3 id="heading-prep-step-4">Prep Step 4:</h3>
<p>We practice how to quickly rollback to the old version of Postgres, just in case the upgrade fails and we are forced to restore a stable environment ASAP.</p>
<h2 id="heading-how-to-upgrade-postgres"><strong>How to Upgrade </strong>Postgres<em>**</em></h2>
<p>We typically perform the actual upgrade on a Friday night, when website and API traffic is low. Plus, we must have a good rest during the daytime, to preserve enough energy to perform such dangerous and stressful operations in production later that same evening :)</p>
<p>Since we’ve created a detailed TODO list in Notion during the previous few days of preparation, we carefully follow the TODO list to upgrade Postgres:</p>
<h3 id="heading-upgrade-step-1">Upgrade Step 1:</h3>
<p>We provision a new read-only replica DB with the old version of Postgres. Let’s call it DB_A. It’ll sync data from the primary DB in real-time, and will be upgraded to the new version of Postgres first then be promoted to be primary. </p>
<p>If the upgrade on DB_A fails later, we still have the option to quickly rollback and use the old primary DB instead.</p>
<h3 id="heading-upgrade-step-2">Upgrade Step 2:</h3>
<p>We stop all offline tasks, except for one Celery worker to handle some time sensitive async tasks, such as sending login emails. We’ll stop this Celery worker right before Step 4. </p>
<p>We also take most web/API servers out of load balancer, leaving only a minimal fleet of online servers.</p>
<h3 id="heading-upgrade-step-3">Upgrade Step 3:</h3>
<p>We change all DB hosts’ IP addresses in “/etc/hosts” on the minimal fleet of online servers (for example, web, API…) to use an old read-only DB. Let’s call it DB_B.</p>
<p>From this point on, all write ops should fail. This step is to make sure the future new primary DB won’t have outdated data.</p>
<h3 id="heading-upgrade-step-4">Upgrade Step 4:</h3>
<p>We run pg_upgrade (with “--link”) on DB_A to upgrade to the new version of Postgres, and promote it to be a primary DB. From this point on, DB_A is the primary, running the new version of Postgres.</p>
<h3 id="heading-upgrade-step-5">Upgrade Step 5:</h3>
<p>We replace all occurrences of DB_B’s IP with DB_A’s in “/etc/hosts” of the minimal fleet of online servers (for example, web, API…). By this point, DB_A is used as both primary and replica. And write ops should be good now.</p>
<h3 id="heading-upgrade-step-6">Upgrade Step 6:</h3>
<p>We change “/etc/hosts” to use DB_A for all DB hosts (primary + replica) on all other servers and bring back offline tasks. </p>
<p>From the users’ point of view, all Listen Notes services should be normal now . In fact, all API users should not experience any outage during the entire upgrade process, while a tiny portion of website users may experience errors when performing “write operations,” such as creating a podcast playlist or clip.</p>
<h3 id="heading-the-most-important-step-in-upgrading-to-a-new-version-of-postgres"><strong>The most important step in upgrading to a new version of Postgres</strong></h3>
<p>Among them all, Step 4 is the most critical. If it fails or runs too long (for example, more than 10 minutes), then we must rollback by changing “/etc/hosts” on those online servers.</p>
<p>From our experience, it took less than 1 minute to run Step 4. Your mileage may vary if you’ve got a bigger (or smaller) database.</p>
<p>After we make sure things are back to normal after Step 6, we could re-provision replica DB instances with the new version of Postgres. And eventually, we terminate old DB instances.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>Sounds complex? Yeah, kind of…</p>
<p>Database operations in production are inherently complex and dangerous. Can’t rush the process :)</p>
<p><img src="https://production.listennotes.com/web/image/747a0d713bc541d8bc063df623d35ee0.jpeg" alt="Image" width="600" height="400" loading="lazy"></p>
<h2 id="heading-faqs"><strong>FAQs</strong></h2>
<h3 id="heading-why-dont-you-use-managed-postgres-for-example-amazon-rds">Why don’t you use managed Postgres, for example Amazon RDS?</h3>
<p>We want to have full control of key infrastructure software (for example, Postgres, Elasticsearch…), because…</p>
<ol>
<li>We don’t want platform lock-in</li>
<li>We want to understand what’s going on inside the server, avoiding helplessly waiting for 3rd-party customer support teams (for example, AWS) to help solve urgent production issues inside blackboxes</li>
<li>It’s more cost-effective for us to run Postgres instances on our own — if money is not an issue (for example, raising big VC funding) or if we had less Postgres operational experience, then Amazon RDS might have been a good option to start with. Just like many things in life, we need to do things with constraints (like money, time, expertise…).</li>
</ol>
<p>As far as I know, using a managed Postgres (like Amazon RDS) won’t remove the pain of upgrading across major versions: Google “<a target="_blank" href="https://www.google.com/search?q=amazon+rds+upgrade+postgres+versions+zero+downtime">Amazon RDS upgrade Postgres versions with zero downtime</a>”.</p>
<h3 id="heading-why-dont-you-use-3rd-party-tools-to-automate-the-process-a-bit-like-one-button-push-to-automate-the-whole-thing">Why don’t you use 3rd-party tools to automate the process a bit (like one-button push to automate the whole thing)?</h3>
<p>We don’t know if there are any reliable 3rd-party tools out there that are easy to use, easy to understand, safe to use… But we are open to recommendations – <a target="_blank" href="mailto:wenbin@listennotes.com">wenbin@listennotes.com</a>.</p>
<p>We oftentimes need to evaluate if it is worth the time and risk to learn and to use new blackbox tools in production, especially for serious DevOps tasks.</p>
<h3 id="heading-why-dont-you-use-mysql-mongodb-or-other-non-postgres-databases">Why don’t you use MySQL, MongoDB, or other non-Postgres databases?</h3>
<p>When I started Listen Notes, I knew Postgres way better than MySQL and other databases, because my previous employer Nextdoor.com uses Postgres. And I know Instagram and other large scale online services also use Postgres as their main data store (at least for the first few years). </p>
<p>If Postgres works well for huge online services, then it should also work for Listen Notes :) Sometimes we spend time learning new technologies to start a project, but more often we simply use technologies that we already know in order to jump-start a project faster and more efficiently.</p>
<p>Again, upgrading non-Postgres databases across major versions is also not easy…but here’s hoping all the above steps helped make whatever Postgres upgrade you performed a success!</p>
<p><em>This blog post was originally published at <a target="_blank" href="https://www.listennotes.com/blog/a-practical-way-to-upgrade-postgres-major-49/">ListenNotes.com</a>.</em></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Schedule a Job in PostgreSQL ]]>
                </title>
                <description>
                    <![CDATA[ By Jagruti Tiwari Scheduling allows you to automate things so you don't have to do them in real time. In this article we will see how to schedule a job in PostgreSQL. We'll use pgAgent, a job scheduling agent for PostgreSQL. How to Install PostgreSQL... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-schedule-a-job-in-postgresql/</link>
                <guid isPermaLink="false">66d45f379208fb118cc6cfcb</guid>
                
                    <category>
                        <![CDATA[ automation ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 12 Jul 2022 14:04:43 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/07/pexels-mat-brown-552598.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Jagruti Tiwari</p>
<p>Scheduling allows you to automate things so you don't have to do them in real time.</p>
<p>In this article we will see how to schedule a job in PostgreSQL. We'll use pgAgent, a job scheduling agent for PostgreSQL.</p>
<h1 id="heading-how-to-install-postgresql-and-stack-builder">How to Install PostgreSQL and Stack Builder</h1>
<p>You can install pgAgent with Stack Builder.</p>
<p>Install PostreSQL from the <a target="_blank" href="https://www.postgresql.org/download/">official website</a>. This will download Stack Builder along with the installer.</p>
<p>If you have PostgreSQL already installed, you could download the installer and run Stack Builder if you don't have it already. </p>
<p>Stack Builder runs once PostgreSQL installation is complete. I am using PostgreSQL14 and pgAdmin4. </p>
<h1 id="heading-how-to-install-pgagent">How to Install pgAgent</h1>
<p>When you run Stack Builder it will first open a welcome wizard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-163841_auto_x2_auto_x2_colored_toned_light_ai.jpg" alt="Screenshot-2022-07-10-163841_auto_x2_auto_x2_colored_toned_light_ai" width="600" height="400" loading="lazy"></p>
<p>If you have multiple PostgreSQL versions installed you will pick one to use to install pgAgent.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-163907_auto_x2_colored_toned_light_ai_auto_x2_colored_toned_light_ai.jpg" alt="Screenshot-2022-07-10-163907_auto_x2_colored_toned_light_ai_auto_x2_colored_toned_light_ai" width="600" height="400" loading="lazy"></p>
<p>Under <em>Adds-ons, tools and utilities</em>, you will find pgAgent. Check the checkbox to install it.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-163926_auto_x2_colored_toned_light_ai--1-_auto_x2_colored_toned_light_ai.jpg" alt="Screenshot-2022-07-10-163926_auto_x2_colored_toned_light_ai--1-_auto_x2_colored_toned_light_ai" width="600" height="400" loading="lazy"></p>
<p>Next, it will ask you to choose a directory where you want to install pgAgent.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-163956.png" alt="Screenshot-2022-07-10-163956" width="600" height="400" loading="lazy"></p>
<p>Stack Builder will then open a pgAgent SetUp Wizard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-164018.png" alt="Screenshot-2022-07-10-164018" width="600" height="400" loading="lazy"></p>
<p>Here you will pick whether you want to install it in an upgrade mode. If you do not want to automatically change scripts while upgrading you can check the box.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-164038.png" alt="Screenshot-2022-07-10-164038" width="600" height="400" loading="lazy"></p>
<p>In the <em>PostgreSQL installation details</em> wizard, provide the username and password that you entered when you installed PostgreSQL.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-164125.png" alt="Screenshot-2022-07-10-164125" width="600" height="400" loading="lazy"></p>
<p>If you enter incorrect details it will throw a connection error. So make sure you remember those details.</p>
<blockquote>
<p><strong><em>NOTE:</em></strong> Login to PostgreSQL with the username and password you provided at this stage to view <em>pgAgent jobs</em>.</p>
</blockquote>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-164233.png" alt="Screenshot-2022-07-10-164233" width="600" height="400" loading="lazy"></p>
<p>After adding those details, the setup begins:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-164250.png" alt="Screenshot-2022-07-10-164250" width="600" height="400" loading="lazy"></p>
<p>It takes a couple of seconds to finish.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-164304.png" alt="Screenshot-2022-07-10-164304" width="600" height="400" loading="lazy"></p>
<p>Click the <em>finish</em> button at the end.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-164320.png" alt="Screenshot-2022-07-10-164320" width="600" height="400" loading="lazy"></p>
<p>Stack Builder will also display an <em>installation completed</em> wizard. It has instructions to install and uninstall utilites. </p>
<p>Once Stack Builder is installed you simply run it to install other utilites. To uninstall them you need to use the Control Panel.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-190229.png" alt="Screenshot-2022-07-10-190229" width="600" height="400" loading="lazy"></p>
<p><em>pgAgent jobs</em> will be visible to you in the browser tree on the left side of the dashboard. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-152307-1.png" alt="Screenshot-2022-07-10-152307-1" width="600" height="400" loading="lazy"></p>
<p>Above you can see a close up view of the browser tree.</p>
<h1 id="heading-how-to-create-a-job-in-pgagent">How to Create a Job in pgAgent</h1>
<p>To create a new job, right click on the <em>pgAgent Jobs</em> button and click on <em>create</em>. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-152329-1.png" alt="Screenshot-2022-07-10-152329-1" width="600" height="400" loading="lazy"></p>
<p>You will see a menu, and there just click <em>create</em> &gt; <em>pgAgent Job</em>.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-191902.png" alt="Screenshot-2022-07-10-191902" width="600" height="400" loading="lazy"></p>
<p>The <em>create pgAgent dialog box</em> has four tabs. </p>
<p>The first one is <em>General</em> tab. Here you enter the name of the job and select a category. </p>
<p><em>Category</em> is just for internal categorization purposes – this does not affect how your job runs. You can select one based on the function of the job. Since I want to export the data to a CSV, I will pick the <em>Data Export</em> category.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-152531-1.png" alt="Screenshot-2022-07-10-152531-1" width="600" height="400" loading="lazy"></p>
<p>Next we click on the <em>Steps</em> tab in the <em>create pgAgent</em> dialog box. In the top right corner of the box you will see a + sign. Click on it to add a new row.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-153345-1.png" alt="Screenshot-2022-07-10-153345-1" width="600" height="400" loading="lazy"></p>
<p>The <em>Steps</em> tab has two sections: <em>General</em> and <em>Code</em>.</p>
<p>In the <em>General</em> tab: </p>
<p>1) Add the name of the step.
2) Next, you <em>Enable</em> or <em>Disable</em> the step. Your job will run only if the step is enabled.
3) Depending on whether your job is <em>local</em> or <em>remote</em>, you can pick the <em>Connection type</em>. I will choose a remote connection.
4) A remote connection allows you to manually add the Connection String. The syntax should be like in <a target="_blank" href="https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING">libq connection string</a>. I will add my connection details in the same format: <code>host=localhost port=5432 dbname=postgres</code>
5) In the <em>On Error</em> select box, you can pick what should be happen in case an error occurs. I have selected for the job to fail.
6) Finally, you  can comment on the step. Then save the changes.</p>
<p>Next comes the <em>code</em> section in <em>Steps</em> tab.
<img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-155158-1.png" alt="Screenshot-2022-07-10-155158-1" width="600" height="400" loading="lazy"></p>
<p>Since I want to export the data from a view, I will call the view and ask it to export the file. The code will be:
<code>COPY (select * from acc_view) TO E'C:\\test-data\\try.csv';</code></p>
<p>I will save the changes after adding the code.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-153614-1.png" alt="Screenshot-2022-07-10-153614-1" width="600" height="400" loading="lazy"></p>
<p>We are now ready to schedule a job. In the <em>Schedules</em> tab we add the <em>start date time</em> and the <em>end date time</em> for the job to start and end. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-163332-1.png" alt="Screenshot-2022-07-10-163332-1" width="600" height="400" loading="lazy"></p>
<p><em>SQL</em> is the last tab. It shows the code generated by the GUI. If you want to schedule a job dynamically you will have to execute the procedure code displayed here.</p>
<h1 id="heading-how-to-view-created-jobs-in-pgagent">How to View Created Jobs in pgAgent</h1>
<p>Once a new job is created, it will be displayed under <em>pgAgent jobs</em> in the browser tree.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-163417-1.png" alt="Screenshot-2022-07-10-163417-1" width="600" height="400" loading="lazy"></p>
<p>Its <em>schedules</em> and <em>steps</em> will be displayed when you extend the job.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-163540-1.png" alt="Screenshot-2022-07-10-163540-1" width="600" height="400" loading="lazy"></p>
<p>To see whether the job was executed (whether it failed or succeeded), you select the job by its name and click on the <em>Statistics</em> tab in the dashboard. Here you can view the number of times the job was executed, start and end time, its status and id. <em>s</em> means success and <em>f</em> means failed in the <em>Status</em> column.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-163639-1.png" alt="Screenshot-2022-07-10-163639-1" width="600" height="400" loading="lazy"></p>
<p>To debug why a job failed, you can simply click on the name of the step under <em>Steps</em> in the browser tree and click <em>Statistics</em> on the dashboard. In the <em>output</em>  column you can see why the job failed. </p>
<p>In my case it wasn't able to access the directory I was trying to copy the data to. Once I changed the path, my job was successfully executed (note the first row).</p>
<h1 id="heading-how-to-edit-jobs-in-pgagent">How to Edit Jobs in pgAgent</h1>
<p>To edit a job in pgAgent you select the job and click on the <em>Properties</em> tab on the dashboard.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/07/Screenshot-2022-07-10-201542.png" alt="Screenshot-2022-07-10-201542" width="600" height="400" loading="lazy"></p>
<p>Click on the pencil icon in the top left corner, it will open a wizard where you can edit all the details. </p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>It is not always feasible to create schedulers in your code, but when it's an option, it can be really helpful. </p>
<p>Job Scheduling coupled with exporting data in CSV format is a powerful feature of PostgreSQL. I will try to explain how to create a job dynamically in the next tutorial. Happy learning.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Manage PostgreSQL Databases from the Command Line with psql ]]>
                </title>
                <description>
                    <![CDATA[ Now is a great time to learn relational databases and SQL. From web development to data science, they are used everywhere. In the Stack Overflow 2021 Survey, 4 out of the top 5 database technologies used by professional developers were relational dat... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/manage-postgresql-with-psql/</link>
                <guid isPermaLink="false">66d45ee547a8245f78752a38</guid>
                
                    <category>
                        <![CDATA[ command line ]]>
                    </category>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                    <category>
                        <![CDATA[ terminal ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Gerard Hynes ]]>
                </dc:creator>
                <pubDate>Tue, 07 Jun 2022 15:29:11 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/05/manage-postgreSQL-with-psql.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Now is a great time to learn relational databases and SQL. From web development to data science, they are used everywhere.</p>
<p>In the <a target="_blank" href="https://insights.stackoverflow.com/survey/2021#most-popular-technologies-database-prof">Stack Overflow 2021 Survey</a>, 4 out of the top 5 database technologies used by professional developers were relational database management systems.</p>
<p>PostgreSQL is an excellent choice as a first relational database management system to learn.</p>
<ol>
<li><p>It’s widely used in industry, including at <a target="_blank" href="https://stackshare.io/postgresql">Uber, Netflix, Instagram, Spotify, and Twitch</a>.</p>
</li>
<li><p>It’s open source, so you won’t be locked into a particular vendor.</p>
</li>
<li><p>It's more than 25 years old, and in that time it has earned a reputation for stability and reliability.</p>
</li>
</ol>
<p>Whether you’re learning from the freeCodeCamp <a target="_blank" href="https://www.freecodecamp.org/learn/relational-database/">Relational Database Certification</a> or trying out PostgreSQL on your own computer, you need a way to create and manage databases, insert data into them, and query data from them.</p>
<p>While there are several graphical applications for interacting with PostgreSQL, using psql and the command line is probably the most direct way to communicate with your database.</p>
<h2 id="heading-what-is-psql">What is psql?</h2>
<p>psql is a tool that lets you interact with PostgreSQL databases through a terminal interface. When you install PostgreSQL on a machine, psql is automatically included.</p>
<p>psql lets you write SQL queries, send them to PostgreSQL, and view the results. It also lets you use meta-commands (which start with a backslash) for administering the databases. You can even write scripts and automate tasks relating to your databases.</p>
<p>Now, running a database on your local computer and using the command line can seem intimidating at first. I’m here to tell you it’s really not so bad. This guide will teach you the basics of managing PostgreSQL databases from the command line, including how to create, manage, back up, and restore databases.</p>
<h2 id="heading-prerequisite-install-postgresql">Prerequisite – Install PostgreSQL</h2>
<p>If you haven’t already installed PostgreSQL on your computer, follow the instructions for your operating system on the <a target="_blank" href="https://www.postgresql.org/download/">official PostgreSQL documentation</a>.</p>
<p>When you install PostgreSQL, you will be asked for a password. Keep this in a safe place as you’ll need it to connect to any databases you create.</p>
<h2 id="heading-how-to-connect-to-a-database">How to Connect to a Database</h2>
<p>You have two options when using psql to connect to a database: you can connect via the command line or by using the psql application. Both provide pretty much the same experience.</p>
<h3 id="heading-option-1-connect-to-a-database-with-the-command-line">Option 1 – Connect to a database with the command line</h3>
<p>Open a terminal. You can make sure psql is installed by typing <code>psql --version</code>. You should see <code>psql (PostgreSQL) version_number</code>, where <code>version_number</code> is the version of PostgreSQL that’s installed on your machine. In my case, it's 14.1.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-4.png" alt="Checking psql version via the command line" width="600" height="400" loading="lazy"></p>
<p><em>Checking psql version via the command line</em></p>
<p>The pattern for connecting to a database is:</p>
<pre><code class="lang-python">psql -d database_name -U username
</code></pre>
<p>The <code>-d</code> flag is shorter alternative for <code>--dbname</code> while <code>-U</code> is an alternative for <code>--username</code>.</p>
<p>When you installed PostgreSQL, a default database and user were created, both called <code>postgres</code>. So enter <code>psql -d postgres -U postgres</code> to connect to the <code>postgres</code> database as the <code>postgres</code> superuser.</p>
<pre><code class="lang-python">psql -d postgres -U postgres
</code></pre>
<p>You will be prompted for a password. Enter the password you chose when you installed PostgreSQL on your computer. Your terminal prompt will change to show that you’re now connected to the <code>postgres</code> database.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-5.png" alt="Connecting to a database from the command line with psql" width="600" height="400" loading="lazy"></p>
<p><em>Connecting to a database from the command line with psql</em></p>
<p>If you want to directly connect to a database as yourself (rather than as the <code>postgres</code> superuser), enter your system username as the username value.</p>
<h3 id="heading-option-2-connect-to-a-database-with-the-psql-application">Option 2 – Connect to a database with the psql application</h3>
<p>Launch the psql application – it'll be called "SQL Shell (psql)". You will be prompted for a server, a database, a port and a username. You can just press enter to select the default values, which are <code>localhost</code>, <code>postgres</code>, <code>5432</code>, and <code>postgres</code>.</p>
<p>Next, you’ll be prompted for the password you chose when you installed PostgreSQL. Once you enter this, your terminal prompt will change to show that you’re connected to the <code>postgres</code> database.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-2.png" alt="Connecting to a database with the psql application" width="600" height="400" loading="lazy"></p>
<p><em>Connecting to a database with the psql application</em></p>
<p><strong>Note:</strong> If you’re on Windows you might see a warning like “Console code page (850) differs from Windows code page (1252) 8-bit characters might not work correctly. See psql reference page 'Notes for Windows users' for details.” You don’t need to worry about this at this stage. If you want to read more about it, see the <a target="_blank" href="https://www.postgresql.org/docs/current/app-psql.html">psql documentation</a>.</p>
<h2 id="heading-how-to-get-help-in-psql">How to Get Help in psql</h2>
<p>To see a list of all psql meta-commands, and a brief summary of what they do, use the <code>\?</code> command.</p>
<pre><code class="lang-python">\?
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-6.png" alt="psql's help command" width="600" height="400" loading="lazy"></p>
<p><em>psql's help command</em></p>
<p>If you want help with a PostgreSQL command, use <code>\h</code> or <code>\help</code> and the command.</p>
<pre><code class="lang-python">\h COMMAND
</code></pre>
<p>This will give you a description of the command, its syntax (with optional parts in square brackets), and a URL for the relevant part of the PostgreSQL documentation.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-7.png" alt="psql describing the DROP TABLE statement" width="600" height="400" loading="lazy"></p>
<p><em>psql describing the DROP TABLE statement</em></p>
<h2 id="heading-how-to-quit-a-command-in-psql">How to Quit a Command in psql</h2>
<p>If you’ve run a command that’s taking a long time or printing too much information to the console, you can quit it by typing <code>q</code>.</p>
<pre><code class="lang-python">q
</code></pre>
<h2 id="heading-how-to-create-a-database">How to Create a Database</h2>
<p>Before you can manage any databases, you’ll need to create one.</p>
<p><strong>Note:</strong> SQL commands should end with a semicolon, while meta-commands (which start with a backslash) don’t need to.</p>
<p>The SQL command to create a database is:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> database_name;
</code></pre>
<p>For this guide, we’re going to be working with book data, so let’s create a database called <code>books_db</code>.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> books_db;
</code></pre>
<h2 id="heading-how-to-list-databases">How to List Databases</h2>
<p>You can view a list of all available databases with the list command.</p>
<pre><code class="lang-python">\l
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-8.png" alt="Listing all databases" width="600" height="400" loading="lazy"></p>
<p><em>Listing all databases</em></p>
<p>You should see <code>books_db</code>, as well as <code>postgres</code>, <code>template0</code>, and <code>template1</code>. (The <code>CREATE DATABASE</code> command actually works by copying the standard database, called <code>template1</code>. You can read more about this in the <a target="_blank" href="https://www.postgresql.org/docs/current/manage-ag-templatedbs.html">PostgreSQL documentation</a>.)</p>
<p>Using <code>\l+</code> will display additional information, such as the size of the databases and their tablespaces (the location in the filesystem where the files representing the database will be stored).</p>
<pre><code class="lang-python">\l+
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-22.png" alt="Listing all databases with additional information" width="600" height="400" loading="lazy"></p>
<p><em>Listing all databases with additional information</em></p>
<h2 id="heading-how-to-switch-databases">How to Switch Databases</h2>
<p>You’re currently still connected to the default <code>postgres</code> database. To connect to a database or to switch between databases, use the <code>\c</code> command.</p>
<pre><code class="lang-python">\c database_name
</code></pre>
<p>So <code>\c books_db</code> will connect you to the <code>books_db</code> database. Note that your terminal prompt changes to reflect the database you’re currently connected to.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-9.png" alt="Switching databases" width="600" height="400" loading="lazy"></p>
<p><em>Switching databases</em></p>
<h2 id="heading-how-to-delete-a-database">How to Delete a Database</h2>
<p>If you want to delete a database, use the <code>DROP DATABASE</code> command.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">DATABASE</span> database_name;
</code></pre>
<p>You will only be allowed to delete a database if you are a superuser, such as <code>postgres</code>, or if you are the database’s owner.</p>
<p>If you try to delete a database that doesn’t exist, you will get an error. Use <code>IF EXISTS</code> to get a notice instead.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">DATABASE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">EXISTS</span> database_name;
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-10.png" alt="Deleting a database" width="600" height="400" loading="lazy"></p>
<p><em>Deleting a database</em></p>
<p>You can’t delete a database that has active connections. So if you want to delete the database you are currently connected to, you’ll need to switch to another database.</p>
<h2 id="heading-how-to-create-tables">How to Create Tables</h2>
<p>Before we can manage tables, we need to create a few and populate them with some sample data.</p>
<p>The command to create a table is:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> table_name();
</code></pre>
<p>This will create an empty table. You can also pass column values into the parentheses to create a table with columns. At the very least, a basic table should have a Primary Key (a unique identifier to tell each row apart) and a column with some data in it.</p>
<p>For our <code>books_db</code>, we’ll create a table for authors and another for books. For authors, we’ll record their first name and last name. For books, we’ll record the title and the year they were published.</p>
<p>We’ll make sure that the authors’ <code>first_name</code> and <code>last_name</code> and the books’ <code>title</code> aren’t null, since this is pretty vital information to know about them. To do this we include the <code>NOT NULL</code> constraint.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">authors</span>(
    author_id <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>, 
    first_name <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>, 
    last_name <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</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> books(
    book_id <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>, 
    title <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</span>) <span class="hljs-keyword">NOT</span> <span class="hljs-literal">NULL</span>, 
    published_year <span class="hljs-built_in">INT</span>
);
</code></pre>
<p>You will see <code>CREATE TABLE</code> printed to the terminal if the table was created successfully.</p>
<p>Now let's connect the two tables by adding a Foreign Key to books. Foreign Keys are unique identifiers that reference the Primary Key of another table. Books can, of course, have multiple authors but we’re not going to get into the complexities of many to many relationships right now.</p>
<p>Add a Foreign Key to <code>books</code> with the following command:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> books <span class="hljs-keyword">ADD</span> <span class="hljs-keyword">COLUMN</span> author_id <span class="hljs-built_in">INT</span> <span class="hljs-keyword">REFERENCES</span> <span class="hljs-keyword">authors</span>(author_id);
</code></pre>
<p>Next, let’s insert some sample data into the tables. We’ll start with <code>authors</code>.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> <span class="hljs-keyword">authors</span> (first_name, last_name) 
<span class="hljs-keyword">VALUES</span> (‘Tamsyn’, ‘Muir’), (‘Ann’, ‘Leckie’), (‘Zen’, ‘Cho’);
</code></pre>
<p>Select everything from <code>authors</code> to make sure the insert command worked.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">authors</span>;
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-13.png" alt="Querying all data from the authors table" width="600" height="400" loading="lazy"></p>
<p><em>Querying all data from the authors table</em></p>
<p>Next, we’ll insert some books data into <code>books</code>.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">INSERT</span> <span class="hljs-keyword">INTO</span> books(title, published_year, author_id) 
<span class="hljs-keyword">VALUES</span> (‘Gideon the Ninth’, <span class="hljs-number">2019</span>, <span class="hljs-number">1</span>), (‘<span class="hljs-keyword">Ancillary</span> Justice’, <span class="hljs-number">2013</span>, <span class="hljs-number">2</span>), (‘Black Water Sister’, <span class="hljs-number">2021</span>, <span class="hljs-number">3</span>);
</code></pre>
<p>If you run <code>SELECT * FROM books;</code> you’ll see the book data.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-14.png" alt="Querying all data from the books table" width="600" height="400" loading="lazy"></p>
<p><em>Querying all data from the books table</em></p>
<h2 id="heading-how-to-list-all-tables">How to List All Tables</h2>
<p>You can use the <code>\dt</code> command to list all the tables in a database.</p>
<pre><code class="lang-python">\dt
</code></pre>
<p>For <code>books_db</code> you will see <code>books</code> and <code>authors</code>. You'll also see <code>books_book_id_seq</code> and <code>authors_author_id_seq</code>. These keep track of the sequence of integers used as ids by the tables because we used <code>SERIAL</code> to generate their Primary Keys.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-15.png" alt="Listing all tables in a database" width="600" height="400" loading="lazy"></p>
<p><em>Listing all tables in a database</em></p>
<h2 id="heading-how-to-describe-a-table">How to Describe a Table</h2>
<p>To see more information about a particular table, you can use the describe table command: <code>\d table_name</code>. This will list the columns, indexes, and any references to other tables.</p>
<pre><code class="lang-python">\d table_name
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-25.png" alt="Describing the authors table" width="600" height="400" loading="lazy"></p>
<p><em>Describing the authors table</em></p>
<p>Using <code>\dt+ table_name</code> will provide more information, such as about storage and compression.</p>
<h2 id="heading-how-to-rename-a-table">How to Rename a Table</h2>
<p>If you ever need to change the name of a table, you can rename it with the <code>ALTER TABLE</code> command.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">ALTER</span> <span class="hljs-keyword">TABLE</span> table_name <span class="hljs-keyword">RENAME</span> <span class="hljs-keyword">TO</span> new_table_name;
</code></pre>
<h2 id="heading-how-to-delete-a-table">How to Delete a Table</h2>
<p>If you want to delete a table, you can use the <code>DROP TABLE</code> command.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> table_name;
</code></pre>
<p>If you try to delete a table that doesn’t exist, you will get an error. You can avoid this by including the <code>IF EXISTS</code> option in the statement. This way you’ll get a notice instead.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">DROP</span> <span class="hljs-keyword">TABLE</span> <span class="hljs-keyword">IF</span> <span class="hljs-keyword">EXISTS</span> table_name;
</code></pre>
<h2 id="heading-how-to-manage-longer-commands-and-queries">How to Manage Longer Commands and Queries</h2>
<p>If you’re writing longer SQL queries, the command line isn’t the most ergonomic way to do it. It's probably better to write your SQL in a file and then have psql execute it.</p>
<p>If you are working with psql and think your next query will be long, you can open a text editor from psql and write it there. If you have an existing query, or maybe want to run several queries to load sample data, you can execute commands from a file that is already written.</p>
<h3 id="heading-option-1-open-a-text-editor-from-psql">Option 1 – Open a text editor from psql</h3>
<p>If you enter the <code>\e</code> command, psql will open a text editor. When you save and close the editor, psql will run the command you just wrote.</p>
<pre><code class="lang-python">\e
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-30.png" alt="Writing commands in a text editor" width="600" height="400" loading="lazy"></p>
<p><em>Writing commands in a text editor</em></p>
<p>On Windows, the default text editor for psql is Notepad, while on MacOs and Linux it's vi. You can change this to another editor by setting the <code>EDITOR</code> value in your computer’s environment variables.</p>
<h3 id="heading-option-2-execute-commands-and-queries-from-a-file">Option 2 – Execute commands and queries from a file</h3>
<p>If you have particularly long commands or multiple commands that you want to run, it would be better to write the SQL in a file ahead of time and have psql execute that file once you’re ready.</p>
<p>The <code>\i</code> command lets you read input from a file as if you had typed it into the terminal.</p>
<pre><code class="lang-python">\i path_to_file/file_name.sql
</code></pre>
<p><strong>Note:</strong> If you're executing this command on Windows, you still need to use forward slashes in the file path.</p>
<p>If you don’t specify a path, psql will look for the file in the last directory that you were in before you connected to PostgreSQL.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-29.png" alt="Executing SQL commands from a file" width="600" height="400" loading="lazy"></p>
<p><em>Executing SQL commands from a file</em></p>
<h2 id="heading-how-to-time-queries">How to Time Queries</h2>
<p>If you want to see how long your queries are taking, you can turn on query execution timing.</p>
<pre><code class="lang-python">\timing
</code></pre>
<p>This will display in milliseconds the time that the query took to complete.</p>
<p>If you run the <code>\timing</code> command again, it will turn off query execution timing.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-19.png" alt="Using query execution timing" width="600" height="400" loading="lazy"></p>
<p><em>Using query execution timing</em></p>
<h2 id="heading-how-to-import-data-from-a-csv-file">How to Import Data from a CSV File</h2>
<p>If you have a CSV file with data and you want to load this into a PostgreSQL database, you can do this from the command line with psql.</p>
<p>First, create a CSV file called <code>films.csv</code> with the following structure (It doesn’t matter if you use Excel, Google Sheets, Numbers, or any other program).</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-21.png" alt="A spreadsheet with Pixar film data" width="600" height="400" loading="lazy"></p>
<p><em>A spreadsheet with Pixar film data</em></p>
<p>Open psql and create a <code>films_db</code> database, connect to it, and create a <code>films</code> table.</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> films_db;

\c films_db

<span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">TABLE</span> films(
    <span class="hljs-keyword">id</span> <span class="hljs-built_in">SERIAL</span> PRIMARY <span class="hljs-keyword">KEY</span>,
    title <span class="hljs-built_in">VARCHAR</span>(<span class="hljs-number">100</span>),
    <span class="hljs-keyword">year</span> <span class="hljs-built_in">INT</span>,
    running_time <span class="hljs-built_in">INT</span>
);
</code></pre>
<p>You can then use the <code>\copy</code> command to import the CSV file into <code>films</code>. You need to provide an absolute path to where the CSV file is on your computer.</p>
<pre><code class="lang-python">\copy films(title, year, running_time) FROM <span class="hljs-string">'path_to_file'</span> DELIMITER ‘,’ CSV HEADER;
</code></pre>
<p>The <code>DELIMITER</code> option specifies the character that separates the columns in each row of the file being imported, <code>CSV</code> specifies that it is a CSV file, and <code>HEADER</code> specifies that the file contains a header line with the names of the columns.</p>
<p><strong>Note:</strong> The column names of the <code>films</code> table don't need to match the column names of <code>films.csv</code> but they do need to be in the same order.</p>
<p>Use <code>SELECT * FROM films;</code> to see if the process was successful.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-31.png" alt="Importing data from a .csv file." width="600" height="400" loading="lazy"></p>
<p><em>Importing data from a .csv file</em></p>
<h2 id="heading-how-to-back-up-a-database-with-pgdump">How to Back Up a Database with <code>pg_dump</code></h2>
<p>If you need to backup a database, <code>pg_dump</code> is a utility that lets you extract a database into a SQL script file or other type of archive file.</p>
<p>First, on the command line (not in psql), navigate to the PostgreSQL <code>bin</code> folder.</p>
<pre><code class="lang-python">cd <span class="hljs-string">"C:\Program Files\PostgreSQL\14\bin"</span>
</code></pre>
<p>Then run the following command, using <code>postgres</code> as the username, and filling in the database and output file that you want to use.</p>
<pre><code class="lang-python">pg_dump -U username database_name &gt; path_to_file/filename.sql
</code></pre>
<p>Use <code>postgres</code> for the username and you will be prompted for the <code>postgres</code> superuser's password. <code>pg_dump</code> will then create a <code>.sql</code> file containing the SQL commands needed to recreate the database.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-60.png" alt="Backing up a database to a .sql file." width="600" height="400" loading="lazy"></p>
<p><em>Backing up a database to a .sql file</em></p>
<p>If you don’t specify a path for the output file, <code>pg_dump</code> will save the file in the last directory that you were in before you connected to PostgreSQL.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-45.png" alt="Contents of films.sql backup file" width="600" height="400" loading="lazy"></p>
<p><em>Contents of films.sql backup file</em></p>
<p>You can pass the <code>-v</code> or <code>--verbose</code> flag to <code>pg_dump</code> to see what <code>pg_dump</code> is doing at each step.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-61.png" alt="Running pg_dump in verbose mode." width="600" height="400" loading="lazy"></p>
<p><em>Running pg_dump in verbose mode</em></p>
<p>You can also backup a database to other file formats, such as <code>.tar</code> (an archive format).</p>
<pre><code class="lang-python">pg_dump -U username -F t database_name &gt; path_to_file/filename.tar
</code></pre>
<p>Here the <code>-F</code> flag tells <code>pg_dump</code> that you're going to specify an output format, while <code>t</code> tells it it's going to be in the <code>.tar</code> format.</p>
<h2 id="heading-how-to-restore-a-database">How to Restore a Database</h2>
<p>You can restore a database from a backup file using either psql or the <code>pg_restore</code> utility. Which one you choose depends on the type of file you are restoring the database from.</p>
<ol>
<li><p>If you backed up the database to a plaintext format, such as <code>.sql</code>, use psql.</p>
</li>
<li><p>If you backed up the database to an archive format, such as <code>.tar</code>, use <code>pg_restore</code>.</p>
</li>
</ol>
<h3 id="heading-option-1-restore-a-database-using-psql">Option 1 – Restore a database using psql</h3>
<p>To restore a database from a <code>.sql</code> file, on the command line (so not in psql), use <code>psql -U username -d database_name -f filename.sql</code>.</p>
<p>You can use the <code>films_db</code> database and <code>films.sql</code> file you used earlier, or create a new backup file.</p>
<p>Create an empty database for the file to restore the data into. If you're using <code>films.sql</code> to restore <code>films_db</code>, the easiest thing might be to delete <code>films_db</code> and recreate it.</p>
<pre><code class="lang-python">DROP DATABASE films_db;

CREATE DATABASE films_db;
</code></pre>
<p>In a separate terminal (not in psql), run the following command, passing in <code>postgres</code> as the username, and the names of the database and backup file you are using.</p>
<pre><code class="lang-python">psql -U username -d database_name -f path_to_file/filename.sql
</code></pre>
<p>The <code>-d</code> flag points psql to a specific database, while the <code>-f</code> flag tells psql to read from the specified file.</p>
<p>If you don’t specify a path for the backup file, psql will look for the file in the last directory that you were in before you connected to PostgreSQL.</p>
<p>You will be prompted for the <code>postgres</code> superuser's password and then will see a series of commands get printed to the command line while psql recreates the database.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-52.png" alt="Restoring a database using psql." width="600" height="400" loading="lazy"></p>
<p><em>Restoring a database using psql</em></p>
<p>This command ignores any errors that occur during the restore. If you want to stop restoring the database if an error occurs, pass in <code>--set ON_ERROR_STOP=on</code>.</p>
<pre><code class="lang-python">psql -U username -d database_name --set ON_ERROR_STOP=on -f filename.sql
</code></pre>
<h3 id="heading-option-2-restore-a-database-using-pgrestore">Option 2 – Restore a database using <code>pg_restore</code></h3>
<p>To restore a database using <code>pg_restore</code>, use <code>pg_restore -U username -d database_name path_to_file/filename.tar</code>.</p>
<p>Create an empty database for the file to restore the data into. If you're restoring <code>films_db</code> from a <code>films.tar</code> file, the easiest thing might be to delete <code>films_db</code> and recreate it.</p>
<pre><code class="lang-python">DROP DATABASE films_db;

CREATE DATABASE films_db;
</code></pre>
<p>On the command line (not in psql), run the following command, passing in <code>postgres</code> as the username, and the names of the database and backup file you are using.</p>
<pre><code class="lang-python">pg_restore -U username -d database_name path_to_file/filename.tar
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-54.png" alt="Restoring a database using pg_restore" width="600" height="400" loading="lazy"></p>
<p><em>Restoring a database using pg_restore</em></p>
<p>You can also pass in the <code>-v</code> or <code>--verbose</code> flag to see what <code>pg_restore</code> is doing at each step.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/06/image-55.png" alt="Using pg_restore in verbose mode" width="600" height="400" loading="lazy"></p>
<p><em>Using pg_restore in verbose mode</em></p>
<h2 id="heading-how-to-quit-psql">How to Quit psql</h2>
<p>If you’ve finished with psql and want to exit from it, enter <code>quit</code> or <code>\q</code>.</p>
<pre><code class="lang-python">\q
</code></pre>
<p>This will close the psql application if you were using it, or return you to your regular command prompt if you were using psql from the command line.</p>
<h2 id="heading-where-to-take-it-from-here">Where to Take it from Here</h2>
<p>There are lots more things you can do with psql, such as managing schemas, roles, and tablespaces. But this guide should be enough to get you started with managing PostgreSQL databases from the command line.</p>
<p>If you want to learn more about PostgreSQL and psql, you could try out freeCodeCamp’s <a target="_blank" href="https://www.freecodecamp.org/learn/relational-database/">Relational Database Certificate</a> . The official <a target="_blank" href="https://www.postgresql.org/docs/current/">PostgreSQL documentation</a> is comprehensive, and <a target="_blank" href="https://www.postgresqltutorial.com/postgresql-administration/psql-commands/">PostgreSQL Tutorial</a> offers several in-depth tutorials.</p>
<p>I hope you find this guide helpful as you continue to learn about PostgreSQL and relational databases.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Join MySQL and Postgres in a Live Materialized View ]]>
                </title>
                <description>
                    <![CDATA[ By Bobby Iliev When you're working on a project that consists of a lot of microservices, it'll likely also include multiple databases.  For example, you might have a MySQL database and a PostgreSQL database, both running on separate servers. Usually,... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-join-mysql-and-postgres-in-a-live-materialized-view/</link>
                <guid isPermaLink="false">66d45dd5182810487e0ce0f1</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Microservices ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MySQL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ postgres ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 03 May 2022 15:18:26 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2022/04/how-to-join-mysql-and-postgres-in-a-live-materialized-view2.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Bobby Iliev</p>
<p>When you're working on a project that consists of a lot of microservices, it'll likely also include multiple databases. </p>
<p>For example, you might have a <a target="_blank" href="https://www.mysql.com">MySQL database</a> and a <a target="_blank" href="https://www.postgresql.org">PostgreSQL database</a>, both running on separate servers.</p>
<p>Usually, to join the data from the two databases, you would have to introduce a new microservice that would join the data together. But this would increase the complexity of the system.</p>
<p>In this tutorial, we will be using Materialize to join MySQL and Postgres in a live materialized view. We'll then be able to query that directly and get results back from both databases in real-time using standard SQL.  </p>
<p><a target="_blank" href="https://github.com/MaterializeInc/materialize/">Materialize</a> is a source-available streaming database written in Rust that maintains the results of a SQL query (a materialized view) in memory as the data changes. </p>
<p>The tutorial includes a demo project which you can start using <code>docker-compose</code>.</p>
<p>The demo project that we are going to use will monitor the orders on our mock website. It'll generate events that could, later on, be used to send notifications when a cart has been abandoned for a long time.</p>
<p>The architecture of the demo project is as follows:</p>
<p><img src="https://user-images.githubusercontent.com/21223421/143267063-2dbb1ec2-d48d-4ba5-8da8-f0d9ac1404e4.png" alt="mz-abandoned-cart-demo" width="600" height="400" loading="lazy"></p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>All of the services that we will be using in the demo will run inside Docker containers, that way you will not have to install any additional services on your laptop or server rather than Docker and Docker Compose.  </p>
<p>In case that you don't have Docker and Docker Compose already installed, you can follow the official instructions on how to do that here:</p>
<ul>
<li><a target="_blank" href="https://docs.docker.com/get-docker/">Install Docker</a></li>
<li><a target="_blank" href="https://docs.docker.com/compose/install/">Install Docker Compose</a></li>
</ul>
<h2 id="heading-overview">Overview</h2>
<p>As shown in the diagram above, we will have the following components:</p>
<ul>
<li>A mock service to continually generate orders.</li>
<li>The orders will be stored in a <strong>MySQL database</strong>.</li>
<li>As the database writes occur, <strong>Debezium</strong> streams the changes out of MySQL to a <strong>Redpanda</strong> topic.</li>
<li>We'll also have a <strong>Postgres</strong> database where we can get our users.</li>
<li>We'll then ingest this Redpanda topic into <strong>Materialize</strong> directly along with the users from the Postgres database.</li>
<li>In Materialize we'll join our orders and users together, do some filtering, and create a materialized view that shows the abandoned cart information.</li>
<li>We will then create a sink to send the abandoned cart data out to a new Redpanda topic.</li>
<li>At the end we will use <strong>Metabase</strong> to visualize the data.</li>
<li>You could, later on, use the information from that new topic to send out notifications to your users and remind them that they have an abandoned cart.</li>
</ul>
<p>As a side note here, you would be perfectly fine using Kafka instead of Redpanda. I just like the simplicity that Redpanda brings to the table, as you can run a single Redpanda instance instead of all of the Kafka components.</p>
<h2 id="heading-how-to-run-the-demo">How to Run the Demo</h2>
<p>First, start by cloning the repository:</p>
<pre><code>git clone https:<span class="hljs-comment">//github.com/bobbyiliev/materialize-tutorials.git</span>
</code></pre><p>After that you can access the directory:</p>
<pre><code>cd materialize-tutorials/mz-join-mysql-and-postgresql
</code></pre><p>Let's start by first running the Redpanda container:</p>
<pre><code>docker-compose up -d redpanda
</code></pre><p>Build the images:</p>
<pre><code>docker-compose build
</code></pre><p>Finally, start all of the services:</p>
<pre><code>docker-compose up -d
</code></pre><p>In order to Launch the Materialize CLI, you can run the following command:</p>
<pre><code>docker-compose run mzcli
</code></pre><p>This is just a shortcut to a Docker container with <code>postgres-client</code> pre-installed. If you already have <code>psql</code> you could run <code>psql -U materialize -h localhost -p 6875 materialize</code> instead.</p>
<h3 id="heading-how-to-create-a-materialize-kafka-source">How to Create a Materialize Kafka Source</h3>
<p>Now that you're in the Materialize CLI, let's define the <code>orders</code> tables in the <code>mysql.shop</code> database as Redpanda sources:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">SOURCE</span> orders
<span class="hljs-keyword">FROM</span> KAFKA BROKER <span class="hljs-string">'redpanda:9092'</span> TOPIC <span class="hljs-string">'mysql.shop.orders'</span>
<span class="hljs-keyword">FORMAT</span> AVRO <span class="hljs-keyword">USING</span> CONFLUENT <span class="hljs-keyword">SCHEMA</span> REGISTRY <span class="hljs-string">'http://redpanda:8081'</span>
ENVELOPE DEBEZIUM;
</code></pre>
<p>If you were to check the available columns from the <code>orders</code> source by running the following statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SHOW</span> <span class="hljs-keyword">COLUMNS</span> <span class="hljs-keyword">FROM</span> orders;
</code></pre>
<p>You would be able to see that, as Materialize is pulling the message schema data from the Redpanda registry, it knows the column types to use for each attribute:</p>
<pre><code class="lang-sql">    name      | nullable |   type
<span class="hljs-comment">--------------+----------+-----------</span>
 id           | f        | bigint
 user_id      | t        | bigint
 order_status | t        | integer
 price        | t        | numeric
 created_at   | f        | text
 updated_at   | t        | timestamp
</code></pre>
<h3 id="heading-how-to-create-materialized-views">How to Create Materialized Views</h3>
<p>Next, we will create our first Materialized View, to get all of the data from the <code>orders</code> Redpanda source:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MATERIALIZED</span> <span class="hljs-keyword">VIEW</span> orders_view <span class="hljs-keyword">AS</span>
<span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> orders;
</code></pre>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MATERIALIZED</span> <span class="hljs-keyword">VIEW</span> abandoned_orders <span class="hljs-keyword">AS</span>
    <span class="hljs-keyword">SELECT</span>
        user_id,
        order_status,
        <span class="hljs-keyword">SUM</span>(price) <span class="hljs-keyword">as</span> revenue,
        <span class="hljs-keyword">COUNT</span>(<span class="hljs-keyword">id</span>) <span class="hljs-keyword">AS</span> total
    <span class="hljs-keyword">FROM</span> orders_view
    <span class="hljs-keyword">WHERE</span> order_status=<span class="hljs-number">0</span>
    <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-number">1</span>,<span class="hljs-number">2</span>;
</code></pre>
<p>You can now use <code>SELECT * FROM abandoned_orders;</code> to see the results:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> abandoned_orders;
</code></pre>
<p>For more information on creating materialized views, check out the <a target="_blank" href="https://materialize.com/docs/sql/create-materialized-view/">Materialized Views</a> section of the Materialize documentation.</p>
<h3 id="heading-how-to-create-a-postgres-source">How to Create a Postgres Source</h3>
<p>There are two ways to create a Postgres source in Materialize:</p>
<ul>
<li>Using Debezium just like we did with the MySQL source.</li>
<li>Using the Postgres Materialize Source, which allows you to connect Materialize direct to Postgres so you don't have to use Debezium.</li>
</ul>
<p>For this demo, we will use the Postgres Materialize Source just as a demonstration on how to use it, but feel free to use Debezium instead.</p>
<p>To create a Postgres Materialize Source run the following statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MATERIALIZED</span> <span class="hljs-keyword">SOURCE</span> <span class="hljs-string">"mz_source"</span> <span class="hljs-keyword">FROM</span> POSTGRES
<span class="hljs-keyword">CONNECTION</span> <span class="hljs-string">'user=postgres port=5432 host=postgres dbname=postgres password=postgres'</span>
PUBLICATION <span class="hljs-string">'mz_source'</span>;
</code></pre>
<p>A quick rundown of the above statement:</p>
<ul>
<li><code>MATERIALIZED</code>: Materializes the PostgreSQL source’s data. All of the data is retained in memory and makes sources directly selectable.</li>
<li><code>mz_source</code>: The name for the PostgreSQL source.</li>
<li><code>CONNECTION</code>: The PostgreSQL connection parameters.</li>
<li><code>PUBLICATION</code>: The PostgreSQL publication, containing the tables to be streamed to Materialize.</li>
</ul>
<p>Once we've created the PostgreSQL source, in order to be able to query the PostgreSQL tables, we would need to create views that represent the upstream publication’s original tables. </p>
<p>In our case, we only have one table called <code>users</code> so the statement that we would need to run is:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> VIEWS <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">SOURCE</span> mz_source (<span class="hljs-keyword">users</span>);
</code></pre>
<p>To see the available views execute the following statement:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SHOW</span> <span class="hljs-keyword">FULL</span> VIEWS;
</code></pre>
<p>Once that is done, you can query the new views directly:</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>Next, let's go ahead and create a few more views.</p>
<h3 id="heading-how-to-create-a-kafka-sink">How to Create a Kafka Sink</h3>
<p><a target="_blank" href="https://materialize.com/docs/sql/create-sink/">Sinks</a> let you send data from Materialize to an external source.</p>
<p>For this Demo, we will be using <a target="_blank" href="https://materialize.com/docs/third-party/redpanda/">Redpanda</a>.</p>
<p>Redpanda is Kafka API-compatible and Materialize can process data from it just as it would process data from a Kafka source.</p>
<p>Let's create a materialized view, that will hold all of the high volume unpaid orders:</p>
<pre><code class="lang-sql"> <span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MATERIALIZED</span> <span class="hljs-keyword">VIEW</span> high_value_orders <span class="hljs-keyword">AS</span>
      <span class="hljs-keyword">SELECT</span>
        users.id,
        users.email,
        abandoned_orders.revenue,
        abandoned_orders.total
      <span class="hljs-keyword">FROM</span> <span class="hljs-keyword">users</span>
      <span class="hljs-keyword">JOIN</span> abandoned_orders <span class="hljs-keyword">ON</span> abandoned_orders.user_id = users.id
      <span class="hljs-keyword">GROUP</span> <span class="hljs-keyword">BY</span> <span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>
      <span class="hljs-keyword">HAVING</span> revenue &gt; <span class="hljs-number">2000</span>;
</code></pre>
<p>As you can see, here we are actually joining the <code>users</code> view which is ingesting the data directly from our Postgres source, and the <code>abandond_orders</code> view which is ingesting the data from the Redpanda topic, together.</p>
<p>Let's create a Sink where we will send the data of the above materialized view:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> SINK high_value_orders_sink
    <span class="hljs-keyword">FROM</span> high_value_orders
    <span class="hljs-keyword">INTO</span> KAFKA BROKER <span class="hljs-string">'redpanda:9092'</span> TOPIC <span class="hljs-string">'high-value-orders-sink'</span>
    <span class="hljs-keyword">FORMAT</span> AVRO <span class="hljs-keyword">USING</span>
    CONFLUENT <span class="hljs-keyword">SCHEMA</span> REGISTRY <span class="hljs-string">'http://redpanda:8081'</span>;
</code></pre>
<p>Now if you were to connect to the Redpanda container and use the <code>rpk topic consume</code> command, you will be able to read the records from the topic.</p>
<p>However, as of the time being, we won’t be able to preview the results with <code>rpk</code> because it’s AVRO formatted. Redpanda would most likely implement this in the future, but for the moment, we can actually stream the topic back into Materialize to confirm the format.</p>
<p>First, get the name of the topic that has been automatically generated:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> topic <span class="hljs-keyword">FROM</span> mz_kafka_sinks;
</code></pre>
<p>Output:</p>
<pre><code class="lang-sql">                              topic
<span class="hljs-comment">-----------------------------------------------------------------</span>
 high-volume-orders-sink-u12-1637586945-13670686352905873426
</code></pre>
<p>For more information on how the topic names are generated check out the documentation <a target="_blank" href="https://materialize.com/docs/sql/create-sink/#kafka-sinks">here</a>.</p>
<p>Then create a new Materialized Source from this Redpanda topic:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">MATERIALIZED</span> <span class="hljs-keyword">SOURCE</span> high_volume_orders_test
<span class="hljs-keyword">FROM</span> KAFKA BROKER <span class="hljs-string">'redpanda:9092'</span> TOPIC <span class="hljs-string">' high-volume-orders-sink-u12-1637586945-13670686352905873426'</span>
<span class="hljs-keyword">FORMAT</span> AVRO <span class="hljs-keyword">USING</span> CONFLUENT <span class="hljs-keyword">SCHEMA</span> REGISTRY <span class="hljs-string">'http://redpanda:8081'</span>;
</code></pre>
<p>Make sure to change the topic name accordingly!</p>
<p>Finally, query this new materialized view:</p>
<pre><code class="lang-sql"><span class="hljs-keyword">SELECT</span> * <span class="hljs-keyword">FROM</span> high_volume_orders_test <span class="hljs-keyword">LIMIT</span> <span class="hljs-number">2</span>;
</code></pre>
<p>Now that you have the data in the topic, you can have other services connect to it and consume it and then trigger emails or alerts for example.</p>
<h2 id="heading-how-to-connect-metabase">How to Connect Metabase</h2>
<p>In order to access the <a target="_blank" href="https://materialize.com/docs/third-party/metabase/">Metabase</a> instance visit <code>http://localhost:3030</code> if you are running the demo locally or <code>http://your_server_ip:3030</code> if you are running the demo on a server. Then follow the steps to complete the Metabase setup.</p>
<p>Make sure to select Materialize as the source of the data.</p>
<p>Once ready you will be able to visualize your data just as you would with a standard PostgreSQL database.</p>
<h2 id="heading-how-to-stop-the-demo">How to Stop the Demo</h2>
<p>To stop all of the services, run the following command:</p>
<pre><code>docker-compose down
</code></pre><h2 id="heading-conclusion">Conclusion</h2>
<p>As you can see, this is a very simple example of how to use Materialize. You can use Materialize to ingest data from a variety of sources and then stream it to a variety of destinations.</p>
<h2 id="heading-helpful-resources">Helpful resources:</h2>
<ul>
<li><a target="_blank" href="https://materialize.com/docs/sql/create-source/postgres/"><code>CREATE SOURCE: PostgreSQL</code></a></li>
<li><a target="_blank" href="https://materialize.com/docs/sql/create-source/"><code>CREATE SOURCE</code></a></li>
<li><a target="_blank" href="https://materialize.com/docs/sql/create-views"><code>CREATE VIEWS</code></a></li>
<li><a target="_blank" href="https://materialize.com/docs/sql/select"><code>SELECT</code></a></li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
