<?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[ full stack - 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[ full stack - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Mon, 18 May 2026 10:47:15 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/news/tag/full-stack/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Fashion App That Helps You Organize Your Wardrobe  ]]>
                </title>
                <description>
                    <![CDATA[ I used to spend too long deciding what to wear, even when my closet was full. That frustration made the problem feel very clear to me: it was not about having fewer clothes. It was about having better ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-fashion-app-to-organize-your-wardrobe/</link>
                <guid isPermaLink="false">69de6abf91716f3cfb5448a1</guid>
                
                    <category>
                        <![CDATA[ webdev ]]>
                    </category>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Machine Learning ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Docker ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MathJax ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mokshita V P ]]>
                </dc:creator>
                <pubDate>Tue, 14 Apr 2026 16:26:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/bf593ff6-6de8-4b30-ab0a-700c3410ccb1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I used to spend too long deciding what to wear, even when my closet was full.</p>
<p>That frustration made the problem feel very clear to me: it was not about having fewer clothes. It was about having better organization, better visibility, and better guidance when making outfit decisions.</p>
<p>So I built a fashion web app that helps users organize their wardrobe, get outfit suggestions, evaluate shopping decisions, and improve recommendations over time using feedback.</p>
<p>In this article, I’ll walk through what the app does, how I built it, the decisions I made along the way, and the challenges that shaped the final result.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-table-of-contents">Table of Contents</a></p>
</li>
<li><p><a href="#heading-what-the-app-does">What the App Does</a></p>
</li>
<li><p><a href="#heading-why-i-built-it">Why I Built It</a></p>
</li>
<li><p><a href="#heading-tech-stack">Tech Stack</a></p>
</li>
<li><p><a href="#heading-product-walkthrough-what-users-see">Product Walkthrough (What Users See)</a></p>
</li>
<li><p><a href="#heading-how-i-built-it">How I Built It</a></p>
</li>
<li><p><a href="#heading-challenges-i-faced">Challenges I Faced</a></p>
</li>
<li><p><a href="#heading-what-i-learned">What I Learned</a></p>
</li>
<li><p><a href="#heading-what-i-want-to-improve-next">What I Want to Improve Next</a></p>
</li>
<li><p><a href="#heading-future-improvements">Future Improvements</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-what-the-app-does">What the App Does</h2>
<p>At a high level, the app combines six core capabilities:</p>
<ol>
<li><p>Wardrobe management</p>
</li>
<li><p>Outfit recommendations</p>
</li>
<li><p>Shopping suggestions</p>
</li>
<li><p>Discard recommendations</p>
</li>
<li><p>Feedback and usage tracking</p>
</li>
<li><p>Secure multi-user accounts</p>
</li>
</ol>
<p>Users can upload clothing items, explore suggested outfits, and mark recommendations as helpful or not helpful. They can also rate outfits and track whether items are worn, kept, or discarded.</p>
<p>That feedback becomes structured data for improving future recommendation quality.</p>
<h2 id="heading-why-i-built-it">Why I Built It</h2>
<p>I wanted to create something that felt personal and actually useful. A lot of fashion apps look polished, but they do not always help with everyday decisions. My goal was to build something that could make wardrobe management easier and outfit selection less overwhelming. The app needed to do three things well:</p>
<ul>
<li><p>store each user’s wardrobe data</p>
</li>
<li><p>personalize recommendations</p>
</li>
<li><p>learn from user feedback over time .</p>
</li>
</ul>
<p>That feedback loop mattered to me because it makes the app feel more alive instead of static.</p>
<h2 id="heading-tech-stack">Tech Stack</h2>
<p>Here are the tools I used to built the app:</p>
<ul>
<li><p>Frontend: React + Vite</p>
</li>
<li><p>Backend: FastAPI</p>
</li>
<li><p>Database: SQLite (local development)</p>
</li>
<li><p>Background jobs: Celery + Redis</p>
</li>
<li><p>Authentication: JWT (access + refresh token flow)</p>
</li>
<li><p>Deployment support: Docker and GitHub Codespaces</p>
</li>
</ul>
<p>This ended up giving me a pretty modular setup, which helped a lot as features started increasing: fast frontend iteration, clean API boundaries, and room to evolve recommendations separately from UI.</p>
<h2 id="heading-product-walkthrough-what-users-see">Product Walkthrough (What Users See)</h2>
<h3 id="heading-1-onboarding-and-account-setup">1. Onboarding and Account Setup</h3>
<p>To start using the app, a user needs to register, verify their email, and complete some profile basics.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/1ff4fb0d-dc97-4088-b720-db917b53ba5b.png" alt="Onboarding screen showing account creation, email verification, and profile fields for body shape, height, weight, and style preferences." style="display:block;margin:0 auto" width="1319" height="850" loading="lazy">

<p>Each account is isolated, so wardrobe history and recommendations stay user-specific.</p>
<p>In this onboarding screen above, you can see account creation, email verification, and profile fields for body shape, height, weight, and style preferences.</p>
<h3 id="heading-2-wardrobe-upload">2. Wardrobe Upload</h3>
<p>Users can upload clothing images .</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/d69bf10b-b79b-4294-923c-5c9e5840098a.png" alt="Wardrobe upload form showing clothing image analysis results with category, dominant color, secondary color, and pattern details." style="display:block;margin:0 auto" width="1320" height="625" loading="lazy">

<p>Image analysis labels each item and makes it searchable for recommendations. The wardrobe upload form shows image analysis results with category, dominant color, secondary color, and pattern details listed.</p>
<h3 id="heading-3-outfit-recommendations">3. Outfit Recommendations</h3>
<p>Users can request recommendations, then rate outputs.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/61527ddf-11e4-4284-92fd-2d0c948ae2db.png" alt="Outfit recommendation dashboard showing ranked outfit cards with feedback and rating actions." style="display:block;margin:0 auto" width="1011" height="692" loading="lazy">

<p>Above you can see the outfit recommendation dashboard that shows ranked outfit cards with feedback and rating actions. Recommendations are ranked by a weighted scoring model.</p>
<h3 id="heading-4-shopping-and-discard-assistants">4. Shopping and Discard Assistants</h3>
<p>The app evaluates new items against existing wardrobe data and flags low-value wardrobe items that may be worth removing.</p>
<img src="https://cdn.hashnode.com/uploads/covers/68ab1274684dc97382d342ea/88ed83c4-fdba-40e7-ad32-f77bdf21cb4d.png" alt="Shopping and discard analysis screen showing recommendation scores, written reasons, and styling guidance for each item." style="display:block;margin:0 auto" width="1324" height="852" loading="lazy">

<p>You can see the recommendation scores, written reasons (not just a binary decision), and styling guidance for each item above. It also features a "how to style it" incase the user still wants to keep the item.</p>
<h2 id="heading-how-i-built-it">How I Built It</h2>
<h3 id="heading-1-frontend-setup-react-vite">1. Frontend Setup (React + Vite)</h3>
<p>I used React + Vite because I wanted fast iteration and a clean component structure.</p>
<p>The frontend is split into feature areas like onboarding, wardrobe management, outfits, shopping, and discarded-item suggestions. I also keep API calls in a service layer so the UI components stay focused on rendering and interaction.</p>
<p>The snippet below is a simplified example of the API service pattern used in the app. It is not meant to be copy-pasted as-is, but it shows the same structure the frontend uses when talking to the backend.</p>
<p>Example API client pattern:</p>
<pre><code class="language-javascript">export async function getOutfitRecommendations(userId, params = {}) {
  const query = new URLSearchParams(params).toString();
  const url = `/users/\({userId}/outfits/recommend\){query ? `?${query}` : ""}`;

  const response = await fetch(url, {
    headers: {
      Authorization: `Bearer ${localStorage.getItem("access_token")}`,
    },
  });

  if (!response.ok) {
    throw new Error("Failed to fetch outfit recommendations");
  }

  return response.json();
}
</code></pre>
<p>Here's what's happening in that snippet:</p>
<ul>
<li><p><code>URLSearchParams</code> builds optional query strings like <code>occasion</code>, <code>season</code>, or <code>limit</code>.</p>
</li>
<li><p>The request path is user-scoped, which keeps each user’s recommendations isolated.</p>
</li>
<li><p>The <code>Authorization</code> header sends the access token so the backend can verify the session.</p>
</li>
<li><p>The response is checked before parsing so the UI can surface a useful error if the request fails.</p>
</li>
</ul>
<p>This pattern kept the frontend simple and reusable as the number of API calls grew.</p>
<h3 id="heading-2-backend-architecture-with-fastapi">2. Backend Architecture with FastAPI</h3>
<p>The backend is organized around clear route groups:</p>
<ul>
<li><p>auth routes for register, login, refresh, logout, and sessions</p>
</li>
<li><p>user analysis routes</p>
</li>
<li><p>wardrobe CRUD routes</p>
</li>
<li><p>recommendation routes for outfits, shopping, and discard analysis</p>
</li>
<li><p>feedback routes for ratings and helpfulness signals</p>
</li>
</ul>
<p>One of the most important design choices was enforcing ownership checks on user-scoped resources. That prevented one user from accessing another user’s wardrobe or feedback data.</p>
<p>The backend snippet below is another simplified example from the app’s route layer. It shows the request validation and orchestration logic, while the actual scoring work stays in the recommendation service.</p>
<pre><code class="language-python">@app.get("/users/{user_id}/outfits/recommend")
def recommend_outfits(user_id: int, occasion: str | None = None, season: str | None = None, limit: int = 10):
    user = get_user_or_404(user_id)
    wardrobe_items = get_user_wardrobe(user_id)

    if len(wardrobe_items) &lt; 2:
        raise HTTPException(status_code=400, detail="Not enough wardrobe items")

    recommendations = outfit_generator.generate_outfit_recommendations(
        wardrobe_items=wardrobe_items,
        body_shape=user.body_shape,
        undertone=user.undertone,
        occasion=occasion,
        season=season,
        top_k=limit,
    )

    return {"user_id": user_id, "recommendations": recommendations}
</code></pre>
<p>Here's how to read that code:</p>
<ul>
<li><p><code>get_user_or_404</code> loads the profile data needed for personalization.</p>
</li>
<li><p><code>get_user_wardrobe</code> fetches only the current user’s items.</p>
</li>
<li><p>The minimum wardrobe check prevents the recommendation logic from running on incomplete data.</p>
</li>
<li><p><code>generate_outfit_recommendations</code> handles the scoring logic separately, which keeps the route handler small and easier to test.</p>
</li>
<li><p>The response returns the results in a shape the frontend can consume directly.</p>
</li>
</ul>
<p>That separation helped keep the API layer readable while the recommendation logic stayed isolated in its own service.</p>
<h3 id="heading-3-recommendation-logic">3. Recommendation Logic</h3>
<p>I intentionally started with deterministic rules before introducing heavy ML. That made behavior easier to debug and explain.</p>
<p>The outfit recommender scores combinations using weighted signals:</p>
<p>$$\text{outfit score} = 0.4 \cdot \text{color harmony} + 0.4 \cdot \text{body-shape fit} + 0.2 \cdot \text{undertone fit}$$</p>
<p>The snippet below is a simplified example from the recommendation engine. It shows how the app combines multiple signals into a single score:</p>
<pre><code class="language-python">def score_outfit(combo, user_context):
    color_score = color_harmony.score(combo)
    shape_score = body_shape_rules.score(combo, user_context.body_shape)
    undertone_score = undertone_rules.score(combo, user_context.undertone)

    total = 0.4 * color_score + 0.4 * shape_score + 0.2 * undertone_score
    return round(total, 3)
</code></pre>
<p>The logic behind this approach is straightforward:</p>
<ul>
<li><p>color harmony helps the outfit feel visually coherent</p>
</li>
<li><p>body-shape scoring helps the outfit feel flattering</p>
</li>
<li><p>undertone scoring helps the colors work better with the user’s profile</p>
</li>
</ul>
<p>I used a similar structure for discard recommendations and shopping suggestions, but with different factors and thresholds.</p>
<h3 id="heading-4-authentication-and-secure-multi-user-design">4. Authentication and Secure Multi-user Design</h3>
<p>Security was one of the most important parts of this build.</p>
<p>I implemented:</p>
<ul>
<li><p>short-lived access tokens</p>
</li>
<li><p>refresh tokens with JTI tracking</p>
</li>
<li><p>token rotation on refresh</p>
</li>
<li><p>session revocation (single session and all sessions)</p>
</li>
<li><p>email verification and password reset flows</p>
</li>
</ul>
<p>The snippet below is a simplified example of the refresh-token lifecycle used in the app. It shows the important control points rather than every helper function:</p>
<pre><code class="language-python">def refresh_access_token(refresh_token: str):
    payload = decode_jwt(refresh_token)
    jti = payload["jti"]

    token_record = db.get_refresh_token(jti)
    if not token_record or token_record.revoked:
        raise AuthError("Invalid refresh token")

    new_refresh, new_jti = issue_refresh_token(payload["sub"])
    token_record.revoked = True
    token_record.replaced_by_jti = new_jti

    new_access = issue_access_token(payload["sub"])
    return {"access_token": new_access, "refresh_token": new_refresh}
</code></pre>
<p>What this code is doing:</p>
<ul>
<li><p>It decodes the refresh token and looks up its JTI in the database.</p>
</li>
<li><p>It rejects reused or revoked sessions, which helps prevent replay attacks.</p>
</li>
<li><p>It rotates the refresh token instead of reusing it.</p>
</li>
<li><p>It issues a fresh access token so the session stays valid without forcing the user to log in again.</p>
</li>
</ul>
<p>This design made multi-device sessions safer and gave me server-side control over logout behavior.</p>
<h3 id="heading-5-background-jobs-for-long-running-operations">5. Background Jobs for Long-running Operations</h3>
<p>Image analysis can be expensive, especially when the app needs to classify clothing, analyze colors, and estimate body-shape-related signals. To keep the request path responsive, I added Celery + Redis support for background tasks.</p>
<p>That gave the app two modes:</p>
<ul>
<li><p>synchronous processing for simpler local development</p>
</li>
<li><p>queued processing for heavier or slower jobs</p>
</li>
</ul>
<p>That tradeoff mattered because it let me keep the developer experience simple without blocking the app during more expensive work.</p>
<h3 id="heading-6-data-model-and-feedback-capture">6. Data Model and Feedback Capture</h3>
<p>A recommendation system only improves if it captures the right signals.</p>
<p>So I added dedicated feedback tables for:</p>
<ul>
<li><p>outfit ratings (1-5 + optional comments)</p>
</li>
<li><p>recommendation helpful/unhelpful feedback</p>
</li>
<li><p>item usage actions (worn/kept/discarded)</p>
</li>
</ul>
<p>Here is the shape of one of those models:</p>
<pre><code class="language-python">class RecommendationFeedback(Base):
    __tablename__ = "recommendation_feedback"

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
    recommendation_type = Column(String(50), nullable=False)
    recommendation_id = Column(Integer, nullable=False)
    helpful = Column(Boolean, nullable=False)
    created_at = Column(DateTime, default=datetime.utcnow)
</code></pre>
<p>How to read this model:</p>
<ul>
<li><p><code>user_id</code> ties feedback to the person who gave it.</p>
</li>
<li><p><code>recommendation_type</code> tells me whether the feedback belongs to outfits, shopping, or discard suggestions.</p>
</li>
<li><p><code>recommendation_id</code> identifies the exact recommendation.</p>
</li>
<li><p><code>helpful</code> stores the user’s direct response.</p>
</li>
<li><p><code>created_at</code> makes it possible to analyze feedback trends over time.</p>
</li>
</ul>
<p>This part of the system gives the app a real learning foundation, even though the feedback-to-model-update loop is still a future improvement.</p>
<h2 id="heading-challenges-i-faced">Challenges I Faced</h2>
<p>This was the section that taught me the most.</p>
<h3 id="heading-1-image-heavy-endpoints-were-slower-than-i-wanted">1. Image-heavy endpoints were slower than I wanted</h3>
<p>The analyze and wardrobe upload flows were doing a lot of work at once: image validation, classification, color extraction, storage, and database writes.</p>
<p>At first, that made the request flow feel heavier than it should have.</p>
<p>What I changed:</p>
<ul>
<li><p>I bounded concurrent image jobs so the app wouldn't try to do too much at once.</p>
</li>
<li><p>I separated slower jobs into background processing where possible.</p>
</li>
<li><p>I used load-test results to confirm which endpoints were actually expensive.</p>
</li>
</ul>
<p>The practical effect was that heavy image requests stopped competing with each other so aggressively. Instead of letting many expensive tasks pile up inside the same request cycle, I limited the active work and pushed slower operations into the queue when needed.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Bounding concurrency prevented the system from overloading CPU-bound tasks.</p>
</li>
<li><p>Moving expensive work into async jobs kept the main request/response cycle more responsive.</p>
</li>
<li><p>Load testing gave me evidence instead of guesswork, so I could tune the system based on real performance behavior.</p>
</li>
</ul>
<p>In other words, I didn't just “optimize” the endpoint in theory. I changed the execution model so expensive analysis could not block every other request behind it.</p>
<h3 id="heading-2-jwt-sessions-needed-real-server-side-control">2. JWT sessions needed real server-side control</h3>
<p>A basic JWT setup is easy to get working, but it becomes less useful if you cannot revoke sessions or manage multiple devices cleanly.</p>
<p>What I changed:</p>
<ul>
<li><p>I stored refresh tokens in the database.</p>
</li>
<li><p>I tracked token JTI values.</p>
</li>
<li><p>I rotated refresh tokens when users refreshed their session.</p>
</li>
<li><p>I added endpoints for logging out a single session or all sessions.</p>
</li>
</ul>
<p>The important shift here was moving from “token exists, therefore session is valid” to “token exists, matches the database record, and has not been revoked or replaced.” That gave the server the authority to invalidate old sessions immediately.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Server-side token tracking made revocation possible.</p>
</li>
<li><p>Rotation reduced the chance of token reuse.</p>
</li>
<li><p>Session management became visible to the user, which made the app feel more trustworthy.</p>
</li>
</ul>
<p>This is what made logout-all and multi-device management work in a real way instead of just being cosmetic UI actions.</p>
<h3 id="heading-3-user-data-isolation-had-to-be-explicit">3. User data isolation had to be explicit</h3>
<p>Because this is a multi-user app, I had to be careful that one account could never accidentally see another account’s wardrobe data.</p>
<p>What I changed:</p>
<ul>
<li><p>I added ownership checks to user-scoped routes.</p>
</li>
<li><p>I kept all wardrobe and feedback queries filtered by <code>user_id</code>.</p>
</li>
<li><p>I used encrypted image storage instead of exposing raw paths.</p>
</li>
</ul>
<p>In practice, this meant every route had to ask the same question: “Does this user own the resource they are trying to access?” If the answer was no, the request stopped immediately.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Ownership checks made data access rules explicit.</p>
</li>
<li><p>User-filtered queries prevented accidental cross-account reads.</p>
</li>
<li><p>Encrypted storage improved privacy and reduced the risk of exposing image data directly.</p>
</li>
</ul>
<p>That combination is what kept wardrobe data, feedback history, and images separated correctly across accounts.</p>
<h3 id="heading-4-docker-made-the-project-easier-to-share-but-only-after-the-stack-was-organized">4. Docker made the project easier to share, but only after the stack was organized</h3>
<p>The app includes the frontend, backend, Redis, Celery worker, and Celery Beat, so the first challenge was making the setup feel reproducible instead of fragile.</p>
<p>What I changed:</p>
<ul>
<li><p>I defined the stack in Docker Compose.</p>
</li>
<li><p>I documented the required environment variables.</p>
</li>
<li><p>I kept the dev stack aligned with how the app runs in practice.</p>
</li>
</ul>
<p>This removed a lot of setup ambiguity. Instead of asking someone to manually figure out how the frontend, backend, Redis, and workers fit together, I made the stack describe itself.</p>
<p>Why this fixed it:</p>
<ul>
<li><p>Docker let contributors start the project with fewer manual steps.</p>
</li>
<li><p>Clear environment configuration reduced setup mistakes.</p>
</li>
<li><p>Matching the stack to the architecture made the app easier to understand and test.</p>
</li>
</ul>
<p>That was important because the app depends on several moving parts, and the simplest way to make the project approachable was to make startup behavior predictable.</p>
<h2 id="heading-what-i-learned">What I Learned</h2>
<p>This project taught me a few important lessons:</p>
<ul>
<li><p>Small features become much more valuable when they work together.</p>
</li>
<li><p>Feedback data is one of the strongest signals for improving recommendations.</p>
</li>
<li><p>Clean data modeling matters a lot when multiple users are involved.</p>
</li>
<li><p>Docker and clear setup instructions make a project much easier for other people to try.</p>
</li>
</ul>
<p>I also learned that a project does not need to be huge to be useful. A focused app that solves one problem well can still feel meaningful.</p>
<h2 id="heading-what-i-want-to-improve-next">What I Want to Improve Next</h2>
<p>My roadmap from here:</p>
<ol>
<li><p>Integrate feedback directly into ranking updates</p>
</li>
<li><p>Add visual analytics for recommendation quality trends</p>
</li>
<li><p>Improve mobile UX parity</p>
</li>
<li><p>Deploy with persistent cloud storage and production database defaults</p>
</li>
<li><p>Provide a public demo mode for easier evaluation</p>
</li>
</ol>
<h2 id="heading-future-improvements">Future Improvements</h2>
<p>There are still a few things I would like to add later:</p>
<ul>
<li><p>a more advanced recommendation engine</p>
</li>
<li><p>visual analytics for user feedback</p>
</li>
<li><p>better mobile support</p>
</li>
<li><p>live deployment with persistent cloud storage</p>
</li>
<li><p>a public demo mode for easier testing</p>
</li>
</ul>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This project began as a personal frustration and turned into a full web application with authentication, wardrobe storage, recommendation logic, and feedback infrastructure.</p>
<p>The most rewarding part was seeing how practical software decisions, not just flashy UI, can help people make everyday choices faster.</p>
<p>If you want to explore or run the project, <a href="https://github.com/Mokshitavp1/fashion_assistant">check out the repo</a>. You can try the flows and share feedback. I would especially love input on recommendation quality, UX clarity, and what features would make this genuinely useful in daily life.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Full-Stack CRUD App with React, AWS Lambda, DynamoDB, and Cognito Auth ]]>
                </title>
                <description>
                    <![CDATA[ Building a web application that works only on your local machine is one thing. Building one that is secure, connected to a real database, and accessible to anyone on the internet is another challenge  ]]>
                </description>
                <link>https://www.freecodecamp.org/news/full-stack-aws-react-lambda-dynamodb-tutorial/</link>
                <guid isPermaLink="false">69b96f7ec22d3eeb8ac3bf81</guid>
                
                    <category>
                        <![CDATA[ serverless ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Cloud Computing ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Benedicta Onyebuchi ]]>
                </dc:creator>
                <pubDate>Tue, 17 Mar 2026 15:13:02 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/1a996eff-72f5-4f4d-b8da-cf4d646c3224.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Building a web application that works only on your local machine is one thing. Building one that is secure, connected to a real database, and accessible to anyone on the internet is another challenge entirely. And it requires a different set of tools.</p>
<p>Most production web applications share a common set of needs: they store and retrieve data, they expose that data through an API, they require users to authenticate before accessing sensitive operations, and they need to be deployed somewhere reliable and fast.</p>
<p>Meeting all of those needs used to require managing servers, configuring databases, handling authentication infrastructure, and provisioning hosting environments – often as separate, manual processes.</p>
<p>AWS changes that model significantly. With the combination of services you'll use in this tutorial (Lambda, DynamoDB, API Gateway, Cognito, and CloudFront), you can build and deploy a fully functional, secured, globally distributed application without managing a single server.</p>
<p>Each service handles one specific responsibility:</p>
<ul>
<li><p>DynamoDB stores your data</p>
</li>
<li><p>Lambda runs your business logic on demand</p>
</li>
<li><p>API Gateway exposes your functions as a REST API</p>
</li>
<li><p>Cognito manages user authentication</p>
</li>
<li><p>CloudFront delivers your frontend worldwide over HTTPS.</p>
</li>
</ul>
<p>The AWS CDK (Cloud Development Kit) ties all of this together by letting you define every one of those services as TypeScript code. Instead of clicking through the AWS Console to configure each resource manually, you describe your entire infrastructure in a single file and deploy it with one command.</p>
<p>By the end of this tutorial, you will have a fully deployed vendor management dashboard. Users can sign up, log in, and then create, read, and delete vendors, with all data securely stored in AWS DynamoDB and all routes protected by Amazon Cognito authentication.</p>
<h2 id="heading-what-youll-build">What You'll Build</h2>
<p>In this handbook, you'll build a two-panel web app where authenticated users can:</p>
<ul>
<li><p>Add a new vendor (name, category, contact email)</p>
</li>
<li><p>View all saved vendors in real time</p>
</li>
<li><p>Delete a vendor from the list</p>
</li>
<li><p>Sign in and sign out securely</p>
</li>
</ul>
<p>The frontend is built with Next.js. The backend runs entirely on AWS: DynamoDB stores the data, Lambda functions handle the logic, API Gateway exposes a REST API, Cognito manages authentication, and CloudFront serves the app globally over HTTPS.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a href="#heading-who-this-is-for">Who This Is For</a></p>
</li>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-architecture-overview">Architecture Overview</a></p>
</li>
<li><p><a href="#heading-part-1-set-up-your-aws-account-and-tools">Part 1: Set Up Your AWS Account and Tools</a></p>
</li>
<li><p><a href="#heading-part-2-set-up-the-project-structure">Part 2: Set Up the Project Structure</a></p>
</li>
<li><p><a href="#heading-part-3-define-the-database-dynamodb">Part 3: Define the Database (DynamoDB)</a></p>
</li>
<li><p><a href="#heading-part-4-write-the-lambda-functions">Part 4: Write the Lambda Functions</a></p>
</li>
<li><p><a href="#heading-part-5-build-the-api-with-api-gateway">Part 5: Build the API with API Gateway</a></p>
</li>
<li><p><a href="#heading-part-6-deploy-the-backend-to-aws">Part 6: Deploy the Backend to AWS</a></p>
</li>
<li><p><a href="#heading-part-7-build-the-react-frontend">Part 7: Build the React Frontend</a></p>
</li>
<li><p><a href="#heading-part-8-add-authentication-with-amazon-cognito">Part 8: Add Authentication with Amazon Cognito</a></p>
</li>
<li><p><a href="#heading-part-9-deploy-the-frontend-with-s3-and-cloudfront">Part 9: Deploy the Frontend with S3 and CloudFront</a></p>
</li>
<li><p><a href="#heading-what-you-built">What You Built</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-who-this-is-for">Who This Is For</h2>
<p>This tutorial is for developers who know basic JavaScript and React but have never used AWS. You don't need any prior backend, cloud, or DevOps experience. I'll explain every AWS concept before we use it.</p>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before starting, make sure you have the following installed and available:</p>
<ul>
<li><p><strong>Node.js 18 or higher</strong>: <a href="https://nodejs.org">Download here</a></p>
</li>
<li><p><strong>npm</strong>: Included with Node.js</p>
</li>
<li><p><strong>A code editor</strong>: I recommend VS Code</p>
</li>
<li><p><strong>A terminal</strong>: Any terminal on macOS, Linux, or Windows (WSL recommended on Windows)</p>
</li>
<li><p><strong>An AWS account</strong>: You will create one in Part 1. A credit card is required, but the Free Tier covers everything in this tutorial.</p>
</li>
<li><p><strong>Basic familiarity with React and TypeScript</strong>: You should understand components, <code>useState</code>, and <code>useEffect</code>.</p>
</li>
</ul>
<h2 id="heading-architecture-overview">Architecture Overview</h2>
<p>Before writing any code, here's a plain-English description of how the pieces fit together.</p>
<p>When a user clicks "Add Vendor" in the React app:</p>
<ol>
<li><p>The frontend reads the user's JWT auth token from the browser session</p>
</li>
<li><p>It sends a <code>POST</code> request to API Gateway, including the token in the request header</p>
</li>
<li><p>API Gateway checks the token against Cognito. If the token is invalid or missing, it rejects the request with a 401 error immediately</p>
</li>
<li><p>If the token is valid, API Gateway passes the request to the createVendor Lambda function</p>
</li>
<li><p>The Lambda function writes the new vendor to DynamoDB</p>
</li>
<li><p>DynamoDB confirms the write, and the Lambda returns a success response</p>
</li>
<li><p>The frontend re-fetches the vendor list and updates the UI</p>
</li>
</ol>
<p>The same flow applies to reading and deleting vendors, with different Lambda functions and HTTP methods.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/70486bdc-f272-45db-be30-f10752916546.png" alt="Architecture diagram of the Vendors Tracker Application" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>How the app is deployed:</strong> Your React app is exported as a static site, uploaded to an S3 bucket, and served globally through CloudFront. Your backend infrastructure (Lambda functions, API Gateway, DynamoDB, Cognito) is defined in TypeScript using AWS CDK and deployed with a single command.</p>
<h2 id="heading-part-1-set-up-your-aws-account-and-tools">Part 1: Set Up Your AWS Account and Tools</h2>
<p>Before writing any application code, you need three things in place: an AWS account, the right tools on your machine, and credentials that let those tools communicate with AWS on your behalf.</p>
<h3 id="heading-11-create-your-aws-account">1.1 Create Your AWS Account</h3>
<p>If you don't have an AWS account:</p>
<ol>
<li><p>Go to <a href="https://aws.amazon.com">https://aws.amazon.com</a></p>
</li>
<li><p>Click <strong>Create an AWS Account</strong></p>
</li>
<li><p>Follow the sign-up prompts and add a payment method</p>
</li>
<li><p>Once registered, log in to the AWS Management Console</p>
</li>
</ol>
<p>AWS has a Free Tier that covers all the services used in this tutorial. You won't be charged for normal use while following along.</p>
<h3 id="heading-12-install-the-aws-cli-and-cdk">1.2 Install the AWS CLI and CDK</h3>
<p>The <strong>AWS CLI</strong> is a command-line tool that lets you interact with AWS from your terminal: checking resources, configuring credentials, and more.</p>
<p>The <strong>AWS CDK (Cloud Development Kit)</strong> is the tool you will use to define your entire backend (database, Lambda functions, API) using TypeScript code. Instead of clicking through the AWS Console to create each resource, you describe what you want in a TypeScript file and CDK builds it for you.</p>
<p>Install both:</p>
<pre><code class="language-shell"># Install AWS CLI (macOS)
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

# For Linux, see: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-linux.html
# For Windows, see: https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2-windows.html

# Install AWS CDK globally
npm install -g aws-cdk
</code></pre>
<p>Verify both are installed:</p>
<pre><code class="language-shell">aws --version
cdk --version
</code></pre>
<p>Both commands should print a version number. If they do, you are ready to move on.</p>
<h3 id="heading-13-configure-your-aws-credentials-iam">1.3 Configure Your AWS Credentials (IAM)</h3>
<p>This step is critical. Your terminal needs a set of credentials – like a username and password – to act on your behalf inside AWS.</p>
<p>Think of your root account (the one you signed up with) as the master key to your entire AWS account. You should never use it for day-to-day development. Instead, you will create a separate IAM user with its own set of keys. If those keys are ever exposed, you can delete them without compromising your root account.</p>
<h4 id="heading-phase-1-create-an-iam-user">Phase 1: Create an IAM User</h4>
<ol>
<li><p>Log in to the AWS Console and search for IAM in the top search bar</p>
</li>
<li><p>In the left sidebar, click Users, then click Create user</p>
</li>
<li><p>Name the user <code>cdk-dev</code>. Leave "Provide user access to the AWS Management Console" unchecked – you only need terminal access, not console access</p>
</li>
<li><p>On the permissions screen, choose Attach policies directly</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/d4699108-c1aa-4dd3-957c-b84292c719a2.png" alt="IAM Console showing the “Attach policies directly” screen with AdministratorAccess checked" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<ol>
<li>Search for <code>AdministratorAccess</code> and check the box next to it</li>
</ol>
<p>Note on permissions: In a production job you would use a more restricted policy. For this tutorial, Administrator access is needed because CDK creates many different types of AWS resources.</p>
<p>6. Click through to the end and click Create user</p>
<h4 id="heading-phase-2-generate-access-keys">Phase 2: Generate Access Keys</h4>
<ol>
<li><p>Click on your newly created <code>cdk-dev</code> user from the Users list</p>
</li>
<li><p>Go to the Security credentials tab</p>
</li>
<li><p>Scroll down to Access keys and click Create access key</p>
</li>
<li><p>Select Command Line Interface (CLI), check the acknowledgment box, and click Next</p>
</li>
<li><p>Click Create access key</p>
</li>
</ol>
<p><strong>Important</strong>: Copy both the Access Key ID and the Secret Access Key right now. You will never be able to see the Secret Access Key again after closing this screen. Save both values in a password manager or secure note.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/d85bb4eb-0ecf-4d92-be92-d75af5a534c6.png" alt="IAM Console showing the Create access key screen with the Access Key ID and Secret Access Key" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h4 id="heading-phase-3-connect-your-terminal-to-aws">Phase 3: Connect Your Terminal to AWS</h4>
<p>Run the following command in your terminal:</p>
<pre><code class="language-shell">aws configure
</code></pre>
<p>You will be prompted for four values:</p>
<pre><code class="language-shell">AWS Access Key ID:     [paste your Access Key ID]
AWS Secret Access Key: [paste your Secret Access Key]
Default region name:   us-east-1
Default output format: json
</code></pre>
<p>Use <code>us-east-1</code> as your region for this tutorial. After this step, every CDK and AWS CLI command you run will use these credentials automatically.</p>
<h2 id="heading-part-2-set-up-the-project-structure">Part 2: Set Up the Project Structure</h2>
<p>You will use a <strong>monorepo</strong> layout – one top-level folder with two sub-projects inside: <code>frontend</code> for your React app and <code>backend</code> for your AWS infrastructure code. They are deployed independently but live side by side.</p>
<h3 id="heading-21-create-the-workspace">2.1 Create the Workspace</h3>
<pre><code class="language-shell">mkdir vendor-tracker &amp;&amp; cd vendor-tracker
mkdir backend frontend
</code></pre>
<h3 id="heading-22-initialize-the-frontend-nextjs">2.2 Initialize the Frontend (Next.js)</h3>
<p>Navigate into the <code>frontend</code> folder and run:</p>
<pre><code class="language-shell">cd frontend
npx create-next-app@latest .
</code></pre>
<p>When prompted, choose the following options:</p>
<ul>
<li><p><strong>TypeScript</strong> --&gt; Yes</p>
</li>
<li><p><strong>ESLint</strong> --&gt; Yes</p>
</li>
<li><p><strong>Tailwind CSS</strong> --&gt; Yes</p>
</li>
<li><p><strong>src/ directory</strong> --&gt;No</p>
</li>
<li><p><strong>App Router</strong> --&gt; Yes</p>
</li>
<li><p><strong>Import alias</strong> --&gt; No</p>
</li>
</ul>
<h3 id="heading-23-initialize-the-backend-cdk">2.3 Initialize the Backend (CDK)</h3>
<p>Navigate into the <code>backend</code> folder and run:</p>
<pre><code class="language-shell">cd ../backend
cdk init app --language typescript
</code></pre>
<p>This generates a boilerplate CDK project. The most important file it creates is <code>backend/lib/backend-stack.ts</code>. This is where you will define all of your AWS infrastructure as TypeScript code.</p>
<p>Also install <code>esbuild</code>, which CDK uses to bundle your Lambda functions:</p>
<pre><code class="language-shell">npm install --save-dev esbuild
</code></pre>
<h3 id="heading-24-understanding-cdk-before-you-write-any-code">2.4 Understanding CDK Before You Write Any Code</h3>
<p>CDK is likely different from most tools you have used. Here is how it works:</p>
<p>Normally, you would create AWS resources by clicking through the AWS Console: create a table here, configure a Lambda function there. CDK lets you do all of that using TypeScript code instead.</p>
<p>When you run <code>cdk deploy</code>, CDK reads your TypeScript file, converts it into an AWS CloudFormation template (an internal AWS format for describing infrastructure), and submits it to AWS. AWS then creates all the resources you described.</p>
<p>A few terms you will see throughout this tutorial:</p>
<ul>
<li><p><strong>Stack</strong>: The collection of all AWS resources you define together. Your <code>BackendStack</code> class is your stack.</p>
</li>
<li><p><strong>Construct</strong>: Each individual AWS resource you create inside a stack (a table, a Lambda function, an API) is called a construct.</p>
</li>
<li><p><strong>Deploy</strong>: Running <code>cdk deploy</code> sends your TypeScript definition to AWS and creates or updates the real resources.</p>
</li>
</ul>
<p>The main file you'll work in is <code>backend/lib/backend-stack.ts</code>. Think of it as the blueprint for your entire backend.</p>
<p>Your final project structure will look like this:</p>
<pre><code class="language-plaintext">vendor-tracker/
├── backend/
│   ├── lambda/
│   │   ├── createVendor.ts
│   │   ├── getVendors.ts
│   │   └── deleteVendor.ts
│   ├── lib/
│   │   └── backend-stack.ts
│   └── package.json
└── frontend/
    ├── app/
    │   ├── layout.tsx
    │   ├── page.tsx
    │   └── providers.tsx
    ├── lib/
    │   └── api.ts
    ├── types/
    │   └── vendor.ts
    └── .env.local
</code></pre>
<h2 id="heading-part-3-define-the-database-dynamodb">Part 3: Define the Database (DynamoDB)</h2>
<p>DynamoDB is AWS's NoSQL database. Think of it as a fast, scalable key-value store in the cloud. Every item in a DynamoDB table must have a unique ID called the <strong>partition key</strong>. For your vendor table, that key will be <code>vendorId</code>.</p>
<p>Open <code>backend/lib/backend-stack.ts</code>. Replace the entire file contents with the following:</p>
<pre><code class="language-typescript">import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. DynamoDB Table
    const vendorTable = new dynamodb.Table(this, 'VendorTable', {
      partitionKey: {
        name: 'vendorId',
        type: dynamodb.AttributeType.STRING,
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY, // For development only
    });
  }
}
</code></pre>
<p><strong>What each line does:</strong></p>
<ul>
<li><p><code>partitionKey</code> tells DynamoDB that <code>vendorId</code> is the unique identifier for every record. No two vendors can share the same <code>vendorId</code>.</p>
</li>
<li><p><code>PAY_PER_REQUEST</code> means you only pay when data is actually read or written. There is no charge when the table is idle, which makes it cost-effective for learning.</p>
</li>
<li><p><code>RemovalPolicy.DESTROY</code> means the table will be deleted when you run <code>cdk destroy</code>. For production apps you would not use this.</p>
</li>
</ul>
<h2 id="heading-part-4-write-the-lambda-functions">Part 4: Write the Lambda Functions</h2>
<p>A Lambda function is your server, but unlike a traditional server, it only runs when it's called. AWS spins it up on demand, runs your code, and shuts it down. You're only charged for the time your code is actually running.</p>
<p>You'll write three Lambda functions:</p>
<ul>
<li><p><code>createVendor.ts</code>: Adds a new vendor to DynamoDB</p>
</li>
<li><p><code>getVendors.ts</code>: Returns all vendors from DynamoDB</p>
</li>
<li><p><code>deleteVendor.ts</code>: Removes a vendor from DynamoDB by ID</p>
</li>
</ul>
<p>Create a new folder inside <code>backend</code>:</p>
<pre><code class="language-shell">mkdir backend/lambda
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/6330a84b-77c3-4001-9783-5fedc89ae1c0.png" alt="6330a84b-77c3-4001-9783-5fedc89ae1c0" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h3 id="heading-a-note-on-the-aws-sdk">A Note on the AWS SDK</h3>
<p>All three Lambda functions use <strong>AWS SDK v3</strong> (<code>@aws-sdk/client-dynamodb</code> and <code>@aws-sdk/lib-dynamodb</code>). This is the current standard. An older version of the SDK (<code>aws-sdk</code>) exists but is deprecated and not bundled in the Node.js 18 Lambda runtime, which is what you'll use. Stick to v3 throughout.</p>
<h3 id="heading-41-create-vendor-lambda">4.1 Create Vendor Lambda</h3>
<p>Create <code>backend/lambda/createVendor.ts</code>:</p>
<pre><code class="language-typescript">import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
import { randomUUID } from "crypto";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async (event: any) =&gt; {
  try {
    const body = JSON.parse(event.body);

    const item = {
      vendorId: randomUUID(), // Generates a collision-safe unique ID
      name: body.name,
      category: body.category,
      contactEmail: body.contactEmail,
      createdAt: new Date().toISOString(),
    };

    await docClient.send(
      new PutCommand({
        TableName: process.env.TABLE_NAME!,
        Item: item,
      })
    );

    return {
      statusCode: 201,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type,Authorization",
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE",
      },
      body: JSON.stringify({ message: "Vendor created", vendorId: item.vendorId }),
    };
  } catch (error) {
    console.error("Error creating vendor:", error);
    return {
      statusCode: 500,
      headers: { "Access-Control-Allow-Origin": "*" },
      body: JSON.stringify({ error: "Failed to create vendor" }),
    };
  }
};
</code></pre>
<p><strong>What each part does:</strong></p>
<ul>
<li><p><code>randomUUID()</code> generates a universally unique ID using Node's built-in <code>crypto</code> module. No extra package is needed. This is more reliable than <code>Date.now()</code>, which can produce duplicate IDs if two requests arrive within the same millisecond.</p>
</li>
<li><p><code>process.env.TABLE_NAME</code> reads the DynamoDB table name from an environment variable. You'll set this value in the CDK stack. This avoids hardcoding the table name inside your Lambda code.</p>
</li>
<li><p>The <code>headers</code> block is required for CORS (Cross-Origin Resource Sharing). Without <code>Access-Control-Allow-Origin</code>, your browser will block responses from a different domain than your frontend. Without <code>Access-Control-Allow-Headers</code>, the <code>Authorization</code> header you add later for Cognito will be rejected during the browser's preflight check.</p>
</li>
</ul>
<h3 id="heading-42-get-vendors-lambda">4.2 Get Vendors Lambda</h3>
<p>Create <code>backend/lambda/getVendors.ts</code>:</p>
<pre><code class="language-typescript">import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, ScanCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async () =&gt; {
  try {
    const response = await docClient.send(
      new ScanCommand({
        TableName: process.env.TABLE_NAME!,
      })
    );

    return {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type,Authorization",
        "Content-Type": "application/json",
      },
      body: JSON.stringify(response.Items ?? []),
    };
  } catch (error) {
    console.error("Error fetching vendors:", error);
    return {
      statusCode: 500,
      headers: { "Access-Control-Allow-Origin": "*" },
      body: JSON.stringify({ error: "Failed to fetch vendors" }),
    };
  }
};
</code></pre>
<p><strong>What each part does:</strong></p>
<ul>
<li><p><code>ScanCommand</code> reads every item in the table and returns them as an array. For a learning project this is fine. In a production app with millions of rows, you would use a more targeted <code>QueryCommand</code> to avoid reading the entire table on every request.</p>
</li>
<li><p><code>response.Items ?? []</code> returns an empty array if the table is empty, preventing the frontend from crashing when there are no vendors yet.</p>
</li>
</ul>
<h3 id="heading-43-delete-vendor-lambda">4.3 Delete Vendor Lambda</h3>
<p>Create <code>backend/lambda/deleteVendor.ts</code>:</p>
<pre><code class="language-typescript">import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, DeleteCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

export const handler = async (event: any) =&gt; {
  try {
    const body = JSON.parse(event.body);
    const { vendorId } = body;

    if (!vendorId) {
      return {
        statusCode: 400,
        headers: { "Access-Control-Allow-Origin": "*" },
        body: JSON.stringify({ error: "vendorId is required" }),
      };
    }

    await docClient.send(
      new DeleteCommand({
        TableName: process.env.TABLE_NAME!,
        Key: { vendorId },
      })
    );

    return {
      statusCode: 200,
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Headers": "Content-Type,Authorization",
        "Access-Control-Allow-Methods": "OPTIONS,POST,GET,DELETE",
      },
      body: JSON.stringify({ message: "Vendor deleted" }),
    };
  } catch (error) {
    console.error("Error deleting vendor:", error);
    return {
      statusCode: 500,
      headers: { "Access-Control-Allow-Origin": "*" },
      body: JSON.stringify({ error: "Failed to delete vendor" }),
    };
  }
};
</code></pre>
<p><strong>What each part does:</strong></p>
<ul>
<li><p><code>DeleteCommand</code> removes the item whose <code>vendorId</code> matches the key you provide. DynamoDB doesn't return an error if the item doesn't exist. It simply does nothing.</p>
</li>
<li><p>The <code>400</code> guard at the top returns a clear error if the caller forgets to send a <code>vendorId</code>, rather than letting DynamoDB throw a confusing internal error.</p>
</li>
</ul>
<h2 id="heading-part-5-build-the-api-with-api-gateway">Part 5: Build the API with API Gateway</h2>
<p>API Gateway is what gives your Lambda functions a public URL. Without it, there's no way for your browser to trigger a Lambda function. Think of it as the front door of your backend: it receives HTTP requests, checks whether the caller is authorized, routes the request to the correct Lambda, and returns the Lambda's response to the caller.</p>
<p>Now you'll wire everything together in <code>backend/lib/backend-stack.ts</code>.</p>
<h3 id="heading-51-add-lambda-functions-and-api-gateway-to-the-stack">5.1 Add Lambda Functions and API Gateway to the Stack</h3>
<p>Replace the entire contents of <code>backend/lib/backend-stack.ts</code> with this complete, assembled file:</p>
<pre><code class="language-typescript">import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. DynamoDB Table 
    const vendorTable = new dynamodb.Table(this, 'VendorTable', {
      partitionKey: {
        name: 'vendorId',
        type: dynamodb.AttributeType.STRING,
      },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // 2. Lambda Functions
    const lambdaEnv = { TABLE_NAME: vendorTable.tableName };

    const createVendorLambda = new NodejsFunction(this, 'CreateVendorHandler', {
      entry: 'lambda/createVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const getVendorsLambda = new NodejsFunction(this, 'GetVendorsHandler', {
      entry: 'lambda/getVendors.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const deleteVendorLambda = new NodejsFunction(this, 'DeleteVendorHandler', {
      entry: 'lambda/deleteVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    // 3. Permissions (Least Privilege)
    vendorTable.grantWriteData(createVendorLambda);
    vendorTable.grantReadData(getVendorsLambda);
    vendorTable.grantWriteData(deleteVendorLambda);

    // 4. API Gateway
    const api = new apigateway.RestApi(this, 'VendorApi', {
      restApiName: 'Vendor Service',
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ['Content-Type', 'Authorization'],
      },
    });

    const vendors = api.root.addResource('vendors');
    vendors.addMethod('POST', new apigateway.LambdaIntegration(createVendorLambda));
    vendors.addMethod('GET', new apigateway.LambdaIntegration(getVendorsLambda));
    vendors.addMethod('DELETE', new apigateway.LambdaIntegration(deleteVendorLambda));

    // 5. Outputs
    new cdk.CfnOutput(this, 'ApiEndpoint', {
      value: api.url,
    });
  }
}
</code></pre>
<p><strong>What each section does:</strong></p>
<p><code>NodejsFunction</code> is a special CDK construct that automatically bundles your Lambda code and all its dependencies into a single file using <code>esbuild</code> before uploading it to AWS. This is why you installed <code>esbuild</code> in Part 2.</p>
<p>Always use <code>NodejsFunction</code> instead of the basic <code>lambda.Function</code> construct. The basic version requires you to manually manage bundling, which causes "Module not found" errors at runtime.</p>
<p><strong>Permissions (Least Privilege):</strong> In AWS, no resource can communicate with any other resource by default. A Lambda function has no access to DynamoDB, S3, or anything else unless you explicitly grant it.</p>
<p>This is called the <strong>Least Privilege</strong> principle: each piece of your system gets exactly the permissions it needs, and nothing more. <code>grantWriteData</code> lets a Lambda write and delete items. <code>grantReadData</code> lets a Lambda read items. Using separate grants for each function means the <code>getVendors</code> Lambda can never accidentally delete data.</p>
<p><code>CfnOutput</code> prints a value to your terminal after <code>cdk deploy</code> completes. You'll use the <code>ApiEndpoint</code> URL to configure your frontend.</p>
<h2 id="heading-part-6-deploy-the-backend-to-aws">Part 6: Deploy the Backend to AWS</h2>
<p>Your infrastructure is fully defined in code. Now you'll deploy it to AWS and get a live API URL.</p>
<h3 id="heading-61-bootstrap-your-aws-environment">6.1 Bootstrap Your AWS Environment</h3>
<p>Before your first CDK deployment, AWS needs a small landing zone in your account – an S3 bucket where CDK can upload your Lambda bundles and other assets. This setup step is called <strong>bootstrapping</strong> and only needs to be done once per AWS account per region.</p>
<p>From inside your <code>backend</code> folder, run:</p>
<pre><code class="language-shell">cdk bootstrap
</code></pre>
<p><strong>Important</strong>: Bootstrapping is region-specific. If you ever switch to a different AWS region, you will need to run <code>cdk bootstrap</code> again in that region.</p>
<h3 id="heading-62-deploy">6.2 Deploy</h3>
<p>Run:</p>
<pre><code class="language-shell">cdk deploy
</code></pre>
<p>CDK will display a summary of everything it is about to create and ask for your confirmation. Type <code>y</code> and press Enter.</p>
<p>When the deployment finishes, you'll see an <strong>Outputs</strong> section in your terminal:</p>
<pre><code class="language-plaintext">Outputs:
BackendStack.ApiEndpoint = https://abcdef123.execute-api.us-east-1.amazonaws.com/prod/
</code></pre>
<p>Copy that URL. You'll need it when building the frontend.</p>
<h3 id="heading-63-troubleshooting-how-to-read-aws-error-logs">6.3 Troubleshooting: How to Read AWS Error Logs</h3>
<p>Real deployments rarely go perfectly the first time. If something goes wrong after deploying, here is how to find the actual error message.</p>
<h4 id="heading-error-502-bad-gateway">Error: 502 Bad Gateway</h4>
<p>A <code>502</code> means API Gateway received your request but your Lambda crashed before it could respond. The most common cause is a missing environment variable – for example, if <code>TABLE_NAME</code> was not passed correctly and the Lambda cannot find the table.</p>
<p>To find the actual error message, use <strong>CloudWatch Logs</strong>:</p>
<ol>
<li><p>Log in to the AWS Console and search for CloudWatch</p>
</li>
<li><p>In the left sidebar, click Logs --&gt; Log groups</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/abfb78fc-574b-4a75-a12b-12fb09f041b3.png" alt="CloudWatch left sidebar with log groups, and the search field showing /aws/lambda/" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<ol>
<li><p>Find the group named <code>/aws/lambda/BackendStack-CreateVendorHandler...</code></p>
</li>
<li><p>Click the most recent Log stream</p>
</li>
<li><p>Read the error message. It will tell you exactly what went wrong</p>
</li>
</ol>
<p>Two common messages and their fixes:</p>
<ul>
<li><p><code>Runtime.ImportModuleError</code> : Your Lambda cannot find a module. Make sure you're using <code>NodejsFunction</code> (not <code>lambda.Function</code>) in your CDK stack. <code>NodejsFunction</code> automatically bundles dependencies; <code>lambda.Function</code> does not.</p>
</li>
<li><p><code>AccessDeniedException</code>: Your Lambda tried to access DynamoDB but doesn't have permission. Check that you have the correct <code>grantWriteData</code> or <code>grantReadData</code> call in your stack for that Lambda.</p>
</li>
</ul>
<h2 id="heading-part-7-build-the-react-frontend">Part 7: Build the React Frontend</h2>
<p>Your backend is live. Now you'll build the React UI that talks to it.</p>
<h3 id="heading-71-define-the-vendor-type">7.1 Define the Vendor Type</h3>
<p>Before writing any API or component code, define what a "vendor" looks like in TypeScript. This gives you type safety throughout your frontend code.</p>
<p>Create <code>frontend/types/vendor.ts</code>:</p>
<pre><code class="language-typescript">export interface Vendor {
  vendorId?: string; // Optional when creating — the Lambda generates it
  name: string;
  category: string;
  contactEmail: string;
  createdAt?: string;
}
</code></pre>
<p>The <code>vendorId?</code> is marked optional with <code>?</code> because when you are <em>creating</em> a new vendor, you don't have an ID yet. The <code>createVendor</code> Lambda generates one. When you <em>read</em> vendors back from the API, <code>vendorId</code> will always be present.</p>
<h3 id="heading-72-create-the-api-service-layer">7.2 Create the API Service Layer</h3>
<p>Rather than writing <code>fetch</code> calls directly inside your React components, you'll centralize all your API logic in one file. This pattern is called a <strong>service layer</strong>. It keeps your components clean and makes it easy to update API calls in one place.</p>
<p>First, create a <code>.env.local</code> file inside your <code>frontend</code> folder to store your API URL:</p>
<pre><code class="language-bash"># frontend/.env.local
NEXT_PUBLIC_API_URL=https://abcdef123.execute-api.us-east-1.amazonaws.com/prod
</code></pre>
<p>Replace the URL with the <code>ApiEndpoint</code> value from your <code>cdk deploy</code> output. The <code>NEXT_PUBLIC_</code> prefix is required by Next.js to make an environment variable accessible in the browser.</p>
<p>You might be wondering: <strong>why not hardcode the URL</strong>? If you paste your API URL directly into your code and push it to GitHub, it becomes publicly visible. While an API URL alone does not expose your data (Cognito will protect that), it's good practice to keep URLs and secrets out of source control. Always use .env.local and add it to your .gitignore.</p>
<p>Make sure <code>.env.local</code> is in your <code>.gitignore</code>:</p>
<pre><code class="language-shell">echo ".env.local" &gt;&gt; frontend/.gitignore
</code></pre>
<p>Now create <code>frontend/lib/api.ts</code>:</p>
<pre><code class="language-typescript">import { Vendor } from '@/types/vendor';

const BASE_URL = process.env.NEXT_PUBLIC_API_URL!;

export const getVendors = async (): Promise&lt;Vendor[]&gt; =&gt; {
  const response = await fetch(`${BASE_URL}/vendors`);
  if (!response.ok) throw new Error('Failed to fetch vendors');
  return response.json();
};

export const createVendor = async (vendor: Omit&lt;Vendor, 'vendorId' | 'createdAt'&gt;): Promise&lt;void&gt; =&gt; {
  const response = await fetch(`${BASE_URL}/vendors`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(vendor),
  });
  if (!response.ok) throw new Error('Failed to create vendor');
};

export const deleteVendor = async (vendorId: string): Promise&lt;void&gt; =&gt; {
  const response = await fetch(`${BASE_URL}/vendors`, {
    method: 'DELETE',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ vendorId }),
  });
  if (!response.ok) throw new Error('Failed to delete vendor');
};
</code></pre>
<p><strong>What each part does:</strong></p>
<ul>
<li><p><code>Omit&lt;Vendor, 'vendorId' | 'createdAt'&gt;</code> means the <code>createVendor</code> function accepts a vendor without an ID or timestamp (those are generated server-side).</p>
</li>
<li><p><code>if (!response.ok) throw new Error(...)</code> ensures that any HTTP error (4xx or 5xx) surfaces as a JavaScript error in your component, where you can show the user a meaningful message instead of silently failing.</p>
</li>
</ul>
<p>You'll update these functions later in Part 8 to include the Cognito auth token.</p>
<h3 id="heading-73-build-the-main-page">7.3 Build the Main Page</h3>
<p>Now create the main page component. It includes a form for adding vendors and a live list that displays all current vendors.</p>
<p>Replace the contents of <code>frontend/app/page.tsx</code> with:</p>
<pre><code class="language-typescript">'use client';

import { useState, useEffect } from 'react';
import { createVendor, getVendors, deleteVendor } from '@/lib/api';
import { Vendor } from '@/types/vendor';

export default function Home() {
  const [vendors, setVendors] = useState&lt;Vendor[]&gt;([]);
  const [form, setForm] = useState({ name: '', category: '', contactEmail: '' });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const loadVendors = async () =&gt; {
    try {
      const data = await getVendors();
      setVendors(data);
    } catch {
      setError('Failed to load vendors.');
    }
  };

  // Load vendors once when the page first renders
  useEffect(() =&gt; {
    loadVendors();
  }, []);
  // The empty [] means this runs only once. Without it, the effect would
  // run after every render, causing an infinite loop of fetch requests.

  const handleSubmit = async (e: React.FormEvent) =&gt; {
    e.preventDefault(); // Prevent the browser from reloading the page on submit
    setLoading(true);
    setError('');
    try {
      await createVendor(form);
      setForm({ name: '', category: '', contactEmail: '' }); // Reset the form
      await loadVendors(); // Refresh the list from DynamoDB
    } catch {
      setError('Failed to add vendor. Please try again.');
    } finally {
      setLoading(false);
    }
  };

  const handleDelete = async (vendorId: string) =&gt; {
    try {
      await deleteVendor(vendorId);
      await loadVendors(); // Refresh after deleting
    } catch {
      setError('Failed to delete vendor.');
    }
  };

  return (
    &lt;main className="p-10 max-w-5xl mx-auto"&gt;
      &lt;h1 className="text-3xl font-bold mb-2 text-gray-900"&gt;Vendor Tracker&lt;/h1&gt;
      &lt;p className="text-gray-500 mb-8"&gt;Manage your vendors, stored in AWS DynamoDB.&lt;/p&gt;

      {error &amp;&amp; (
        &lt;div className="mb-4 p-3 bg-red-100 text-red-700 rounded"&gt;{error}&lt;/div&gt;
      )}

      &lt;div className="grid grid-cols-1 md:grid-cols-2 gap-10"&gt;

        {/* ── Add Vendor Form ── */}
        &lt;section&gt;
          &lt;h2 className="text-xl font-semibold mb-4 text-gray-800"&gt;Add New Vendor&lt;/h2&gt;
          &lt;form onSubmit={handleSubmit} className="space-y-4"&gt;
            &lt;input
              className="w-full p-2 border rounded text-black focus:outline-none focus:ring-2 focus:ring-orange-400"
              placeholder="Vendor Name"
              value={form.name}
              onChange={e =&gt; setForm({ ...form, name: e.target.value })}
              required
            /&gt;
            &lt;input
              className="w-full p-2 border rounded text-black focus:outline-none focus:ring-2 focus:ring-orange-400"
              placeholder="Category (e.g. SaaS, Hardware)"
              value={form.category}
              onChange={e =&gt; setForm({ ...form, category: e.target.value })}
              required
            /&gt;
            &lt;input
              className="w-full p-2 border rounded text-black focus:outline-none focus:ring-2 focus:ring-orange-400"
              placeholder="Contact Email"
              type="email"
              value={form.contactEmail}
              onChange={e =&gt; setForm({ ...form, contactEmail: e.target.value })}
              required
            /&gt;
            &lt;button
              type="submit"
              disabled={loading}
              className="w-full bg-orange-500 text-white p-2 rounded hover:bg-orange-600 disabled:bg-gray-400 transition-colors"
            &gt;
              {loading ? 'Saving...' : 'Add Vendor'}
            &lt;/button&gt;
          &lt;/form&gt;
        &lt;/section&gt;

        {/* ── Vendor List ── */}
        &lt;section&gt;
          &lt;h2 className="text-xl font-semibold mb-4 text-gray-800"&gt;
            Current Vendors ({vendors.length})
          &lt;/h2&gt;
          &lt;div className="space-y-3"&gt;
            {vendors.length === 0 ? (
              &lt;p className="text-gray-400 italic"&gt;No vendors yet. Add one using the form.&lt;/p&gt;
            ) : (
              vendors.map(v =&gt; (
                &lt;div
                  key={v.vendorId}
                  className="p-4 border rounded shadow-sm bg-white flex justify-between items-start"
                &gt;
                  &lt;div&gt;
                    &lt;p className="font-semibold text-gray-900"&gt;{v.name}&lt;/p&gt;
                    &lt;p className="text-sm text-gray-500"&gt;{v.category} · {v.contactEmail}&lt;/p&gt;
                  &lt;/div&gt;
                  &lt;button
                    onClick={() =&gt; v.vendorId &amp;&amp; handleDelete(v.vendorId)}
                    className="ml-4 text-sm text-red-500 hover:text-red-700 hover:underline"
                  &gt;
                    Delete
                  &lt;/button&gt;
                &lt;/div&gt;
              ))
            )}
          &lt;/div&gt;
        &lt;/section&gt;

      &lt;/div&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<p><strong>Key points in this component:</strong></p>
<ul>
<li><p><code>'use client'</code> at the top is a Next.js directive. It tells Next.js that this component uses browser APIs (<code>useState</code>, <code>useEffect</code>, event handlers) and must run in the browser, not be pre-rendered on the server.</p>
</li>
<li><p><code>e.preventDefault()</code> inside <code>handleSubmit</code> stops the browser's default form submission behavior, which would cause a full page reload and wipe your React state.</p>
</li>
<li><p>After every <code>createVendor</code> or <code>deleteVendor</code> call, <code>loadVendors()</code> is called again. This re-fetches the latest data from DynamoDB so the UI always matches what is actually stored in the database.</p>
</li>
</ul>
<h3 id="heading-74-test-the-app-locally">7.4 Test the App Locally</h3>
<p>Start your Next.js development server:</p>
<pre><code class="language-shell">cd frontend
npm run dev
</code></pre>
<p>Open <code>http://localhost:3000</code> in your browser. You should see the two-panel layout. Try adding a vendor and confirm it appears in the list.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/281f971a-27b8-49b3-9079-e12601525d80.png" alt="The running Vendor Tracker app at localhost:3000 showing the two-panel layout with the Add Vendor form on the left and an empty vendor list on the right" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/88b5dd74-5847-4310-bec3-b1a2b129fbaa.png" alt="The Vendor Tracker app after a vendor has been added, showing the vendor card in the list" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h4 id="heading-verifying-the-connection-to-aws">Verifying the connection to AWS:</h4>
<p>Open Chrome DevTools (F12) and click the Network tab. When you add a vendor, you should see:</p>
<ul>
<li><p>A <code>POST</code> request to your AWS API URL returning a <strong>201</strong> status code</p>
</li>
<li><p>A <code>GET</code> request returning <strong>200</strong> with the updated vendor list</p>
</li>
</ul>
<p>You can also verify the data was saved by opening the AWS Console, navigating to <strong>DynamoDB --&gt; Tables --&gt; VendorTable --&gt; Explore table items</strong>. Your vendor should appear there.</p>
<h2 id="heading-part-8-add-authentication-with-amazon-cognito">Part 8: Add Authentication with Amazon Cognito</h2>
<p>Right now your API is completely open. Anyone who finds your API URL can add or delete vendors. You'll fix that with <strong>Amazon Cognito</strong>.</p>
<p>Cognito is AWS's authentication service. It manages a User Pool – a database of registered users with usernames and passwords. When a user logs in, Cognito issues a JWT (JSON Web Token): a cryptographically signed string that proves who the user is. Your API Gateway will check for this token on every request. No valid token means no access.</p>
<p><strong>What is a JWT?</strong> A JSON Web Token is a string that looks like <code>eyJhbGci...</code>. It contains encoded information about the user and is signed by Cognito using a secret key.</p>
<p>API Gateway can verify the signature without contacting Cognito on every request, which makes token checking fast. Think of it as a tamper-proof badge: anyone can read the name on it, but only Cognito's signature makes it valid.</p>
<h3 id="heading-81-add-cognito-to-the-cdk-stack">8.1 Add Cognito to the CDK Stack</h3>
<p>Open <code>backend/lib/backend-stack.ts</code> and update it to include Cognito. Here is the complete updated file:</p>
<pre><code class="language-typescript">import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // ─── 1. DynamoDB Table ────────────────────────────────────────────────────
    const vendorTable = new dynamodb.Table(this, 'VendorTable', {
      partitionKey: { name: 'vendorId', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // ─── 2. Lambda Functions ──────────────────────────────────────────────────
    const lambdaEnv = { TABLE_NAME: vendorTable.tableName };

    const createVendorLambda = new NodejsFunction(this, 'CreateVendorHandler', {
      entry: 'lambda/createVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const getVendorsLambda = new NodejsFunction(this, 'GetVendorsHandler', {
      entry: 'lambda/getVendors.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const deleteVendorLambda = new NodejsFunction(this, 'DeleteVendorHandler', {
      entry: 'lambda/deleteVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    // ─── 3. Permissions ───────────────────────────────────────────────────────
    vendorTable.grantWriteData(createVendorLambda);
    vendorTable.grantReadData(getVendorsLambda);
    vendorTable.grantWriteData(deleteVendorLambda);

    // ─── 4. Cognito User Pool ─────────────────────────────────────────────────
    const userPool = new cognito.UserPool(this, 'VendorUserPool', {
      selfSignUpEnabled: true,
      signInAliases: { email: true },
      autoVerify: { email: true },
      userVerification: {
        emailStyle: cognito.VerificationEmailStyle.CODE,
      },
    });

    // Required to host Cognito's internal auth endpoints
    userPool.addDomain('VendorUserPoolDomain', {
      cognitoDomain: {
        domainPrefix: `vendor-tracker-${this.account}`,
      },
    });

    const userPoolClient = userPool.addClient('VendorAppClient');

    // ─── 5. API Gateway + Authorizer ──────────────────────────────────────────
    const api = new apigateway.RestApi(this, 'VendorApi', {
      restApiName: 'Vendor Service',
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ['Content-Type', 'Authorization'],
      },
    });

    const authorizer = new apigateway.CognitoUserPoolsAuthorizer(
      this,
      'VendorAuthorizer',
      { cognitoUserPools: [userPool] }
    );

    const authOptions = {
      authorizer,
      authorizationType: apigateway.AuthorizationType.COGNITO,
    };

    const vendors = api.root.addResource('vendors');
    vendors.addMethod('GET', new apigateway.LambdaIntegration(getVendorsLambda), authOptions);
    vendors.addMethod('POST', new apigateway.LambdaIntegration(createVendorLambda), authOptions);
    vendors.addMethod('DELETE', new apigateway.LambdaIntegration(deleteVendorLambda), authOptions);

    // ─── 6. Outputs ───────────────────────────────────────────────────────────
    new cdk.CfnOutput(this, 'ApiEndpoint', { value: api.url });
    new cdk.CfnOutput(this, 'UserPoolId', { value: userPool.userPoolId });
    new cdk.CfnOutput(this, 'UserPoolClientId', { value: userPoolClient.userPoolClientId });
  }
}
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/c5e91abf-e6af-429f-bf5b-b14d18233f6c.png" alt="The newly created User Pool (VendorUserPool...) in the User Pools list, with the User Pool ID visible" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p><strong>What changed:</strong></p>
<ul>
<li><p><code>CognitoUserPoolsAuthorizer</code> tells API Gateway to check every request for a valid Cognito JWT before passing it to any Lambda. If the token is missing or invalid, API Gateway rejects the request with a <code>401 Unauthorized</code> response without ever touching your Lambda.</p>
</li>
<li><p><code>authOptions</code> is applied to all three API methods: GET, POST, and DELETE. All routes are now protected.</p>
</li>
<li><p><code>autoVerify: { email: true }</code> tells Cognito to mark the email attribute as verified after a user confirms via the verification code email. It doesn't skip the verification email, as users still receive a code. If you want to skip verification during development, you can manually confirm users in the Cognito console (covered in section 8.5).</p>
</li>
<li><p>Two new <code>CfnOutput</code> values (<code>UserPoolId</code> and <code>UserPoolClientId</code>) will appear in your terminal after the next deployment. Your frontend needs them to connect to Cognito.</p>
</li>
</ul>
<p>Deploy the updated stack:</p>
<pre><code class="language-shell">cd backend
cdk deploy
</code></pre>
<p>After deployment, your terminal output will include three values:</p>
<pre><code class="language-plaintext">Outputs:
BackendStack.ApiEndpoint     = https://abc123.execute-api.us-east-1.amazonaws.com/prod/
BackendStack.UserPoolId      = us-east-1_xxxxxxxx
BackendStack.UserPoolClientId = xxxxxxxxxxxxxxxxxxxx
</code></pre>
<p>Save all three values. You'll use them in the next step.</p>
<h3 id="heading-82-install-and-configure-aws-amplify">8.2 Install and Configure AWS Amplify</h3>
<p><strong>AWS Amplify</strong> is a frontend library that handles all the complex authentication logic for you: it manages the login UI, stores tokens in the browser, refreshes expired tokens automatically, and exposes a simple API to read the current user's session.</p>
<p>Install the Amplify libraries inside your <code>frontend</code> folder:</p>
<pre><code class="language-shell">cd frontend
npm install aws-amplify @aws-amplify/ui-react
</code></pre>
<p>Create <code>frontend/app/providers.tsx</code>. This file initializes Amplify with your Cognito configuration. It runs once when the app loads:</p>
<pre><code class="language-typescript">'use client';

import { Amplify } from 'aws-amplify';

Amplify.configure(
  {
    Auth: {
      Cognito: {
        userPoolId: process.env.NEXT_PUBLIC_USER_POOL_ID!,
        userPoolClientId: process.env.NEXT_PUBLIC_USER_POOL_CLIENT_ID!,
      },
    },
  },
  { ssr: true }
);

export function Providers({ children }: { children: React.ReactNode }) {
  return &lt;&gt;{children}&lt;/&gt;;
}
</code></pre>
<p>Add the Cognito IDs to your <code>frontend/.env.local</code> file:</p>
<pre><code class="language-shell">NEXT_PUBLIC_API_URL=https://abc123.execute-api.us-east-1.amazonaws.com/prod
NEXT_PUBLIC_USER_POOL_ID=us-east-1_xxxxxxxx
NEXT_PUBLIC_USER_POOL_CLIENT_ID=xxxxxxxxxxxxxxxxxxxx
</code></pre>
<p>Replace the values with the outputs from your <code>cdk deploy</code>.</p>
<h3 id="heading-83-wire-providers-into-the-app-layout">8.3 Wire Providers into the App Layout</h3>
<p><strong>This step is critical.</strong> Amplify must be initialized before any component tries to use authentication. If you skip this step, <code>fetchAuthSession()</code> will throw an "Amplify not configured" error and nothing will work.</p>
<p>Open <code>frontend/app/layout.tsx</code> and update it to wrap the app in the <code>Providers</code> component:</p>
<pre><code class="language-typescript">import type { Metadata } from 'next';
import './globals.css';
import { Providers } from './providers';

export const metadata: Metadata = {
  title: 'Vendor Tracker',
  description: 'Manage your vendors with AWS',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    &lt;html lang="en"&gt;
      &lt;body&gt;
        &lt;Providers&gt;{children}&lt;/Providers&gt;
      &lt;/body&gt;
    &lt;/html&gt;
  );
}
</code></pre>
<p>By wrapping <code>{children}</code> in <code>&lt;Providers&gt;</code>, you ensure that Amplify is configured once at the root of the app, before any child page or component renders.</p>
<h3 id="heading-84-protect-the-ui-with-withauthenticator">8.4 Protect the UI with withAuthenticator</h3>
<p>Now wrap your <code>Home</code> component so that unauthenticated users see a login screen instead of the dashboard.</p>
<p>Replace the contents of <code>frontend/app/page.tsx</code> with this updated version:</p>
<pre><code class="language-typescript">'use client';

import { useState, useEffect } from 'react';
import { withAuthenticator } from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';
import { getVendors, createVendor, deleteVendor } from '@/lib/api';
import { Vendor } from '@/types/vendor';

// withAuthenticator injects `signOut` and `user` as props automatically
function Home({ signOut, user }: { signOut?: () =&gt; void; user?: any }) {
  const [vendors, setVendors] = useState&lt;Vendor[]&gt;([]);
  const [form, setForm] = useState({ name: '', category: '', contactEmail: '' });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');

  const loadVendors = async () =&gt; {
    try {
      const data = await getVendors();
      setVendors(data);
    } catch {
      setError('Failed to load vendors.');
    }
  };

  useEffect(() =&gt; {
    loadVendors();
  }, []);

  const handleSubmit = async (e: React.FormEvent) =&gt; {
    e.preventDefault();
    setLoading(true);
    setError('');
    try {
      await createVendor(form);
      setForm({ name: '', category: '', contactEmail: '' });
      await loadVendors();
    } catch {
      setError('Failed to add vendor.');
    } finally {
      setLoading(false);
    }
  };

  const handleDelete = async (vendorId: string) =&gt; {
    try {
      await deleteVendor(vendorId);
      await loadVendors();
    } catch {
      setError('Failed to delete vendor.');
    }
  };

  return (
    &lt;main className="p-10 max-w-5xl mx-auto"&gt;
      {/* ── Header ── */}
      &lt;header className="flex justify-between items-center mb-8 p-4 bg-gray-100 rounded"&gt;
        &lt;div&gt;
          &lt;h1 className="text-xl font-bold text-gray-900"&gt;Vendor Tracker&lt;/h1&gt;
          &lt;p className="text-sm text-gray-500"&gt;Signed in as: {user?.signInDetails?.loginId}&lt;/p&gt;
        &lt;/div&gt;
        &lt;button
          onClick={signOut}
          className="bg-red-500 text-white px-4 py-2 rounded hover:bg-red-600 transition-colors"
        &gt;
          Sign Out
        &lt;/button&gt;
      &lt;/header&gt;

      {error &amp;&amp; (
        &lt;div className="mb-4 p-3 bg-red-100 text-red-700 rounded"&gt;{error}&lt;/div&gt;
      )}

      &lt;div className="grid grid-cols-1 md:grid-cols-2 gap-10"&gt;

        {/* ── Add Vendor Form ── */}
        &lt;section&gt;
          &lt;h2 className="text-xl font-semibold mb-4 text-gray-800"&gt;Add New Vendor&lt;/h2&gt;
          &lt;form onSubmit={handleSubmit} className="space-y-4"&gt;
            &lt;input
              className="w-full p-2 border rounded text-black"
              placeholder="Vendor Name"
              value={form.name}
              onChange={e =&gt; setForm({ ...form, name: e.target.value })}
              required
            /&gt;
            &lt;input
              className="w-full p-2 border rounded text-black"
              placeholder="Category (e.g. SaaS, Hardware)"
              value={form.category}
              onChange={e =&gt; setForm({ ...form, category: e.target.value })}
              required
            /&gt;
            &lt;input
              className="w-full p-2 border rounded text-black"
              placeholder="Contact Email"
              type="email"
              value={form.contactEmail}
              onChange={e =&gt; setForm({ ...form, contactEmail: e.target.value })}
              required
            /&gt;
            &lt;button
              type="submit"
              disabled={loading}
              className="w-full bg-orange-500 text-white p-2 rounded hover:bg-orange-600 disabled:bg-gray-400"
            &gt;
              {loading ? 'Saving...' : 'Add Vendor'}
            &lt;/button&gt;
          &lt;/form&gt;
        &lt;/section&gt;

        {/* ── Vendor List ── */}
        &lt;section&gt;
          &lt;h2 className="text-xl font-semibold mb-4 text-gray-800"&gt;
            Current Vendors ({vendors.length})
          &lt;/h2&gt;
          &lt;div className="space-y-3"&gt;
            {vendors.length === 0 ? (
              &lt;p className="text-gray-400 italic"&gt;No vendors yet.&lt;/p&gt;
            ) : (
              vendors.map(v =&gt; (
                &lt;div
                  key={v.vendorId}
                  className="p-4 border rounded shadow-sm bg-white flex justify-between items-start"
                &gt;
                  &lt;div&gt;
                    &lt;p className="font-semibold text-gray-900"&gt;{v.name}&lt;/p&gt;
                    &lt;p className="text-sm text-gray-500"&gt;{v.category} · {v.contactEmail}&lt;/p&gt;
                  &lt;/div&gt;
                  &lt;button
                    onClick={() =&gt; v.vendorId &amp;&amp; handleDelete(v.vendorId)}
                    className="ml-4 text-sm text-red-500 hover:text-red-700 hover:underline"
                  &gt;
                    Delete
                  &lt;/button&gt;
                &lt;/div&gt;
              ))
            )}
          &lt;/div&gt;
        &lt;/section&gt;

      &lt;/div&gt;
    &lt;/main&gt;
  );
}

// Wrapping Home with withAuthenticator means any user who is not logged in
// will see Amplify's built-in login/signup screen instead of this component.
export default withAuthenticator(Home);
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/e65a88dc-ea75-4daa-b7cf-eac3406c8060.png" alt="Amplify-generated login screen" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h3 id="heading-85-pass-the-auth-token-to-api-calls">8.5 Pass the Auth Token to API Calls</h3>
<p>Now that API Gateway requires a JWT on every request, your <code>fetch</code> calls need to include the token in the <code>Authorization</code> header. Without it, every request will return a <code>401 Unauthorized</code> error.</p>
<p>Update <code>frontend/lib/api.ts</code> with a token helper and updated fetch calls:</p>
<pre><code class="language-typescript">import { fetchAuthSession } from 'aws-amplify/auth';
import { Vendor } from '@/types/vendor';

const BASE_URL = process.env.NEXT_PUBLIC_API_URL!;

// Retrieves the current user's JWT token from the active Amplify session
const getAuthToken = async (): Promise&lt;string&gt; =&gt; {
  const session = await fetchAuthSession();
  const token = session.tokens?.idToken?.toString();
  if (!token) throw new Error('No active session. Please sign in.');
  return token;
};

export const getVendors = async (): Promise&lt;Vendor[]&gt; =&gt; {
  const token = await getAuthToken();
  const response = await fetch(`${BASE_URL}/vendors`, {
    headers: { Authorization: token },
  });
  if (!response.ok) throw new Error('Failed to fetch vendors');
  return response.json();
};

export const createVendor = async (
  vendor: Omit&lt;Vendor, 'vendorId' | 'createdAt'&gt;
): Promise&lt;void&gt; =&gt; {
  const token = await getAuthToken();
  const response = await fetch(`${BASE_URL}/vendors`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: token,
    },
    body: JSON.stringify(vendor),
  });
  if (!response.ok) throw new Error('Failed to create vendor');
};

export const deleteVendor = async (vendorId: string): Promise&lt;void&gt; =&gt; {
  const token = await getAuthToken();
  const response = await fetch(`${BASE_URL}/vendors`, {
    method: 'DELETE',
    headers: {
      'Content-Type': 'application/json',
      Authorization: token,
    },
    body: JSON.stringify({ vendorId }),
  });
  if (!response.ok) throw new Error('Failed to delete vendor');
};
</code></pre>
<p><strong>What</strong> <code>getAuthToken</code> <strong>does:</strong></p>
<p><code>fetchAuthSession()</code> reads the currently logged-in user's session from the browser. Amplify stores the session in memory and <code>localStorage</code> after the user signs in.</p>
<p><code>session.tokens?.idToken</code> is the JWT string that API Gateway's Cognito Authorizer is looking for. Passing it as the <code>Authorization</code> header tells API Gateway: "This request is from an authenticated user."</p>
<h3 id="heading-86-troubleshooting-cognito">8.6 Troubleshooting Cognito</h3>
<h4 id="heading-unconfirmed-user-error-after-sign-up">"Unconfirmed" user error after sign-up</h4>
<p>When a new user signs up through the Amplify UI, Cognito marks the account as <em>Unconfirmed</em> until the user verifies their email address. A verification code is sent to the user's email. After entering the code, the account becomes confirmed and the user can log in.</p>
<p>If you are testing locally and want to skip the email step, you can manually confirm any account in the AWS Console:</p>
<ol>
<li><p>Open the AWS Console and navigate to Cognito</p>
</li>
<li><p>Click on your User Pool (<code>VendorUserPool...</code>)</p>
</li>
<li><p>Click the Users tab</p>
</li>
<li><p>Click on the user's email address</p>
</li>
<li><p>Open the Actions dropdown and click Confirm account</p>
</li>
</ol>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/158fb773-9cb1-4c14-9fd7-49e4369ba7e3.png" alt=" Cognito Users list showing a user with &quot;Unconfirmed&quot; status" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/5637ac80-ee0c-4fdf-93cf-d4b7d71f6a65.png" alt="Cognito Users list showing a user with &quot;Unconfirmed&quot; status" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h4 id="heading-401-unauthorized-errors-after-deployment">401 Unauthorized errors after deployment</h4>
<p>If you are getting 401 errors, check two things:</p>
<ol>
<li><p>Open Chrome DevTools --&gt; Network tab, click the failing request, and look at the <strong>Request Headers</strong>. You should see an <code>Authorization</code> header with a long string of characters. If it is missing, <code>getAuthToken</code> is failing. Check that Amplify is configured correctly in <code>providers.tsx</code> and wired in via <code>layout.tsx</code>.</p>
</li>
<li><p>In your CDK stack, confirm that <code>authorizationType: apigateway.AuthorizationType.COGNITO</code> is present on every protected method definition. If it is missing, API Gateway may not be checking tokens even though the authorizer is defined.</p>
</li>
</ol>
<h2 id="heading-part-9-deploy-the-frontend-with-s3-and-cloudfront">Part 9: Deploy the Frontend with S3 and CloudFront</h2>
<p>Your app works locally. Now you'll deploy it to a real HTTPS URL that anyone in the world can visit.</p>
<p><strong>The strategy:</strong> Next.js will export your React app as a set of static HTML, CSS, and JavaScript files. Those files will be uploaded to an <strong>S3 bucket</strong> (AWS's file storage service). <strong>CloudFront</strong> sits in front of the bucket as a Content Delivery Network (CDN), distributing your files to servers around the world and serving them over HTTPS.</p>
<h3 id="heading-91-configure-nextjs-for-static-export">9.1 Configure Next.js for Static Export</h3>
<p>Open <code>frontend/next.config.js</code> (or <code>next.config.mjs</code>) and add the <code>output: 'export'</code> setting:</p>
<pre><code class="language-javascript">/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // Generates a static /out folder instead of a Node.js server
};

export default nextConfig;
</code></pre>
<p><strong>Note on 'use client' and static export</strong>: When output: 'export' is set, Next.js builds every page at compile time. Any component that uses browser-only APIs – like withAuthenticator from Amplify – must have 'use client' at the top of the file. This tells Next.js to skip server-side rendering for that component and run it only in the browser.</p>
<p>You already have 'use client' in page.tsx. If you ever see a build error mentioning window is not defined or similar, check that the relevant component has 'use client' at the top.</p>
<p>Build the frontend:</p>
<pre><code class="language-shell">cd frontend
npm run build
</code></pre>
<p>This generates an <code>/out</code> folder containing your complete website as static files. Verify the folder was created:</p>
<pre><code class="language-shell">ls out
# You should see: index.html, _next/, etc.
</code></pre>
<h3 id="heading-92-add-s3-and-cloudfront-to-the-cdk-stack">9.2 Add S3 and CloudFront to the CDK Stack</h3>
<p>Open <code>backend/lib/backend-stack.ts</code> and add the hosting infrastructure. Here's the complete final version of the file:</p>
<pre><code class="language-typescript">import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';

export class BackendStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // 1. DynamoDB Table 
    const vendorTable = new dynamodb.Table(this, 'VendorTable', {
      partitionKey: { name: 'vendorId', type: dynamodb.AttributeType.STRING },
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // 2. Lambda Functions
    const lambdaEnv = { TABLE_NAME: vendorTable.tableName };

    const createVendorLambda = new NodejsFunction(this, 'CreateVendorHandler', {
      entry: 'lambda/createVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const getVendorsLambda = new NodejsFunction(this, 'GetVendorsHandler', {
      entry: 'lambda/getVendors.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    const deleteVendorLambda = new NodejsFunction(this, 'DeleteVendorHandler', {
      entry: 'lambda/deleteVendor.ts',
      handler: 'handler',
      environment: lambdaEnv,
    });

    // 3. Permissions
    vendorTable.grantWriteData(createVendorLambda);
    vendorTable.grantReadData(getVendorsLambda);
    vendorTable.grantWriteData(deleteVendorLambda);

    // 4. Cognito User Pool
    const userPool = new cognito.UserPool(this, 'VendorUserPool', {
      selfSignUpEnabled: true,
      signInAliases: { email: true },
      autoVerify: { email: true },
      userVerification: {
        emailStyle: cognito.VerificationEmailStyle.CODE,
      },
    });

    userPool.addDomain('VendorUserPoolDomain', {
      cognitoDomain: { domainPrefix: `vendor-tracker-${this.account}` },
    });

    const userPoolClient = userPool.addClient('VendorAppClient');

    // 5. API Gateway + Authorizer
    const api = new apigateway.RestApi(this, 'VendorApi', {
      restApiName: 'Vendor Service',
      defaultCorsPreflightOptions: {
        allowOrigins: apigateway.Cors.ALL_ORIGINS,
        allowMethods: apigateway.Cors.ALL_METHODS,
        allowHeaders: ['Content-Type', 'Authorization'],
      },
    });

    const authorizer = new apigateway.CognitoUserPoolsAuthorizer(
      this,
      'VendorAuthorizer',
      { cognitoUserPools: [userPool] }
    );

    const authOptions = {
      authorizer,
      authorizationType: apigateway.AuthorizationType.COGNITO,
    };

    const vendors = api.root.addResource('vendors');
    vendors.addMethod('GET', new apigateway.LambdaIntegration(getVendorsLambda), authOptions);
    vendors.addMethod('POST', new apigateway.LambdaIntegration(createVendorLambda), authOptions);
    vendors.addMethod('DELETE', new apigateway.LambdaIntegration(deleteVendorLambda), authOptions);

    // 6. S3 Bucket (Frontend Files) 
    const siteBucket = new s3.Bucket(this, 'VendorSiteBucket', {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // 7. CloudFront Distribution (HTTPS + CDN)
    const distribution = new cloudfront.Distribution(this, 'SiteDistribution', {
      defaultBehavior: {
        origin: new origins.S3Origin(siteBucket),
        viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
      },
      defaultRootObject: 'index.html',
      errorResponses: [
        {
          // Redirect all 404s back to index.html so React can handle routing
          httpStatus: 404,
          responseHttpStatus: 200,
          responsePagePath: '/index.html',
        },
      ],
    });

    // 8. Deploy Frontend Files to S3 
    new s3deploy.BucketDeployment(this, 'DeployWebsite', {
      sources: [s3deploy.Source.asset('../frontend/out')],
      destinationBucket: siteBucket,
      distribution,
      distributionPaths: ['/*'], // Clears CloudFront cache on every deploy
    });

    // 9. Outputs ───────────────────────────────────────────────────────────
    new cdk.CfnOutput(this, 'ApiEndpoint', { value: api.url });
    new cdk.CfnOutput(this, 'UserPoolId', { value: userPool.userPoolId });
    new cdk.CfnOutput(this, 'UserPoolClientId', { value: userPoolClient.userPoolClientId });
    new cdk.CfnOutput(this, 'CloudFrontURL', {
      value: `https://${distribution.distributionDomainName}`,
    });
  }
}
</code></pre>
<p><strong>What the hosting infrastructure does:</strong></p>
<ul>
<li><p>The <strong>S3 bucket</strong> stores your static HTML, CSS, and JavaScript files. It is private – users cannot access it directly.</p>
</li>
<li><p><strong>CloudFront</strong> is the CDN that sits in front of S3. It gives you an HTTPS URL and caches your files at edge locations worldwide, so the app loads fast no matter where users are located. <code>REDIRECT_TO_HTTPS</code> automatically upgrades any HTTP request to HTTPS.</p>
</li>
<li><p>The <strong>error response</strong> for 404 returns <code>index.html</code> instead of an error page. This is necessary for single-page apps: if a user navigates directly to a route like <code>/vendors/123</code>, CloudFront cannot find a file at that path, but sending back <code>index.html</code> lets the React app handle the routing correctly.</p>
</li>
<li><p><code>distributionPaths: ['/*']</code> tells CloudFront to invalidate its entire cache after every deployment. This ensures users always see the latest version of your app immediately.</p>
</li>
<li><p><code>BucketDeployment</code> is a CDK construct that automatically uploads the contents of your <code>frontend/out</code> folder to the S3 bucket every time you run <code>cdk deploy</code>.</p>
</li>
</ul>
<h3 id="heading-93-run-the-final-deployment">9.3 Run the Final Deployment</h3>
<p>First, build the frontend with the latest environment variables:</p>
<pre><code class="language-shell">cd frontend
npm run build
</code></pre>
<p>Then deploy everything from the backend folder:</p>
<pre><code class="language-shell">cd ../backend
cdk deploy
</code></pre>
<p>After deployment finishes, copy the <code>CloudFrontURL</code> from the terminal output:</p>
<pre><code class="language-plaintext">Outputs:
BackendStack.CloudFrontURL = https://d1234abcd.cloudfront.net
</code></pre>
<p>Open that URL in your browser. Your app is now live on the internet, served over HTTPS, globally distributed.</p>
<img src="https://cdn.hashnode.com/uploads/covers/62d53ab5bc2c7a1dc672b04f/f8e14979-a667-4afc-bdd4-9afe4abd9593.png" alt="f8e14979-a667-4afc-bdd4-9afe4abd9593" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<h2 id="heading-what-you-built">What You Built</h2>
<p>You now have a fully deployed, production-style full-stack application. Here is a summary of every piece you built and what it does:</p>
<table>
<thead>
<tr>
<th>Layer</th>
<th>Service</th>
<th>What it does</th>
</tr>
</thead>
<tbody><tr>
<td>Frontend</td>
<td>Next.js + CloudFront</td>
<td>React UI served globally over HTTPS</td>
</tr>
<tr>
<td>Auth</td>
<td>Amazon Cognito + Amplify</td>
<td>User sign-up, login, and JWT token management</td>
</tr>
<tr>
<td>API</td>
<td>API Gateway</td>
<td>Routes HTTP requests, validates auth tokens</td>
</tr>
<tr>
<td>Logic</td>
<td>AWS Lambda (×3)</td>
<td>Creates, reads, and deletes vendors on demand</td>
</tr>
<tr>
<td>Database</td>
<td>DynamoDB</td>
<td>Stores vendor records with no idle cost</td>
</tr>
<tr>
<td>Storage</td>
<td>S3</td>
<td>Holds your built frontend files</td>
</tr>
<tr>
<td>Infrastructure</td>
<td>AWS CDK</td>
<td>Defines and deploys all of the above as code</td>
</tr>
</tbody></table>
<h2 id="heading-conclusion">Conclusion</h2>
<p>You have built and deployed the foundational pattern of almost every cloud application: a secured API backed by a database, deployed with infrastructure as code. Here is everything you accomplished:</p>
<p>You set up a professional AWS development environment with scoped IAM credentials. You defined your entire backend infrastructure as TypeScript code using AWS CDK, which means your database, API, Lambda functions, and authentication system are all version-controlled, repeatable, and deployable with a single command.</p>
<p>You wrote three Lambda functions that handle create, read, and delete operations, each with proper error handling and the correct AWS SDK v3 patterns. You connected them to a REST API through API Gateway and protected every route with Amazon Cognito authentication, so only registered, verified users can interact with your data.</p>
<p>On the frontend, you built a Next.js application with a service layer that cleanly separates API logic from UI components, manages JWTs automatically through AWS Amplify, and gives users a complete sign-up and sign-in flow without you writing a single line of authentication UI code.</p>
<p>Finally, you deployed the entire system: your backend to AWS Lambda and DynamoDB, and your frontend as a static site served globally through CloudFront over HTTPS.</p>
<p>The full source code for this tutorial is available on <a href="https://github.com/BenedictaUche/vendor-tracker">GitHub</a>. Clone it, modify it, and use it as a reference for your own projects.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Test a Complex Full-Stack App: Manual Approach vs AI-Assisted Testing ]]>
                </title>
                <description>
                    <![CDATA[ A few days ago, I ran an experiment with an AI-powered testing agent that lets you write test cases in plain English instead of code. I opened its natural language interface and typed four simple sent ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-test-a-complex-full-stack-app-manual-vs-ai-assisted-testing/</link>
                <guid isPermaLink="false">69b843852ad6ae5184d6fa75</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Testing ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ajay Yadav ]]>
                </dc:creator>
                <pubDate>Mon, 16 Mar 2026 17:53:09 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/uploads/covers/5e1e335a7a1d3fcc59028c64/3970744b-194e-4573-b49a-c057a4632d8c.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>A few days ago, I ran an experiment with an AI-powered testing agent that lets you write test cases in plain English instead of code. I opened its natural language interface and typed four simple sentences to test google.com:</p>
<pre><code class="language-plaintext">1. Go to google.com
2. There should be a long input field on the page
3. Type something and verify suggestions appear in a dropdown
4. The input field should not have any placeholder text
</code></pre>
<img src="https://cdn.hashnode.com/uploads/covers/6198d3da5bb9cc256fc69512/24f353d9-8c98-49a9-ba81-3e236546dab2.png" alt="KaneAI's natural language test authoring interface showing a text input field with the prompt &quot;What do you want to test today?" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>A real browser opened Google, found the search bar, typed a query, checked for the autocomplete dropdown, and verified there was no placeholder, all from those four lines.</p>
<p>No Playwright selectors. No <code>page.getByRole()</code>. No CSS class names. Just plain English describing what a user would do.</p>
<p>That made me curious: what happens if I try this on something actually complex? So I tested my own full-stack app's auth endpoint the same way:</p>
<blockquote>
<p><em><strong>Send a GET request to /api/auth/status without any session cookie. Verify it returns 401.</strong></em></p>
</blockquote>
<p>Within 15 seconds, done.</p>
<p>The same test took me an hour to set up manually, building a session helper, separating my Express app from the server startup, seeding a test database, just so I could write five lines of Supertest code.</p>
<p>I ended up testing my entire application both ways: the traditional manual approach and the AI-assisted approach. Same endpoints, same assertions, completely different experience. This article is about what I learned.</p>
<p>But before I get into how I tested it, let's talk about what actually matters: the testing concepts themselves. Because no approach, manual or automated, will save you time or energy if you don't understand what you're testing and why.</p>
<h3 id="heading-what-well-cover">What we'll cover:</h3>
<ol>
<li><p><a href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a href="#heading-how-testing-actually-works-in-fullstack-apps">How Testing Actually Works in Full-Stack Apps</a></p>
</li>
<li><p><a href="#heading-what-makes-this-hard">What Made This Hard</a></p>
</li>
<li><p><a href="#heading-the-manual-approaach">The Manual Approach</a></p>
</li>
<li><p><a href="#heading-the-aiassisted-approach">The AI-Assisted Approach</a></p>
</li>
<li><p><a href="#heading-when-to-use-which-approach">When to Use Which Approach</a></p>
</li>
<li><p><a href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To get the most out of this article, you should have a basic understanding of JavaScript and Node.js, along with some familiarity with React and Express.</p>
<p>Experience writing simple tests with any JavaScript testing framework like Jest or Vitest will be helpful, though I'll explain the core testing concepts as we go.</p>
<p>You should also have Node.js installed on your machine. If you want to follow along with the manual testing examples, you'll need Vitest (or Jest) for unit and API tests, Supertest for HTTP endpoint testing, and Playwright for end-to-end browser tests. For the AI-assisted approach, I used KaneAI by LambdaTest, which you can explore through their platform.</p>
<h2 id="heading-how-testing-actually-works-in-full-stack-apps">How Testing Actually Works in Full-Stack Apps</h2>
<p>If you've only tested isolated React components or written a few unit tests for utility functions, full-stack testing feels like a different sport. The concepts are the same, but the complexity jumps dramatically. Here's what you actually need to know.</p>
<h3 id="heading-three-layers-three-different-jobs">Three Layers, Three Different Jobs</h3>
<p>Every full-stack application has three natural testing layers, and trying to cover everything with just one of them leads to either fragile tests or blind spots.</p>
<p>Unit Tests</p>
<p>Unit tests check that individual functions return the right output for a given input. They don't touch the database, the network, or the browser.</p>
<p>They run in milliseconds. If your function takes a string and returns a formatted slug, a unit test calls that function and checks the result. That's it.</p>
<pre><code class="language-ts">it("converts a title to a slug", () =&gt; {
  expect(slugify("My First Post")).toBe("my-first-post");
});
</code></pre>
<h4 id="heading-api-tests">API Tests</h4>
<p>API tests check that your backend endpoints return the right responses. They send real HTTP requests to your Express (or Next.js) app and verify the status codes, response shapes, and error handling.</p>
<p>If your <code>/api/auth/status</code> endpoint should return 401 without a session cookie, an API test confirms that contract.</p>
<pre><code class="language-ts">it("returns 401 without session cookie", async () =&gt; {
  const res = await request(app).get("/api/auth/status");
  expect(res.status).toBe(401);
});
</code></pre>
<h4 id="heading-end-to-end-e2e-tests">End-to-end (E2E) Tests</h4>
<p>End-to-end (E2E) tests open a real browser and interact with your app the way a user would. They click buttons, fill forms, navigate pages, and check that the right things appear on screen.</p>
<p>If your login flow should redirect to a dashboard after authentication, an E2E test walks through that entire journey.</p>
<pre><code class="language-ts">test("login redirects to dashboard", async ({ page }) =&gt; {
  await page.goto("/");
  await page.getByTestId("username-input").fill("ajay");
  await page.getByTestId("password-input").fill("password123");
  await page.getByTestId("login-button").click();
  await expect(page.getByTestId("dashboard")).toBeVisible();
});
</code></pre>
<h3 id="heading-the-pain-points-nobody-warns-you-about">The Pain Points Nobody Warns You About</h3>
<p>Tutorials make all three layers look straightforward. In practice, each one has a trap.</p>
<p>First, we have the session cookie problem. Most real apps have authentication. To test any authenticated endpoint, you need a valid session.</p>
<p>That means you need a helper function that logs in a test user, extracts the session cookie from the <code>Set-Cookie</code> header, and returns it for future requests.</p>
<p>This sounds simple. It took me an hour to build one that actually works with express-session. Every project reinvents this wheel.</p>
<p>Then we have the app vs. server separation issue. <a href="https://github.com/forwardemail/supertest#readme">Supertest</a> (the most popular API testing library) needs to import your Express app without starting a real server.</p>
<p>If your <code>app.ts</code> file has <code>app.listen(3000)</code> at the bottom, Supertest will try to bind to port 3000, and your tests will crash when running in parallel.</p>
<p>You have to separate your app definition from the server startup. <code>app.ts</code> exports the Express instance, <code>server.ts</code> calls <code>.listen()</code>. It's a three-minute refactor, but nobody tells you about it until your tests fail.</p>
<p>You also have the SSE and real-time nightmare. If your app uses Server-Sent Events (SSE) or WebSockets, you're testing time-dependent behavior.</p>
<p>You open a connection, trigger an action, and wait for an event to arrive. If the event takes too long, your test times out. If you don't set a timeout, the test hangs forever. You end up writing 30 lines of Promise wrappers, timeout handlers, and cleanup logic for a single assertion.</p>
<p>Finally, there's the selector fragility trap. E2E tests that use CSS selectors (<code>.btn-primary</code>, <code>.card-title</code>) break every time you rename a class.</p>
<p>The fix is using <code>data-testid</code> attributes, stable identifiers that exist solely for testing and don't change during refactors. But retrofitting them into an existing app means touching dozens of components.</p>
<h3 id="heading-schema-validation-the-hidden-time-sink">Schema Validation: The Hidden Time Sink</h3>
<p>Here's something nobody tells you about API testing. Writing the assertion for "does this endpoint return 200" takes one line.</p>
<p>Writing assertions that verify the shape of the response, every field exists, every field has the right type, every enum value is valid, takes 15 to 20 lines per endpoint. Multiply that across a dozen endpoints and you're spending hours writing boilerplate like:</p>
<pre><code class="language-ts">expect(res.body[0]).toHaveProperty("title");
expect(typeof res.body[0].title).toBe("string");
expect(res.body[0]).toHaveProperty("status");
expect(["open", "closed", "merged"]).toContain(res.body[0].status);
</code></pre>
<p>It's important work, though: schema validation catches real bugs when your backend changes a response shape. But the repetitiveness is what makes it a good candidate for automation, which I'll get to later.</p>
<p>These aren't edge cases. These are the everyday realities of testing a full-stack app. Knowing them upfront saves you from the "why is this so much harder than the tutorial??" frustration.</p>
<h2 id="heading-what-made-this-hard">What Made This Hard</h2>
<p>A few months ago, I wrote a <a href="https://www.freecodecamp.org/news/how-to-test-javascript-apps-from-unit-tests-to-ai-augmented-qa/">freeCodeCamp article</a> about testing JavaScript apps from unit tests to AI-augmented QA. That article covered testing fundamentals with clean, simple examples.</p>
<p>After publishing it, I kept thinking: what happens when you apply all of this to something messy?</p>
<p>I had the perfect candidate. <strong>Creoper</strong> (code name) is an AI-powered project management tool I built that connects GitHub with Discord.</p>
<p>Teams can monitor repositories, track pull requests, and query project status using natural language, all without leaving their chat platform.</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/6198d3da5bb9cc256fc69512/57f5c35a-20fc-483e-b871-e1f55632b683.png" alt="Ajay Yadav receiving &quot;The Visionary&quot; trophy at the Hatch&amp;Hype hackathon hosted at Montrose Golf Resort and Spa, alongside a close-up of the award celebrating bold innovation with the CreoWis logo" style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>I built it across two internal hackathons at <a href="https://www.creowis.com/">CreoWis</a>, and it won both times. What started as a simple GitHub-Discord automation bot evolved into a full product with five interconnected components:</p>
<img src="https://cloudmate-test.s3.us-east-1.amazonaws.com/uploads/covers/6198d3da5bb9cc256fc69512/b4881ec0-b5bf-4b80-b85d-ffd400240b41.png" alt="Architecture diagram of Creoper showing six interconnected components: React dashboard, Express backend, Discord bot, PostgreSQL database, GitHub webhook handlers, and LLM layer." style="display:block;margin:0 auto" width="600" height="400" loading="lazy">

<p>It has a React dashboard with GitHub OAuth. An Express backend with REST APIs and SSE. A Discord bot that processes natural language through an LLM intent detection layer. PostgreSQL with Prisma. GitHub webhook handlers.</p>
<p>But here's the thing: despite winning two hackathons, Creoper had <strong>zero test cases</strong>. The app wasn't even deployed yet. I'd been stuck on Railway monorepo deployment issues for weeks.</p>
<p>So I was staring at a system that had every real-world testing challenge I'd just written about, auth flows, real-time events, multiple integration points, complex business logic, and no safety net at all.</p>
<p>I decided to test it two different ways and document what actually happened. If you want to explore the full project, I've written two separate <a href="https://www.creowis.com/blog/building-an-ai-powered-project-management-tool">blogs</a> about how I built it.</p>
<h2 id="heading-the-manual-approach">The Manual Approach</h2>
<p>I mapped pure logic components like the intent parser and embed builder to unit tests, since they deal with straightforward input-output behavior. I assigned Express endpoints to API tests using Supertest, which let me send real HTTP requests and verify response codes and shapes.</p>
<p>I planned to cover the React dashboard with end-to-end tests using Playwright, simulating actual user interactions in a real browser. As for Discord bot interactions and webhook delivery, those couldn't be automated reliably yet, so I documented them and tested them manually.</p>
<p>Here's what each layer looked like in practice.</p>
<h3 id="heading-unit-tests-the-easy-win">Unit Tests: The Easy Win</h3>
<p>Creoper has a function that classifies Discord messages into structured intents. If someone types "list prs," it should return <code>LIST_PRS</code> with a high confidence score.</p>
<p>If the message is gibberish, it should return <code>UNKNOWN</code> with zero confidence. The confidence score matters because anything below a threshold triggers a safe fallback instead of executing an action.</p>
<pre><code class="language-ts">it("detects LIST_PRS intent", () =&gt; {
  const result = parseIntent("list prs");
  expect(result.action).toBe("LIST_PRS");
  expect(result.confidence).toBeGreaterThan(0.8);
});

it("returns low confidence when repo name is missing", () =&gt; {
  const result = parseIntent("set active repo");
  expect(result.confidence).toBeLessThan(0.8);
});
</code></pre>
<p>Notice these aren't just <strong>"does it work"</strong> checks. They're testing a safety mechanism, the threshold between executing an action and falling back.</p>
<p>These are exactly the kinds of tests that need to be written by hand because you have to understand the business logic behind the numbers.</p>
<p>I also tested the Discord embed builder the same way. Give it push event data, check that the formatted message contains the right repo name, author, branch, and commit messages.</p>
<p>Pure input, pure output, no external dependencies. Unit tests ran in milliseconds and caught edge cases like empty commit arrays immediately.</p>
<h3 id="heading-api-tests-where-the-friction-starts">API Tests: Where the Friction Starts</h3>
<p>Testing the Express endpoints required the infrastructure work I described earlier. I separated <code>app.ts</code> from <code>server.ts</code>, built the <code>createTestSession()</code> helper, and set up an in-memory test database so tests wouldn't touch real data.</p>
<pre><code class="language-ts">it("returns 401 without session cookie", async () =&gt; {
  const res = await request(app).get("/api/auth/status");
  expect(res.status).toBe(401);
  expect(res.body).toHaveProperty("error");
});

it("returns user data with valid session", async () =&gt; {
  const cookie = await createTestSession();
  const res = await request(app)
    .get("/api/auth/status")
    .set("Cookie", cookie);
  expect(res.status).toBe(200);
  expect(res.body).toHaveProperty("username");
  expect(res.body).not.toHaveProperty("accessToken");
});
</code></pre>
<p>Five lines of test code, one hour of infrastructure to make those five lines work.</p>
<p>Then I had to repeat this pattern across every endpoint: repos, pull requests, issues, active repo configuration, each with happy path, error cases, and the tedious schema validation I mentioned earlier.</p>
<p>The SSE test was the worst. I needed a Promise wrapper, an EventSource connection, a timeout handler, an <code>onopen</code> callback to trigger the change, an event listener to catch the response, and cleanup for both the connection and the server. About 30 lines for a single assertion, and it took three attempts to get the timing right.</p>
<h3 id="heading-e2e-tests-the-full-journey">E2E Tests: The Full Journey</h3>
<p>Playwright's E2E tests were actually pleasant to write once I added <code>data-testid</code> attributes to the React components. The login flow, note creation, editing, and deletion all followed a predictable pattern.</p>
<pre><code class="language-ts">test("login and create a note", async ({ page }) =&gt; {
  await page.goto("/");
  await page.getByTestId("username-input").fill("ajay");
  await page.getByTestId("password-input").fill("password123");
  await page.getByTestId("login-button").click();
  await expect(page.getByTestId("username-display")).toContainText("ajay");
});
</code></pre>
<p>The real cost wasn't writing the tests — it was maintaining them. Midway through development, I renamed a CSS class from <code>.repo-list-item</code> to <code>.repository-card</code>. Two Playwright tests broke immediately. I found the references, updated them, re-ran. Ten minutes for a CSS rename. I can see this becoming death-by-a-thousand-cuts as the UI evolves.</p>
<h2 id="heading-the-ai-assisted-approach">The AI-Assisted Approach</h2>
<p>Now here's the same project, tested with a fundamentally different workflow.</p>
<p>Instead of writing test code, you describe what you want to test in natural language. An AI agent interprets your intent, interacts with the actual application, generates assertions, and produces exportable test code.</p>
<p>The tool I used is <a href="https://www.testmuai.com/">KaneAI</a>, a GenAI-native testing agent that covers web UIs, APIs, and mobile apps through natural language test authoring with real browser execution. That's the only background you need. Let me show you the workflow.</p>
<h3 id="heading-api-testing-describing-instead-of-coding">API Testing: Describing Instead of Coding</h3>
<p>Instead of writing Supertest code, I opened the slash command menu, selected API, and pasted a curl command:</p>
<pre><code class="language-bash">curl -X GET http://localhost:3000/api/auth/status
</code></pre>
<p>It fired the request through the tunnel, showed the 401 response, and I added it to my test steps. For the authenticated version, I pasted the same command with a session cookie from DevTools. No <code>createTestSession()</code> helper. No test database. No app separation.</p>
<p>For the repository endpoints, I described the flow in plain English:</p>
<pre><code class="language-plaintext">1. Set active repository to "atechajay/no-javascript" via POST to /api/repos/active
2. Verify the response confirms the repository is active
3. Fetch open pull requests via GET to /api/repos/pulls
4. Verify each item has title, author, url, and status fields
5. Try an invalid repository name, verify 400 error
</code></pre>
<p>It generated assertions for the happy path and added schema validation I didn't ask for checking that <code>title</code> is a string, <code>labels</code> is an array, <code>status</code> is one of the expected values. That's the tedious work that ate up hours in the manual approach, generated in seconds.</p>
<h3 id="heading-e2e-testing-plain-english-real-browser">E2E Testing: Plain English, Real Browser</h3>
<p>For the React dashboard, instead of Playwright selectors, I described:</p>
<pre><code class="language-plaintext">1. Navigate to localhost:3001
2. Click "Go to Dashboard"
3. Verify redirect to GitHub OAuth
4. After auth, verify the dashboard loads
5. Verify the username appears in the sidebar
</code></pre>
<p>It executed each step in a real cloud browser connected to my localhost. No <code>page.getByRole()</code>, no <code>page.waitForURL()</code>, no selector debugging.</p>
<p>After each test, I exported the generated code. It came with wait conditions and assertion logic baked in.</p>
<p>It wasn't perfect copy-paste: I updated environment variables, adjusted base URLs, and fixed a few field name mismatches where it expected <code>pullRequestUrl</code> instead of my actual <code>url</code> field. But it gave me roughly 70–80% of the foundation.</p>
<h3 id="heading-the-feature-that-surprised-me">The Feature That Surprised Me</h3>
<p>Midway through testing, I renamed that CSS class from <code>.repo-list-item</code> to <code>.repository-card</code>. My manual Playwright tests broke immediately.</p>
<p>But the AI tool's auto-healing detected the selector change, found the closest matching element based on the test's original intent, and continued the test with a review flag. No code changes needed.</p>
<p>For a rapidly changing MVP where class names are still in flux, that alone saved significant maintenance time.</p>
<h2 id="heading-when-to-use-which-approach">When to Use Which Approach</h2>
<p>After testing the same project both ways, here's my honest take.</p>
<p>Write tests by hand when you're testing business logic that requires domain understanding. For Creoper's intent parser, I needed to think about what "low confidence" means in the context of the application's safety mechanism.</p>
<p>An AI tool can generate assertions, but it can't understand why a confidence score of 0.5 should trigger a fallback instead of an action. Pure logic with meaningful edge cases is where hand-written tests earn their keep.</p>
<p>You should also write tests by hand when they need to run in CI without external dependencies. Vitest tests with mocked dependencies are self-contained. They run in milliseconds and don't need a tunnel, a cloud browser, or a third-party account.</p>
<p>Hand-written tests are also best when the team needs to maintain them. Hand-written tests are transparent. Generated code, even when exported, can feel opaque to someone who wasn't there when it was authored.</p>
<p>Reach for AI-assisted testing, on the other hand, when your UI changes frequently. For an MVP where CSS classes and component structure are still in flux, auto-healing prevents the "my tests broke because I renamed a div" problem. You spend less time fixing selectors and more time shipping features.</p>
<p>AI-assisted testing is also helpful when you need coverage fast and plan to refine later. The 70–80% foundation is a real boost when you're the only developer and you need coverage now. You can always hand-tune the exported code later.</p>
<p>Never rely solely on either approach to understand your system. No tool knows that an SSE connection drops after 30 seconds if the heartbeat isn't configured. No tool understands that a Discord bot should never execute a write action when confidence is below 0.8. No tool realizes the OAuth callback silently fails if the <code>redirect_uri</code> doesn't match precisely.</p>
<p>The strategy relies on you knowing which endpoints are crucial, identifying dangerous edge cases, and understanding what should occur during failures. The tool simply accelerates how quickly you can articulate and implement that strategy.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>My Full-stack app won two hackathons. But without tests, it was a house of cards. One renamed CSS class, one changed API response, and the whole system could silently break.</p>
<p>Testing it both ways taught me that the manual vs AI question is the wrong question. The real skill is matching the approach to the problem.</p>
<p>Write unit tests by hand for business logic. Use AI-assisted testing when you're drowning in repetitive schema validation across a dozen endpoints.</p>
<p>Use auto-healing for E2E tests on a fast-changing UI. And for the things you can't automate yet, like Discord bot interactions or webhook delivery, document them and test them manually until you can.</p>
<p>If you're building something complex and thinking <strong>"I'll add tests after I deploy"</strong>, flip that. Test what you can now. Document what you can't. When deployment day comes, you'll ship with confidence instead of anxiety.</p>
<h2 id="heading-before-we-end"><strong>Before We End</strong></h2>
<p>I hope you found this article insightful. I’m Ajay Yadav, a software developer and content creator.</p>
<p>You can connect with me on:</p>
<ul>
<li><p><a href="https://x.com/atechajay">Twitter/X</a> and <a href="https://www.linkedin.com/in/atechajay/">LinkedIn</a>, where I share insights to help you improve 0.01% each day.</p>
</li>
<li><p>Check out my <a href="https://github.com/ATechAjay">GitHub</a> for more projects.</p>
</li>
<li><p>Check out my <a href="https://thedivsoup.com">Medium</a> page for more blogs.</p>
</li>
<li><p>I also run a <a href="http://youtube.com/@atechajay">YouTube Channel</a> where I share content about careers, software engineering, and technical writing.</p>
</li>
</ul>
<p>See you in the next article — until then, keep learning!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Deploy a MERN Stack Notes App on AWS ]]>
                </title>
                <description>
                    <![CDATA[ Platforms like Vercel, Netlify, and Render simplify deployment by handling infrastructure for you. In this tutorial, we’ll step one layer deeper and work directly with AWS to understand the building blocks behind these platforms. You'll take a small ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-deploy-mern-stack-notes-app-aws/</link>
                <guid isPermaLink="false">696af32341a3a861f59ed367</guid>
                
                    <category>
                        <![CDATA[ deployment ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Umair Mirza ]]>
                </dc:creator>
                <pubDate>Sat, 17 Jan 2026 02:25:39 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768616328012/274a3de8-32bb-4b56-9f71-0f0723541c7d.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Platforms like Vercel, Netlify, and Render simplify deployment by handling infrastructure for you. In this tutorial, we’ll step one layer deeper and work directly with AWS to understand the building blocks behind these platforms.</p>
<p>You'll take a small React and Express notes app and ship it straight to AWS. We'll use EC2 for the API, RDS Postgres for the database, and S3 (optionally CloudFront) for the frontend. If you're new to AWS, you can turn on the Free Tier first: <a target="_blank" href="https://aws.amazon.com/free">https://aws.amazon.com/free</a>.</p>
<p>If you’ve mostly used one-click deployments before, this guide will help you understand what’s happening behind the scenes. You’ll work directly with the core AWS services involved, focusing only on the pieces that matter so you can see how everything fits together. This will also enable you to have more control over cost, security, and scaling.</p>
<p>If you just want to grab the finished code, it's all in this public repo: <a target="_blank" href="https://github.com/umair-mirza/mern-notes-aws">umair-mirza/mern-notes-aws</a>. You can clone or fork it and follow along without creating a new project from scratch.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-what-youll-build">What You’ll Build</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-mental-map">Mental Map</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-free-tier-basics">Free Tier Basics</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-environment-variables">Environment Variables</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-1-run-it-locally-first">Step #1 - Run It Locally First</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-2-push-to-github-so-ec2-can-pull">Step #2 - Push to GitHub (So EC2 Can Pull)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-3-create-aws-resources-quick-path">Step #3 - Create AWS Resources (Quick Path)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-4-configure-the-ec2-box">Step #4 - Configure the EC2 Box</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-5-build-and-upload-the-frontend">Step #5 - Build and Upload the Frontend</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-6-quick-troubleshooting">Step #6 - Quick Troubleshooting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-7-secure-and-save">Step #7 - Secure and Save</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-step-8-verify-end-to-end">Step #8 - Verify End-to-End</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></p>
</li>
</ul>
<h2 id="heading-what-youll-build"><strong>What You’ll Build</strong></h2>
<p>Before touching any buttons in AWS, it's helpful to know the exact pieces you're trying to build. At the end of this guide, you'll have a classic three-tier web app: a browser-based frontend, a backend API, and a database, all talking to each other over a network.</p>
<ul>
<li><p>API (Express/Node) on EC2</p>
</li>
<li><p>Postgres on RDS (Free Tier eligible)</p>
</li>
<li><p>React/Vite frontend on S3 (CloudFront optional for CDN/HTTPS)</p>
</li>
<li><p>Health check at <code>/api/health</code> and CRUD at <code>/api/notes</code></p>
</li>
</ul>
<h2 id="heading-prerequisites"><strong>Prerequisites</strong></h2>
<p>You don't need to be a DevOps expert to follow along, but you should be comfortable running basic commands in a terminal and editing some config files. If you've ever used <code>npm install</code> before, then you're in the right place.</p>
<ul>
<li><p>AWS account + AWS CLI configured (<code>aws configure</code>) – see <a target="_blank" href="https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-creating.html">AWS account setup</a> and <a target="_blank" href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html">AWS CLI install</a>.</p>
</li>
<li><p>Node.js 18+ and npm – get it from <a target="_blank" href="http://nodejs.org">nodejs.org</a><a target="_blank" href="https://nodejs.org/">.</a></p>
</li>
<li><p>Git + GitHub repo – see <a target="_blank" href="https://docs.github.com/en/get-started">GitHub getting started</a>.</p>
</li>
<li><p>(Optional) Route 53 domain for a clean URL – <a target="_blank" href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/domain-register.html">Route 53 domains</a>.</p>
</li>
</ul>
<h2 id="heading-mental-map"><strong>Mental Map</strong></h2>
<p>AWS throws a lot of jargon at you (VPCs, security groups, subnets). This section is the story version of what happens when someone opens your app in the browser, without any buzzwords. If you can picture this flow, the later AWS screens will feel less scary.</p>
<ul>
<li><p>Browser loads the built React app from S3 (or CloudFront -&gt; S3)</p>
</li>
<li><p>Browser calls the API on EC2 over HTTP/HTTPS</p>
</li>
<li><p>EC2 talks to RDS Postgres on port 5432 inside your VPC</p>
</li>
<li><p>Security groups: allow 80/443 to EC2; allow 5432 only from the EC2 SG to RDS</p>
</li>
</ul>
<h2 id="heading-free-tier-basics"><strong>Free Tier Basics</strong></h2>
<p>AWS can be cheap if you use the free tier, but it can also surprise you with bills if you accidentally orprovision or leave things running. Here are the main knobs that affect cost for this tutorial and what to watch out for.</p>
<ul>
<li><p>EC2: <code>t2.micro</code> or <code>t3.micro</code> ~750 hours/month</p>
</li>
<li><p>RDS: <code>db.t3.micro</code> Postgres/MySQL with ~20 GB storage</p>
</li>
<li><p>S3/CloudFront: Small sites cost pennies - free tier includes some egress</p>
</li>
<li><p>Save money: Stop EC2 when idle. Delete unused buckets/DBs</p>
</li>
</ul>
<h2 id="heading-environment-variables"><strong>Environment Variables</strong></h2>
<p>Environment variables are just configuration values that live outside your code: ports, database URLs, and allowed origins. They keep secrets (like DB passwords) out of your Git repo and let the same code run in different places (local, staging, production) with different settings.</p>
<ul>
<li><p>Backend: <code>PORT</code>, <code>DATABASE_URL</code> (your RDS endpoint), <code>DATABASE_SSL</code> (<code>true</code> on RDS), <code>CORS_ORIGIN</code></p>
</li>
<li><p>Frontend: <code>VITE_API_URL</code> (API base, for example, <code>https://api.example.com/api</code>)</p>
</li>
</ul>
<h2 id="heading-step-1-run-it-locally-first"><strong>Step #1 - Run It Locally First</strong></h2>
<p>Before touching AWS, you want to prove the app actually works on your own machine. This removes a whole category of "Is it AWS or my code?" debugging later. In this step you just install dependencies and run both backend and frontend in dev mode.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> mern-notes-aws

<span class="hljs-comment"># Backend</span>
<span class="hljs-built_in">cd</span> backend
npm install
cp .env.example .env   <span class="hljs-comment"># set DATABASE_URL to RDS (or local Postgres), DATABASE_SSL=true for RDS</span>
npm run dev            <span class="hljs-comment"># API on http://localhost:4000</span>

<span class="hljs-comment"># Frontend (new terminal)</span>
<span class="hljs-built_in">cd</span> frontend
npm install
cp .env.example .env   <span class="hljs-comment"># keep API URL at http://localhost:4000/api for local dev</span>
npm run dev            <span class="hljs-comment"># SPA on http://localhost:5173</span>
</code></pre>
<p>Open <code>http://localhost:5173</code>, add a note, and check if it persists. <code>/api/health</code> should return <code>{ status: 'ok' }</code>. If something is broken here, pause and fix it before moving on. AWS will only make debugging harder.</p>
<h2 id="heading-step-2-push-to-github-so-ec2-can-pull"><strong>Step #2 - Push to GitHub (So EC2 Can Pull)</strong></h2>
<p>Your EC2 server in AWS needs a place to pull your code from. Using GitHub is the simplest option: you push your code once, then the EC2 instance clones that repo. You can also reuse this repo later with CI/CD if you decide to automate deployments.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> mern-notes-aws
git init
git add .
git commit -m <span class="hljs-string">"feat: mern notes app"</span>
git branch -M main
git remote add origin https://github.com/&lt;you&gt;/mern-notes-aws.git
git push -u origin main
</code></pre>
<p>If you're following along with my example repo instead of creating your own, you can simply fork <a target="_blank" href="https://github.com/umair-mirza/mern-notes-aws">umair-mirza/mern-notes-aws</a> and use that as your remote.</p>
<p>Before pushing, make sure your <code>.env</code> file is <strong>not committed to GitHub</strong>. Add it to your <code>.gitignore</code> so secrets like database passwords never end up in version control:</p>
<pre><code class="lang-bash"><span class="hljs-built_in">echo</span> <span class="hljs-string">".env"</span> &gt;&gt; .gitignore
</code></pre>
<p>If you’ve already created a <code>.env</code> file locally, double-check it doesn’t appear in <code>git status</code> before committing.</p>
<h2 id="heading-step-3-create-aws-resources-quick-path"><strong>Step #3 - Create AWS Resources (Quick Path)</strong></h2>
<h3 id="heading-rds-postgres-free-tier-template"><strong>RDS (Postgres, Free Tier template)</strong></h3>
<p>RDS (Relational Database Service) is AWS's way of running managed databases for you. Instead of installing Postgres manually on a VM, you click a few options and AWS handles backups, patching, and high availability. For this app we only need a small, free tier–eligible Postgres instance.</p>
<p>For more background, you can skim the official <a target="_blank" href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html">Amazon RDS for PostgreSQL docs</a>.</p>
<p>We’ll start by creating the database layer. The settings below are the minimum you need for a small, production-style Postgres setup that stays within the AWS Free Tier while still following basic best practices.</p>
<ul>
<li><p>RDS Create database Postgres Free Tier.</p>
</li>
<li><p>Class <code>db.t3.micro</code>, storage 20 GB gp2/gp3.</p>
</li>
<li><p>Set master user/pass. You'll need them for <code>DATABASE_URL</code>.</p>
</li>
<li><p>Public access: No.</p>
</li>
<li><p>Security group: allow 5432 only from the EC2 security group.</p>
</li>
<li><p>Enable backups and Require SSL. Download the RDS CA if you want strict cert validation.</p>
</li>
</ul>
<h3 id="heading-s3-bucket-for-the-frontend"><strong>S3 Bucket for the Frontend</strong></h3>
<p>S3 is AWS's "infinite hard drive" for files. A React/Vite app builds down to plain HTML, CSS, and JavaScript files, which are perfect to host from S3. Think of S3 as a very simple web server that just serves static files.</p>
<p>If you want to see more options, check the <a target="_blank" href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/WebsiteHosting.html">Hosting a static website on Amazon S3 guide</a>.</p>
<p>Now, we’ll create an S3 bucket to host the React frontend. These options configure the bucket for static website hosting while keeping it simple and inexpensive.</p>
<ul>
<li><p>Create bucket <code>mern-notes-aws-frontend-&lt;suffix&gt;</code>.</p>
</li>
<li><p>For simple hosting, enable static website hosting and allow public reads, or keep private and use CloudFront + OAC.</p>
</li>
<li><p>Turn on versioning if you want rollback safety.</p>
</li>
</ul>
<h3 id="heading-ec2-for-the-api"><strong>EC2 for the API</strong></h3>
<p>EC2 is "a computer in the cloud" that you control. You'll install Node.js on it, pull your code, and run <code>server.js</code> so that your backend API is always on. The security group attached to this instance works like a firewall.</p>
<p>If you've never launched an instance before, the <a target="_blank" href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EC2_GetStarted.html">Getting started with Amazon EC2</a> guide walks through the console screens you'll see.</p>
<p>Finally, we’ll provision a small EC2 instance to run the Express API. The configuration below focuses on a free tier–eligible setup that’s secure enough for learning and easy to extend later.</p>
<ul>
<li><p>Launch Amazon Linux 2023, size <code>t3.micro</code>.</p>
</li>
<li><p>Inbound SG: 22 (your IP), 80 (world), 443 if you add HTTPS on the instance/ALB.</p>
</li>
<li><p>Attach this SG as the allowed source to RDS.</p>
</li>
</ul>
<h3 id="heading-optional-cloudfront-route-53"><strong>Optional: CloudFront + Route 53</strong></h3>
<p>CloudFront is AWS's CDN (content delivery network), and Route 53 is their DNS service. You don't strictly need them to get your app working, but they make it faster and nicer: your app loads from edge locations close to users and can live behind a friendly domain like <code>app.example.com</code>.</p>
<p>For more details, see <a target="_blank" href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/GettingStarted.html">Getting started with Amazon CloudFront</a> and the <a target="_blank" href="https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/Welcome.html">Route 53 DNS developer guide</a>.</p>
<ul>
<li><p>Origin: the S3 bucket. Default root <code>index.html</code>. Add OAC if bucket is private.</p>
</li>
<li><p>Request an ACM cert in <code>us-east-1</code>, then create a Route 53 A/AAAA alias to the distribution.</p>
</li>
</ul>
<h2 id="heading-step-4-configure-the-ec2-box"><strong>Step #4 - Configure the EC2 Box</strong></h2>
<p>Once your EC2 instance is running, you treat it like a clean Linux machine. The commands below install the tools your API needs, pull your code from GitHub, configure environment variables, and run the server in a production-safe way.</p>
<p>Install basics:</p>
<pre><code class="lang-bash">sudo dnf update -y
</code></pre>
<p>This command updates all system packages to the latest versions. It's a good first step on any new Linux server.</p>
<pre><code class="lang-bash">sudo dnf install -y git
</code></pre>
<p>Installs Git so the EC2 instance can clone your repository from GitHub.</p>
<pre><code class="lang-bash">curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
</code></pre>
<p>Adds the official NodeSource repository so you can install a modern version of Node.js (v20). Amazon Linux doesn’t ship with recent Node versions by default.</p>
<pre><code class="lang-bash">sudo dnf install -y nodejs
</code></pre>
<p>Installs Node.js and npm, which are required to run your Express API.</p>
<pre><code class="lang-bash">sudo npm install -g pm2
</code></pre>
<p>Installs PM2, a lightweight process manager that keeps your Node app running in the background and restarts it if it crashes or the server reboots.</p>
<p>Pull code and set environment variables:</p>
<pre><code class="lang-bash">git <span class="hljs-built_in">clone</span> https://github.com/&lt;you&gt;/mern-notes-aws.git
<span class="hljs-built_in">cd</span> mern-notes-aws/backend
npm install

cat &lt;&lt;<span class="hljs-string">'EOF'</span> &gt; .env
PORT=80
DATABASE_URL=postgres://&lt;user&gt;:&lt;password&gt;@&lt;rds-endpoint&gt;:5432/&lt;dbname&gt;
DATABASE_SSL=<span class="hljs-literal">true</span>
CORS_ORIGIN=https://&lt;your-frontend-domain&gt;
EOF
</code></pre>
<p>Start the API with PM2:</p>
<pre><code class="lang-bash">pm2 start server.js --name mern-notes-api
pm2 save
pm2 startup systemd -u ec2-user --hp /home/ec2-user
</code></pre>
<p>PM2 is a small process manager that makes sure your Node server keeps running if the machine reboots or the process crashes. Test on the box: <code>curl http://localhost/api/health</code>. From your laptop: <code>http://&lt;ec2-public-dns&gt;/api/health</code> (make sure SG allows 80/443).</p>
<h2 id="heading-step-5-build-and-upload-the-frontend"><strong>Step #5 - Build and Upload the Frontend</strong></h2>
<p>In development, Vite serves your React app from memory, but in production you want a set of static files that any web server (or S3) can host. <code>npm run build</code> creates an optimized <code>dist/</code> folder that you sync to S3 so the browser can load it.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> frontend
setx VITE_API_URL <span class="hljs-string">"https://&lt;ec2-or-api-domain&gt;/api"</span>
npm run build
</code></pre>
<p>This sets an environment variable called <code>VITE_API_URL</code> on your local machine. Vite only exposes environment variables to the frontend if they start with the <code>VITE_</code> prefix.</p>
<p>Upload:</p>
<pre><code class="lang-bash">aws s3 sync dist/ s3://mern-notes-aws-frontend-&lt;suffix&gt;/ --delete
</code></pre>
<p>This uploads your compiled frontend (<code>dist/</code>) to S3 and removes old files that no longer exist locally, ensuring the bucket reflects the current version of the app</p>
<p>Open the S3 website URL or your CloudFront URL.</p>
<h2 id="heading-step-6-quick-troubleshooting"><strong>Step #6 - Quick Troubleshooting</strong></h2>
<p>If something doesn't work the first time, that's normal, especially with networking and AWS permissions. This section gives you a few quick places to look before you start randomly changing settings in the console.</p>
<ul>
<li><p>API 500s: <code>pm2 logs mern-notes-api</code>. This is often a bad <code>DATABASE_URL</code> or SSL flag.</p>
</li>
<li><p>DB connect issues: RDS SG must allow the EC2 SG - use the RDS endpoint.</p>
</li>
<li><p>CORS errors: <code>CORS_ORIGIN</code> must match your frontend origin exactly.</p>
</li>
<li><p>403 from S3: If you’re using static website hosting, allow public reads. With CloudFront, keep bucket private and use OAC.</p>
</li>
<li><p>Blank page: Confirm that you’ve uploaded <code>dist/</code> to the right bucket.</p>
</li>
</ul>
<h2 id="heading-step-7-secure-and-save"><strong>Step #7 - Secure and Save</strong></h2>
<p>Once everything works, you don't want to accidentally expose your database to the internet or burn through free tier hours. These are simple, beginner-friendly hardening steps that make your setup safer and cheaper without turning you into a full-time security engineer.</p>
<ul>
<li><p>Turn off SSH after setup or switch to SSM Session Manager.</p>
</li>
<li><p>Use HTTPS (CloudFront + ACM or ALB + ACM).</p>
</li>
<li><p>Keep RDS private and use SSM port forwarding if needed.</p>
</li>
<li><p>Ship PM2 logs with CloudWatch Agent and add alarms for CPU/status checks.</p>
</li>
<li><p>Snapshot RDS daily and stop EC2 when idle to save hours.</p>
</li>
</ul>
<h2 id="heading-step-8-verify-end-to-end"><strong>Step #8 - Verify End-to-End</strong></h2>
<p>Before you celebrate, run through the app like a real user: open it in the browser, create notes, refresh, and make sure everything behaves as expected. This confirms your frontend, API, and database are all wired together correctly.</p>
<ul>
<li><p>Load the frontend (S3 or CloudFront).</p>
</li>
<li><p>Create and delete notes. They should persist in RDS.</p>
</li>
<li><p>Hit <code>/api/health</code> for a quick liveness check.</p>
</li>
</ul>
<h2 id="heading-next-steps"><strong>Next Steps</strong></h2>
<p>Once you're comfortable with this manual setup, you can start layering on more advanced tools. The ideas are the same: frontend, API and database but you get more automation, safety, and scalability.</p>
<ul>
<li><p>Add Prisma + migrations for stronger schemas.</p>
</li>
<li><p>Add auth (Cognito/Auth0) and per-user notes.</p>
</li>
<li><p>Containerize and run on ECS/Fargate or add an ALB in front of EC2.</p>
</li>
<li><p>Use Terraform/CDK to recreate this stack with one command.</p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Get Type Safety Without Code Generation Using tRPC and Hono ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever updated your backend API property name but neglected to also update the frontend? I'm sure you have. When this occurs, it leads to production crashes and unhappy customers, plus you've wasted your entire week fixing the problem. To reso... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/type-safety-without-code-generation-using-trpc-and-hono/</link>
                <guid isPermaLink="false">696535106ca3442fa7874d4b</guid>
                
                    <category>
                        <![CDATA[ trpc ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Web Development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ APIs ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ hono ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Tarun Singh ]]>
                </dc:creator>
                <pubDate>Mon, 12 Jan 2026 17:53:20 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1768240380001/79d5aa1f-438e-4a1e-a072-3166b9a36333.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever updated your backend API property name but neglected to also update the frontend? I'm sure you have. When this occurs, it leads to production crashes and unhappy customers, plus you've wasted your entire week fixing the problem.</p>
<p>To resolve this issue in the past, you typically had to create a multitude of TypeScript interfaces by hand, using GraphQL Code Generator to generate the interface files, or hope that it all worked out. Well, there’s a better way to accomplish this now, without the need for code generation.</p>
<p><a target="_blank" href="https://trpc.io/">tRPC</a> and <a target="_blank" href="https://hono.dev/">Hono</a> are two applications that are changing how we develop TypeScript-based applications throughout the entirety of the full-stack.</p>
<p>By the end of this tutorial, you’ll understand:</p>
<ul>
<li><p>Why traditional REST APIs fail at type safety</p>
</li>
<li><p>How tRPC provides full end-to-end type inference between backend and frontend</p>
</li>
<li><p>How Hono delivers type-safe APIs while staying REST-friendly</p>
</li>
<li><p>When to choose tRPC vs Hono for your projects</p>
</li>
<li><p>How these tools improve developer experience, team velocity, and reliability</p>
</li>
</ul>
<p>If you’re building full-stack TypeScript applications and want fewer runtime bugs and faster iteration, this guide is for you.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-problem-with-traditional-apis">The Problem with Traditional APIs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-makes-trpc-different">What Makes tRPC Different?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-hono-the-lightweight-challenger">Hono: The Lightweight Challenger</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-why-this-matters-these-days">Why This Matters These Days</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-getting-started">Getting Started</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-future-is-type-safe">The Future is Type-Safe</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>To follow along comfortably, you should have:</p>
<ul>
<li><p>Basic knowledge of TypeScript</p>
</li>
<li><p>Familiarity with REST APIs and how frontend-backend communication works</p>
</li>
<li><p>Some experience with Node.js and modern JavaScript frameworks</p>
</li>
<li><p>A general understanding of frontend frameworks like React or Next.js (helpful, but not required)</p>
</li>
</ul>
<p>You don’t need prior experience with tRPC, Hono, or GraphQL.</p>
<h2 id="heading-the-problem-with-traditional-apis">The Problem with Traditional APIs</h2>
<p>You’ve probably written something like this a hundred times:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Backend (Express)</span>
app.post(<span class="hljs-string">'/api/users'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { name, email } = req.body;
  <span class="hljs-comment">// Do stuff with user</span>
  res.json({ id: <span class="hljs-number">1</span>, name, email });
});

<span class="hljs-comment">// Frontend</span>
<span class="hljs-keyword">const</span> createUser = <span class="hljs-keyword">async</span> (name: <span class="hljs-built_in">string</span>, email: <span class="hljs-built_in">string</span>) =&gt; {
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">'/api/users'</span>, {
    method: <span class="hljs-string">'POST'</span>,
    headers: { <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span> },
    body: <span class="hljs-built_in">JSON</span>.stringify({ name, email })
  });
  <span class="hljs-keyword">return</span> response.json(); 
};
</code></pre>
<p>The backend knows the shape of the data. But the frontend...it hopes it gets it right. You end up writing interfaces manually, like:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">interface</span> User {
  id: <span class="hljs-built_in">number</span>;
  name: <span class="hljs-built_in">string</span>;
  email: <span class="hljs-built_in">string</span>;
}
</code></pre>
<p>If you change the backend tomorrow to return <code>userId</code> instead of <code>id</code>, TypeScript won't catch it. Your types and reality have diverged, and you won't know until runtime.</p>
<p><a target="_blank" href="https://graphql.org/">GraphQL</a> tried to solve this with schemas and codegen, but honestly? Setting up GraphQL feels like assembling IKEA furniture without instructions. You need a schema, resolvers, code generation tools, and suddenly your "simple" API has a 30-minute setup process.</p>
<h2 id="heading-what-makes-trpc-different">What Makes tRPC Different?</h2>
<p>tRPC flips the script entirely. Instead of defining your API in a separate schema language, your TypeScript code is the schema. Here's the same API in tRPC:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Backend (tRPC router)</span>
<span class="hljs-keyword">import</span> { initTRPC } <span class="hljs-keyword">from</span> <span class="hljs-string">'@trpc/server'</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">'zod'</span>;

<span class="hljs-keyword">const</span> t = initTRPC.create();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> appRouter = t.router({
  createUser: t.procedure
    .input(z.object({
      name: z.string(),
      email: z.string().email(),
    }))
    .mutation(<span class="hljs-function">(<span class="hljs-params">{ input }</span>) =&gt;</span> {
      <span class="hljs-comment">// Do stuff with user</span>
      <span class="hljs-keyword">return</span> { id: <span class="hljs-number">1</span>, name: input.name, email: input.email };
    }),
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> AppRouter = <span class="hljs-keyword">typeof</span> appRouter;
</code></pre>
<p>This is where it gets cool. On your frontend:</p>
<pre><code class="lang-typescript"><span class="hljs-comment">// Frontend - fully type-safe!</span>
<span class="hljs-keyword">import</span> { createTRPCClient } <span class="hljs-keyword">from</span> <span class="hljs-string">'@trpc/client'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { AppRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">'./server'</span>;

<span class="hljs-keyword">const</span> client = createTRPCClient&lt;AppRouter&gt;({
  url: <span class="hljs-string">'http://localhost:3000/trpc'</span>,
});

<span class="hljs-comment">// TypeScript knows EVERYTHING about this call</span>
<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> client.createUser.mutate({
  name: <span class="hljs-string">'Alice'</span>,
  email: <span class="hljs-string">'alice@example.com'</span>
});

<span class="hljs-comment">// user is automatically typed as { id: number; name: string; email: string; }</span>
</code></pre>
<p>No code generation or build step, or GraphQL schema. Just pure TypeScript inference doing its thing. If you rename <code>id</code> to <code>userId</code> in your backend, your frontend will immediately show a TypeScript error. You'll catch it before you even save the file.</p>
<p>This is what we call end-to-end type safety, and it's honestly a great transition.</p>
<h2 id="heading-hono-the-lightweight-challenger">Hono: The Lightweight Challenger</h2>
<p>While tRPC is amazing for full-stack TypeScript apps where you control both ends, <a target="_blank" href="https://hono.dev/">Hono</a> takes a slightly different approach. It's a lightweight web framework that gives you type safety while still being a traditional HTTP framework.</p>
<p>Here's the same example in Hono:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { Hono } <span class="hljs-keyword">from</span> <span class="hljs-string">'hono'</span>;
<span class="hljs-keyword">import</span> { z } <span class="hljs-keyword">from</span> <span class="hljs-string">'zod'</span>;
<span class="hljs-keyword">import</span> { zValidator } <span class="hljs-keyword">from</span> <span class="hljs-string">'@hono/zod-validator'</span>;

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

<span class="hljs-keyword">const</span> userSchema = z.object({
  name: z.string(),
  email: z.string().email(),
});

app.post(<span class="hljs-string">'/api/users'</span>, zValidator(<span class="hljs-string">'json'</span>, userSchema), <span class="hljs-function">(<span class="hljs-params">c</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { name, email } = c.req.valid(<span class="hljs-string">'json'</span>);
  <span class="hljs-keyword">return</span> c.json({ id: <span class="hljs-number">1</span>, name, email });
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">type</span> AppType = <span class="hljs-keyword">typeof</span> app;
</code></pre>
<p>On the frontend, you can use Hono’s RPC client:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { hc } <span class="hljs-keyword">from</span> <span class="hljs-string">'hono/client'</span>;
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { AppType } <span class="hljs-keyword">from</span> <span class="hljs-string">'./server'</span>;

<span class="hljs-keyword">const</span> client = hc&lt;AppType&gt;(<span class="hljs-string">'http://localhost:3000'</span>);

<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> client.api.users.$post({
  json: { name: <span class="hljs-string">'Bob'</span>, email: <span class="hljs-string">'bob@example.com'</span> }
});

<span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> response.json();
<span class="hljs-comment">// user is fully typed!</span>
</code></pre>
<p>Hono is incredibly fast (it runs on Cloudflare Workers, Deno, Bun, and Node.js), and it gives you that sweet type safety while still being a "regular" HTTP framework. You get RESTful routes, middleware, and all the familiar patterns – just with TypeScript powers.</p>
<h2 id="heading-why-this-matters-these-days">Why This Matters These Days</h2>
<p>You might think to yourself, “Okay, I know what you mean, but why should I care about it?” There’s a reason why these tools are being utilized more now than ever before.</p>
<h3 id="heading-developer-experience-is-essential">Developer experience is essential</h3>
<p>In 2026 and beyond, we’ll no longer accept long feedback loops. The ability to modify your backend code and see what might break on your frontend application without having to run the application will be fantastic for productivity. We’ll spend less time fixing bugs and more time creating new functionalities.</p>
<h3 id="heading-smaller-teams-better-apps">Smaller teams, better apps</h3>
<p>With tRPC or Hono, one developer can create an entire full-stack application with type safety at a very fast pace because they don’t have to switch back and forth between REST documentation and TypeScript interfaces – all the data is flowing to and from their backend code directly to their frontend.</p>
<h3 id="heading-the-end-of-works-on-my-machine">The end of “Works on my machine“</h3>
<p>With type safety, errors are caught at compile time instead of at the time your end-user clicks on a button. This is especially impactful when working in larger teams, when the backend developers and front-end developers may not be in constant communication with one another.</p>
<h2 id="heading-getting-started">Getting Started</h2>
<p>Want to try this out? Here's the fastest way:</p>
<p>For tRPC:</p>
<pre><code class="lang-bash">npm create @trpc/next-app@latest
</code></pre>
<p>This scaffolds a Next.js app with tRPC already configured. Check out the <a target="_blank" href="https://trpc.io/docs/client/nextjs">official tRPC docs</a> for more.</p>
<p>For Hono:</p>
<pre><code class="lang-bash">npm create hono@latest
</code></pre>
<p>Pick your runtime (Node.js, Cloudflare Workers, etc.), and you're off to the races. The <a target="_blank" href="https://hono.dev/">Hono documentation</a> is excellent and super approachable.</p>
<h2 id="heading-the-future-is-type-safe">The Future is Type-Safe</h2>
<p>Look, REST isn't going anywhere, and GraphQL has its place. But for full-stack TypeScript developers, tRPC and Hono represent something special: type safety without the ceremony. No code generation or no schema duplication, just TypeScript doing what it does best.</p>
<p>In the future, when you start a new project, give one of these a shot. Your future self – the one who's refactoring code at 2 AM – will thank you.</p>
<p>Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Become a Full Stack Developer with One Video ]]>
                </title>
                <description>
                    <![CDATA[ What if you could learn what you need to know to become a full stack developer in just one video. We just published a 48-hour course on the freeCodeCamp.org YouTube channel that will take you from square one to being a hireable full-stack developer. ... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/become-a-full-stack-developer-with-one-video/</link>
                <guid isPermaLink="false">68dd983f4c1d99b015822d01</guid>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ youtube ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Beau Carnes ]]>
                </dc:creator>
                <pubDate>Wed, 01 Oct 2025 21:08:15 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759352860983/1c5a17cf-adfd-4cea-bd45-099ab5c4862a.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>What if you could learn what you need to know to become a full stack developer in just one video.</p>
<p>We just published a 48-hour course on the <a target="_blank" href="http://freeCodeCamp.org">freeCodeCamp.org</a> YouTube channel that will take you from square one to being a hireable full-stack developer. This course was developed by the team at Scrimba.</p>
<p>This course starts right at the beginning with HTML and CSS, giving you the essential building blocks. Then, it dives deep into JavaScript, ensuring you understand the advanced concepts needed for complex applications.</p>
<p>From there, you’ll learn the industry-standard front-end tools: React.js for building slick user interfaces and Next.js to handle the heavy lifting like routing and server-side rendering. For the backend, you will learn about Node.js and learn how to connect it all to databases.</p>
<p>The course also covers TypeScript, which is important for writing safer, more scalable code that won't break at the drop of a hat. And you will learn about testing, so you can confidently build features that actually work.</p>
<p>Finally, the course covers Tools of the Trade and a gives practical look at how AI Engineering is starting to change the way we code. By the time you finish this one course, you won't just have a beginner's understanding; you'll have a complete skill set ready to tackle your first job or build your own startup.</p>
<p>Watch the full course on <a target="_blank" href="https://youtu.be/LzMnsfqjzkA">the freeCodeCamp.org YouTube channel</a> (48-hour watch).</p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/LzMnsfqjzkA" 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[ Learn Key System Design Principles Behind High-Traffic Platforms Like Gaming and Job Discovery ]]>
                </title>
                <description>
                    <![CDATA[ Over the last three months, life has had me juggling a lot – managing my marriage, taking care of family health issues, and overseeing new construction work at home. Somehow, I got through it all. But looking back, I realised something important: I c... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/learn-key-system-design-principles-behind-high-traffic-platforms-like-gaming-and-job-discovery/</link>
                <guid isPermaLink="false">68a5f915c3b51ce78e8f92b3</guid>
                
                    <category>
                        <![CDATA[ System Design ]]>
                    </category>
                
                    <category>
                        <![CDATA[ System Architecture ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Prankur Pandey ]]>
                </dc:creator>
                <pubDate>Wed, 20 Aug 2025 16:34:29 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1755707525928/f4a02c14-fe62-4d6f-9afc-d887d45a98d4.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Over the last three months, life has had me juggling a lot – managing my marriage, taking care of family health issues, and overseeing new construction work at home. Somehow, I got through it all. But looking back, I realised something important: I could’ve handled it much better if I had a <em>system</em> in place.</p>
<p>For me, a <strong>system</strong> means a set of rules, processes, and triggers that guide the entire workflow. This helps you conserve energy and not have to figure things out in the moment. It keeps things productive, efficient, and consistent.</p>
<p>Now that the chaos has settled, I’ve been thinking a lot about systems – not just in life, but in tech. I wish I had applied the same principles of <strong>system design</strong> earlier.</p>
<p>In this article, we’re going to explore real-world system design examples from domains like gaming and job platforms. These industries don’t just scale massively – they also demand high availability, low latency, and seamless customer experiences. Understanding how they’re built is a powerful way to level up your thinking as a developer or architect.</p>
<h3 id="heading-what-well-cover">What We’ll Cover</h3>
<ol>
<li><p><a class="post-section-overview" href="#heading-introduction-what-is-system-design-and-why-scale-matters">Introduction: What is System Design and Why Scale Matters</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-approaches-to-system-design">Approaches to System Design</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-1-bottom-up-approach">1. Bottom-Up Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-top-down-approach">2. Top-Down Approach</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-3-hybrid-design">3. Hybrid Design</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-important-concepts-in-system-design">Important Concepts in System Design</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-full-stack-web-application-components">Full Stack Web Application Components</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-computers-talk-to-each-other-the-internet">How Computers Talk to Each Other (The Internet)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-problem-of-growth">The Problem of Growth</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-speeding-up-your-website-with-caching-and-cdns">Speeding Up Your Website with Caching and CDNs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-building-your-application-monolith-vs-microservices">Building Your Application: Monolith vs. Microservices</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-the-apis">The APIs</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-handling-real-time-data">Handling Real-Time Data</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-databases">Databases</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-understanding-the-cap-theorem">Understanding the CAP Theorem</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-rate-limiting-and-monitoring">Rate Limiting and Monitoring</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-case-studies-scaling-in-the-real-world">Case Studies: Scaling in the Real World</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-case-study-1-scaling-a-job-search-application">Case Study 1: Scaling a Job Search Application</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-case-study-2-scaling-an-online-gaming-application">Case Study 2: Scaling an Online Gaming Application</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-qampa">Q&amp;A</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-final-notes">Final Notes</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ol>
<h2 id="heading-introduction-what-is-system-design-and-why-scale-matters">Introduction: What is System Design and Why Scale Matters</h2>
<p><strong>System design</strong> is the process of defining the architecture, modules, interfaces, and data of a system.</p>
<p>In other words, system design means explaining the different parts of a system, like its structure, building blocks (modules), and components.</p>
<p>It’s a process used to define, develop, and design a system in a way that meets the specific needs of a business or organisation.</p>
<p>The main goal of system design is to give enough information and details about the system, and to properly implement its parts using models and views. Let’s now talk about the different parts of a system.</p>
<h3 id="heading-elements-of-a-system">Elements of a System:</h3>
<ul>
<li><p><strong>Architecture:</strong> This is a basic structure or model that shows how the system works, looks, and behaves.<br>  We often use <strong>flowcharts</strong> to explain and represent this architecture.</p>
</li>
<li><p><strong>Modules:</strong> These are smaller parts or sections of the system. Each module handles a specific task. When all modules are combined, they make the complete system.</p>
</li>
<li><p><strong>Components:</strong> These provide a specific function or a group of related functions. Components are usually made from one or more modules.</p>
</li>
<li><p><strong>Interfaces:</strong> This is the connection point where different parts (components) of the system exchange information with each other.</p>
</li>
<li><p><strong>Data:</strong> This refers to managing information and how it flows through the system.</p>
</li>
</ul>
<h3 id="heading-why-system-design-matters">Why System Design Matters</h3>
<p>System design is important for a number of practical reasons. First, it can help companies and teams solve complex business problems and make sure they thoroughly analyse all requirements before building. It also reduces the chance that errors will be introduced into processes while making design phases more efficient and structured. Finally, it helps you efficiently gather and present your data in a useful format and improves the overall quality of the system.</p>
<h2 id="heading-approaches-to-system-design">Approaches to System Design</h2>
<p>There are several methods you can use to approach system design. The main ones are:</p>
<h3 id="heading-1-bottom-up-approach">1. Bottom-Up Approach</h3>
<p>In this method, the design starts from the lowest-level components or subsystems. These small parts are gradually combined to form higher-level components. This process continues until the entire system is built as one complete structure.</p>
<p>The more abstraction we use, the higher the level of the design becomes.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li><p>Components can be reused in other systems.</p>
</li>
<li><p>It’s easier to identify risks early.</p>
</li>
<li><p>It helps in hiding low-level technical details and can be combined with the top-down approach.</p>
</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li><p>It’s not very focused on the overall structure of the problem.</p>
</li>
<li><p>Building high-quality bottom-up solutions is hard and time-consuming.</p>
</li>
</ul>
<h3 id="heading-2-top-down-approach">2. Top-Down Approach</h3>
<p>Here, the design starts from the entire system, and you break it down into smaller subsystems and components as you go. Each of these subsystems then gets broken down further, step by step, creating a hierarchical structure.</p>
<p>In simple terms, you start with the big picture and keep dividing it until you reach the smallest parts of the system.</p>
<p>To sum up, design starts with defining the whole system, then continues by defining its subsystems and components. When all definitions are ready and fit together, the system is complete.</p>
<p><strong>Advantages</strong>:</p>
<ul>
<li><p>The focus is on understanding the requirements first, which leads to a responsive and purpose-driven design.</p>
</li>
<li><p>It’s easier to handle errors in interfaces between components.</p>
</li>
</ul>
<p><strong>Disadvantages</strong>:</p>
<ul>
<li><p>Components can’t be reused easily in other systems.</p>
</li>
<li><p>The resulting architecture is often less flexible or not very clean.</p>
</li>
</ul>
<h3 id="heading-3-hybrid-design">3. Hybrid Design</h3>
<p>The hybrid design approach is a mix of <strong>top-down</strong> and <strong>bottom-up</strong> methods. Instead of committing to just one way, it takes the strengths of both. You start by looking at the overall system (like in top-down) so that you don’t lose sight of the big picture. At the same time, you also focus on building solid, reusable modules or components (like in bottom-up).</p>
<p>In simple terms, you first plan the big picture, then create smaller components that can work independently, and finally combine everything into a cohesive system.</p>
<p>For instance, in our sports team site, we’d use top-down to define the whole fan journey (homepage → match details → live scores). But bottom-up, we’d build modular components like authentication or stats tracking, which can later be reused in new features like ticket booking or merchandise sales.</p>
<p><strong>Advantages:</strong></p>
<ul>
<li><p>You get the clarity of a top-down plan while still building reusable modules.</p>
</li>
<li><p>It strikes a balance between high-level design and detailed implementation.</p>
</li>
<li><p>Risks are easier to manage since you’re considering both structure and components.</p>
</li>
</ul>
<p><strong>Disadvantages:</strong></p>
<ul>
<li><p>It can be complex to manage since you’re juggling two approaches.</p>
</li>
<li><p>Requires more coordination between teams working on different levels.</p>
</li>
<li><p>It might take more time compared to using a single approach.</p>
</li>
</ul>
<h2 id="heading-important-concepts-in-system-design">Important Concepts in System Design</h2>
<p>Before exploring core components, I want you to first understand two key concepts:</p>
<ul>
<li><p>Full stack web application components</p>
</li>
<li><p>How computers talk to each other (via the internet)</p>
</li>
</ul>
<h3 id="heading-full-stack-web-application-components">Full Stack Web Application Components</h3>
<p>A full-stack web application is a software application that combines both the frontend (what users see and interact with) and the backend (the server, database, and logic that power the app) into one complete system.</p>
<p>Generally, simple websites don’t require much system design – and in some cases, no system design at all. But when it comes to viral applications or platforms offering complex services, system design becomes essential. Most modern applications are full-stack applications, meaning they involve multiple interconnected layers working together.</p>
<p>Here’s a simplified overview of a typical full-stack application:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1755603745131/1114a65f-ea7c-4e3f-aca6-4821c8ca683a.png" alt="full stack web overview" width="600" height="400" loading="lazy"></p>
<p>Before diving deep into each of these components, let me first give you a quick, high-level overview of what they are and how they fit into the bigger picture (starting from the bottom of the image above).</p>
<ul>
<li><p><strong>Frontend</strong> – The user interface where people interact with your application.</p>
</li>
<li><p><strong>Backend</strong> – The logic and brain of the application that processes requests.</p>
</li>
<li><p><strong>APIs</strong> – The bridge that allows communication between frontend, backend, and external services.</p>
</li>
<li><p><strong>Database</strong> – The storage system where all your structured information lives.</p>
</li>
<li><p><strong>Server</strong> – The infrastructure that hosts, runs, and delivers your application.</p>
</li>
</ul>
<p>Now, we need to understand how computers talk to each other.</p>
<h3 id="heading-how-computers-talk-to-each-other-the-internet">How Computers Talk to Each Other (The Internet)</h3>
<p>When you type a website's URL into the browser – and this site could be a simple portfolio site or a full stack app – how does your computer know where to send the request? It uses the <a target="_blank" href="https://www.freecodecamp.org/news/what-is-dns/"><strong>Domain Name System</strong></a> <strong>(DNS)</strong>. The DNS is like a phonebook for the internet – it translates a human-readable website name, like "example.com," into a unique numeric IP address that computers can understand.</p>
<p>Once your computer has the IP address, it uses <strong>communication protocols</strong> to send and receive data. One of the most important protocols is <a target="_blank" href="https://www.freecodecamp.org/news/tcp-vs-udp/"><strong>TCP</strong></a>. It breaks data into small, numbered packets. If a packet gets lost or arrives out of order, TCP ensures it's resent and reassembled correctly, making it a very reliable way to send data.</p>
<p>On top of TCP, we use higher-level protocols like <a target="_blank" href="https://www.freecodecamp.org/news/what-is-http/"><strong>HTTP</strong></a>. This is an application-level protocol that's easier for developers to use. It's the language your browser speaks to the server.</p>
<p><strong>HTTPS</strong> is the same, but it adds an extra layer of encryption for security.</p>
<p>Now that we understand the basics of the Internet, remember that it serves billions of people worldwide.</p>
<p>Let’s break this down with a real-life example. Imagine you own a restaurant with a seating capacity of 50 people. One day, 10 extra guests arrive – and with a bit of adjustment, you still manage. But suddenly, a thousand more people show up at your door. What would you do then? It’s not just about adding more chairs and tables anymore – you’ll need extra food supplies, more staff, and a bigger setup to handle such massive traffic.</p>
<p>This simple example reflects the real challenge of growth and scalability. And that’s exactly what I’ll be diving into in the next chapter of this tutorial.</p>
<h3 id="heading-the-problem-of-growth">The Problem of Growth</h3>
<p>Imagine you've built a simple website for a local sports team. Initially, it's just you and a few friends using it, so a single server is sufficient. This server holds all the website's logic and connects to a single database where player stats are stored.</p>
<p>As the team becomes more popular, though, more people visit your site, and it suddenly becomes slow. This is a <strong>scaling issue</strong>. Your system can't handle all the new traffic.</p>
<h4 id="heading-scaling-your-system-two-main-ways">Scaling Your System: Two Main Ways</h4>
<p>There are two ways to solve this. The first is <strong>vertical scaling</strong>. This is like giving your one server a bigger engine and more memory. You'd upgrade the CPU (the brain) or add more RAM (temporary memory). You could also use a faster disk storage like an SSD.</p>
<p>The problem is, you can only upgrade so much before you hit the limits of what's available. Plus, if that single server fails, your entire website goes down.</p>
<p>A better approach is <strong>horizontal scaling</strong>. This means adding more servers instead of just upgrading one. Now you have a team of servers, and each can handle a portion of the incoming user requests.</p>
<p>This approach allows for almost unlimited growth. It also creates redundancy and fault tolerance, because if one server breaks, the others can pick up the slack, and your site stays online.</p>
<h4 id="heading-directing-traffic-with-a-load-balancer">Directing Traffic with a Load Balancer</h4>
<p>With multiple servers, you need a way to make sure no single server gets overwhelmed. This is where a <strong>load balancer</strong> comes in. It acts like a traffic cop, sitting in front of your servers and directing each new request to the best-suited server. It uses different algorithms to decide where to send the traffic.</p>
<p>For example, the <strong>Round Robin</strong> method sends requests to servers one by one, in a cycle. Another method is <strong>Least Connection</strong>, which sends the request to the server that has the fewest active connections.</p>
<h3 id="heading-speeding-up-your-website-with-caching-and-cdns">Speeding Up Your Website with Caching and CDNs</h3>
<p>Imagine your website is now used by people all over the world. A user in another country might experience slow loading times because their request has to travel all the way to your servers.</p>
<p>To fix this, you can use a <a target="_blank" href="https://www.freecodecamp.org/news/how-cdns-improve-performance-in-front-end-projects/"><strong>Content Delivery Network (CDN)</strong></a>. A CDN is a network of servers around the world that store copies of your website's static files – like images, videos, and text files. When a user requests one of these files, the CDN serves it from the closest server, making the website load much faster for them.</p>
<p>This process is a form of <strong>caching</strong>. Caching is the general idea of making copies of data and storing them in a faster-to-access location. You can cache data on your server so it doesn't need to fetch the same player stats from the database every time. This reduces the load on your database and speeds up the entire application.</p>
<p>You can read more about the <a target="_blank" href="https://www.freecodecamp.org/news/caching-vs-content-delivery-network/">difference between CDNs and caching here</a>.</p>
<h3 id="heading-building-your-application-monolith-vs-microservices">Building Your Application: Monolith vs. Microservices</h3>
<p>As your website grows, its code can become a tangled mess. You might start with a <strong>monolith</strong>, where all the features (like player stats and live scores, in our example) are built into a single, large program. <a target="_blank" href="https://www.freecodecamp.org/news/microservices-vs-monoliths-explained/">A monolith is easier to start with</a>, but it can be hard to manage and update.</p>
<p>A better approach for a large-scale application is to use a <a target="_blank" href="https://www.freecodecamp.org/news/the-microservices-book-build-and-manage-services-in-the-cloud/"><strong>microservice architecture</strong></a>. This means breaking your application into smaller, independent services, each with a specific job. For example, one service could handle player stats and another could handle live scores. This makes your code more organised and easier to update, because a change in one service won't affect the others.</p>
<p>With microservices, you need an <a target="_blank" href="https://www.freecodecamp.org/news/what-are-api-gateways/"><strong>API Gateway</strong></a>. This acts as a single entry point for all user requests, directing them to the correct microservice behind the scenes. It also handles security and other common tasks.</p>
<h3 id="heading-the-apis">The APIs</h3>
<p>Think of <strong>APIs (Application Programming Interfaces)</strong> as the “middlemen” that let different pieces of software talk to each other.</p>
<p>In simple terms, an API is like a waiter in a restaurant. You (the user) tell the waiter what you want, the waiter takes your order to the kitchen (the system), and then brings the food (data or result) back to you.</p>
<p>Without APIs, your app, website, or software wouldn’t know how to ask another system for information or services.</p>
<p>For example, on our sports team website:</p>
<ul>
<li><p>The front-end (what fans see) uses an API to <strong>fetch player stats</strong> from the database.</p>
</li>
<li><p>When someone buys match tickets, the API talks to the <strong>payment system</strong> to confirm the transaction.</p>
</li>
<li><p>If fans want live score updates, the API makes sure the real-time data flows smoothly from the server to their screen.</p>
</li>
</ul>
<p>So, APIs are important for system design because they shape how efficiently different systems connect, share data, and stay reliable under real-world use.</p>
<p>Your front-end and back-end services can communicate in several ways. The most common is a <a target="_blank" href="https://www.freecodecamp.org/news/what-is-a-rest-api/"><strong>REST API</strong></a>. It's a standardised set of rules that uses HTTP to create a consistent way for a client and server to talk to each other. For example, it defines a standard way to signal a successful request ("OK") or a server error ("Internal Server Error").</p>
<h4 id="heading-when-to-use-rest">When to use REST</h4>
<ul>
<li><p>Best when: you need simplicity, broad adoption, and easy integration with browsers, mobile apps, or third-party services.</p>
</li>
<li><p>Example: CRUD apps (blogging platforms, e-commerce sites, user management).</p>
</li>
<li><p>Strength: Human-readable JSON, stateless, widely supported.</p>
</li>
<li><p>Weakness: Over-fetching (getting more data than needed) or under-fetching (not enough data).</p>
</li>
</ul>
<p>Another style is <a target="_blank" href="https://www.freecodecamp.org/news/building-consuming-and-documenting-a-graphql-api/"><strong>GraphQL</strong></a>. Instead of getting all the data a REST API provides, GraphQL lets the client ask for only the specific data it needs, which can make things faster and more efficient.</p>
<h4 id="heading-when-to-use-graphql">When to use GraphQL</h4>
<ul>
<li><p>Best when: clients (like mobile apps) need fine-grained control over exactly what data they fetch.</p>
</li>
<li><p>Example: social media feeds, dashboards with lots of widgets, mobile apps with limited bandwidth.</p>
</li>
<li><p>Strength: Flexible queries, reduces over-fetching, strong typing system.</p>
</li>
<li><p>Weakness: More complex server setup, which can cause performance issues if queries aren’t optimised.</p>
</li>
</ul>
<p>For server-to-server communication, <a target="_blank" href="https://www.freecodecamp.org/news/what-is-grpc-protocol-buffers-stream-architecture/"><strong>gRPC</strong></a> is often used. It's known for being very fast because it uses a more efficient data format called Protocol Buffers instead of JSON.</p>
<h4 id="heading-when-to-use-grpc">When to use gRPC</h4>
<ul>
<li><p>Best when: services talk to each other in microservice architectures, and speed/efficiency is critical.</p>
</li>
<li><p>Example: real-time systems (streaming, payments, IoT, machine learning inference).</p>
</li>
<li><p>Strength: Super fast (binary Protocol Buffers), built-in support for streaming, strong contracts.</p>
</li>
<li><p>Weakness: Not browser-native (needs extra tooling for web), harder debugging compared to REST</p>
</li>
</ul>
<p>So to summarize based on my observations of what I have worked on so far:</p>
<ul>
<li><p>If you’re building something <strong>public-facing and widely consumed</strong> → go for REST.</p>
</li>
<li><p>If your app has <strong>complex, dynamic queries from clients</strong> → go for GraphQL.</p>
</li>
<li><p>If you’re dealing with <strong>high-performance internal service-to-service calls</strong> → go for gRPC.</p>
</li>
</ul>
<p>In system design, choosing the right API style directly affects performance, scalability, and user experience. If you pick REST for its simplicity, GraphQL for its flexibility, or gRPC for its speed, you’re shaping how well your system can grow and adapt as real-world demands change.</p>
<h3 id="heading-handling-real-time-data">Handling Real-Time Data</h3>
<p>Real-time data handling is challenging because it requires maintaining an active connection to continuously transmit and receive data simultaneously. Traditional servers follow a request–response model, where data is only sent when explicitly requested.</p>
<p>That's where <a target="_blank" href="https://www.freecodecamp.org/news/learn-websockets-socket-io/"><strong>WebSockets</strong></a> come in. Unlike HTTP, which is a one-and-done request-and-response model, a WebSocket creates a continuous, two-way connection between the client and server. This allows the server to send updates to the user as soon as they happen, creating a real-time experience.</p>
<p>When microservices need to communicate without being directly connected, they can use <a target="_blank" href="https://www.freecodecamp.org/news/how-message-queues-make-distributed-systems-more-reliable/"><strong>message queues</strong></a>. A service sends a message to the queue, and another service picks it up when it's ready. This helps to decouple the services, so they don't have to worry about the other service being available at that exact moment.</p>
<p>On our sports site, WebSockets allow fans to see live scores instantly without refreshing the page – just like in chat apps, but here it keeps the excitement of the game alive in real time</p>
<h3 id="heading-databases">Databases</h3>
<p>Databases are a critical part of any full-stack application because they serve as the permanent home for user data. Once you’ve decided how to scale your servers and manage communication, you also need to consider the database layer. If everything else scales but the database does not, it can quickly become a bottleneck – leading to crashes, inconsistent records, or even data loss.</p>
<p>Many applications rely on <a target="_blank" href="https://www.freecodecamp.org/news/learn-relational-database-basics-key-concepts-for-beginners/"><strong>relational databases (SQL)</strong></a>, which store data in structured tables with rows and columns and are great for handling structured information. But for applications requiring high flexibility or handling massive unstructured datasets, <a target="_blank" href="https://www.freecodecamp.org/news/learn-nosql-in-3-hours/"><strong>NoSQL databases</strong></a> (like MongoDB or Cassandra) are often chosen. These databases don't follow the strict rules of SQL and are better for handling massive amounts of data.</p>
<p>They follow <a target="_blank" href="https://www.freecodecamp.org/news/acid-databases-explained/">ACID properties</a>:</p>
<ul>
<li><p><strong>Atomicity:</strong> A transaction is all or nothing.</p>
</li>
<li><p><strong>Consistency:</strong> The data always remains in a valid state.</p>
</li>
<li><p><strong>Isolation:</strong> Multiple transactions don't interfere with each other.</p>
</li>
<li><p><strong>Durability:</strong> Once a transaction is complete, the data is permanently saved.</p>
</li>
</ul>
<p>Just like with servers, you might need to scale your database. You can use <strong>sharding</strong>, which divides your data across multiple databases, or <strong>replication</strong>, which creates copies of your database to handle more read requests.</p>
<h3 id="heading-understanding-the-cap-theorem">Understanding the CAP Theorem</h3>
<p>When you're dealing with a distributed system and multiple databases, you inevitably face trade-offs. The <strong>CAP Theorem</strong> states that you can only guarantee two out of the following three properties at the same time:</p>
<ul>
<li><p><strong>Consistency</strong> – Every user sees the same, most up-to-date data.</p>
</li>
<li><p><strong>Availability</strong> – The system is always available to respond to requests.</p>
</li>
<li><p><strong>Partition Tolerance</strong> – The system continues to operate even if a part of the network fails.</p>
</li>
</ul>
<p>Now, from a system design perspective, this theorem forces us to make conscious architectural choices. For example, in financial applications (like banking), consistency often takes priority over availability because even a small inconsistency in balance data can cause chaos.</p>
<p>On the other hand, in social media feeds, availability and partition tolerance are often prioritised – it's okay if you see a slightly outdated post, but the system should never be down.</p>
<p>In the flow we’ve been discussing, whenever we introduce a new component or scale out across multiple regions, we need to reassess which two guarantees matter most for our business case. That decision directly drives what database technology we pick, how we design failover strategies, and what trade-offs we accept in user experience.</p>
<p>In short, the CAP theorem isn’t just a theory – it’s a practical compass. It guides us to balance user expectations, business priorities, and technical feasibility without breaking existing functionality, while still leaving room for future growth.</p>
<h3 id="heading-rate-limiting-and-monitoring">Rate Limiting and Monitoring</h3>
<p>When designing a system, it’s not just about making it <em>work</em> – it’s about making it resilient. Two core guardrails here are <strong>rate limiting</strong> and <strong>monitoring</strong>.</p>
<h4 id="heading-what-is-rate-limiting">What is Rate Limiting?</h4>
<p>Rate limiting is the practice of controlling how many requests a user, client, or service can make to your system within a given timeframe. For example, you might cap an API at 100 calls per user per hour. This prevents abuse, safeguards against denial-of-service attempts, and ensures fair usage across all consumers.</p>
<p>Rate limiting comes into play any time your service is exposed publicly or internally to multiple clients.</p>
<p><a target="_blank" href="https://www.freecodecamp.org/news/implement-api-rate-limiting-in-strapi/">To incorporate it</a>, you can implement limits at the API gateway, reverse proxy (like NGINX), or within your service logic itself. Many cloud providers (AWS API Gateway, GCP Endpoints) also have built-in support.</p>
<h4 id="heading-what-is-monitoring">What is Monitoring?</h4>
<p><a target="_blank" href="https://www.freecodecamp.org/news/the-front-end-monitoring-handbook/">Monitoring</a> is the practice of collecting metrics, logs, and traces from your system to understand its health in real time. Typical signals include:</p>
<ul>
<li><p><strong>Error rates</strong> (for example, how often requests fail)</p>
</li>
<li><p><strong>Latency</strong> (how long requests take)</p>
</li>
<li><p><strong>Traffic volume</strong> (load across the system)</p>
</li>
<li><p><strong>Resource utilisation</strong> (CPU, memory, disk, and so on)</p>
</li>
</ul>
<p>Monitoring is important from day one – it’s your feedback loop. Without it, you’re essentially flying blind.</p>
<p>To work it into your system, you can use observability stacks like <a target="_blank" href="https://www.freecodecamp.org/news/kubernetes-cluster-observability-with-prometheus-and-grafana-on-aws/">Prometheus + Grafana</a>, or managed solutions like Datadog, New Relic, or CloudWatch. You can also set alerts for threshold breaches (for example, 5% error rate spike).</p>
<p>In practice, rate limiting and monitoring work hand-in-hand. Rate limiting proactively guards against overload, while monitoring gives you visibility into whether the limits are working, whether scaling is needed, or whether a new type of failure is emerging.</p>
<p>For example, if you’ve designed a booking system (like in our earlier flow), rate limiting would ensure a single user can’t spam seat reservations, while monitoring would flag anomalies such as unusual spikes in request volume or sudden latency increases – helping you act before the system collapses.</p>
<h4 id="heading-why-does-this-matter-for-system-design">Why Does This Matter for System Design?</h4>
<p>These topics matter for good system design because they form the foundational building blocks of how modern applications actually operate in the real world. The way systems communicate, the type of APIs we adopt, and how we manage real-time interactions directly influence whether a product feels fast, reliable, and seamless – or slow and frustrating. In short, they determine how well the overall experience holds up when real users put it to the test.</p>
<p>When we develop a deeper understanding of how computers communicate, we begin to see the inner mechanics of client–server architecture – how APIs fetch data from databases through backend system calls. From this baseline, we can pivot into higher-level concerns:</p>
<ul>
<li><p><strong>Scalability and resilience</strong>: Using load balancers to protect against server overload.</p>
</li>
<li><p><strong>Security</strong>: Introducing rate limiting to mitigate potential cyberattacks.</p>
</li>
<li><p><strong>Efficiency</strong>: Choosing the right type of API calls and leveraging caching/CDNs for speed and reduced overhead.</p>
</li>
<li><p><strong>Reliability</strong>: Implementing logging and monitoring to detect issues early and debug faster.</p>
</li>
</ul>
<p>Together, these practices elevate a system from simply <em>working</em> to being robust, performant, and future-ready.</p>
<p>We’ve discussed the basics of all the most important concepts you’ll need to understand before building an end-to-end system. Now it’s time to deep dive into the case studies, where I’ll show you how different types of applications use system design to scale and serve billions of users.</p>
<p>I have picked services that are complex to build and handle multiple different types of components at a time, like gaming, education, and job search platforms.</p>
<p>Now let’s decode each of them together, and I’ll explain how I would scale the application if I were the developer building it.</p>
<h2 id="heading-case-studies-scaling-in-the-real-world">Case Studies: Scaling in the Real World</h2>
<p>System design is best understood when you see it in action. To show how principles like scaling, caching, load balancing, and real-time data management come together, let’s walk through two very different types of applications:</p>
<ul>
<li><p>A <strong>job search platform</strong> (focused on structured data and reliability).</p>
</li>
<li><p>An <strong>online gaming platform</strong> (focused on real-time speed and responsiveness).</p>
</li>
</ul>
<p>Looking at both will show you that, while the tools and concepts may be similar, the way we apply them depends on the type of system we’re building.</p>
<p>Both are high-traffic platforms, but with totally different needs. The job portal is about accuracy, reliability, and data-driven workflows, while the gaming platform is about instant responsiveness, fairness, and global reach.</p>
<p>In a job portal, a 1-second delay just means waiting. In a gaming app, a 1-second delay could mean losing the match. Both are failures – but for completely different reasons, and with different consequences.</p>
<p>Together, they show how the same building blocks of system design (scaling, caching, APIs, monitoring) are applied differently depending on context.</p>
<h3 id="heading-case-study-1-scaling-a-job-search-application">Case Study 1: Scaling a Job Search Application</h3>
<p>A job search platform is one of the most used applications nowadays, as there are always people looking for a job. And there are many different job portals out there that handle the complete process, from finding jobs to user onboarding.</p>
<p>We’ll look at an example site called <a target="_blank" href="https://upstaff.com/">Upstaff</a>. It’s a platform that focuses on hiring AI engineers as its core service (although it services other job profiles as well). At its core, it handles structured information – things like user profiles, job postings, and applications.</p>
<p>Day one, you have a few hundred users. On day one hundred, you may have tens of thousands. And in a year? Possibly millions. That growth means you have to think about scale, speed, and data integrity from the start.</p>
<h4 id="heading-the-core-components">🔹 The Core Components</h4>
<ul>
<li><p><strong>User Management:</strong> registration, login, and role-based access (job seeker vs employer).</p>
</li>
<li><p><strong>User Profiles</strong>: résumés, skills, preferences, stored in structured databases.</p>
</li>
<li><p><strong>Job Posting and Listings</strong>: employers create jobs, seekers browse/search/filter.</p>
</li>
<li><p><strong>Application Tracking</strong>: Every job seeker’s application status needs to be accurate and up to date.</p>
</li>
<li><p><strong>Recommendation Engine</strong>: jobs matched to users based on history and profile.</p>
</li>
<li><p><strong>Notifications</strong>: alerts for new job matches, recruiter replies, deadlines.</p>
</li>
</ul>
<p>Every one of these features depends on the system’s ability to handle large amounts of structured data – and handle it reliably.</p>
<h4 id="heading-step-1-starting-small">Step 1: Starting Small</h4>
<p>At the beginning, everything can run on one server with a single database. This setup is enough for a few thousand users.</p>
<h4 id="heading-step-2-growth-and-traffic-spikes">Step 2: Growth and Traffic Spikes</h4>
<p>As more users join, the single server starts to slow down. To fix this, we add a load balancer and scale horizontally – adding multiple servers that share the traffic.</p>
<h4 id="heading-step-3-database-challenges">Step 3: Database Challenges</h4>
<p>Soon, the database becomes the bottleneck. Searching across thousands of jobs slows things down. To fix this, we:</p>
<ul>
<li><p>Use sharding (split the database by user IDs or job IDs).</p>
</li>
<li><p>Add a cache (like Redis) to store frequent queries such as “Software Engineer in New York.”</p>
</li>
<li><p>Use a CDN to deliver logos, profile pictures, and other static files faster.</p>
</li>
</ul>
<h4 id="heading-step-4-heavy-features">Step 4: Heavy Features</h4>
<p>New features like a résumé parser or recommendation engine require extra computing power. Instead of overloading the main app, we move these into separate microservices.</p>
<h4 id="heading-step-5-security-and-reliability">Step 5: Security and Reliability</h4>
<p>Finally, as traffic grows, we add:</p>
<ul>
<li><p><strong>Rate limiting</strong> to stop any one user from spamming APIs.</p>
</li>
<li><p><strong>Monitoring</strong> to track errors, latency, and user activity in real time.</p>
</li>
<li><p><strong>API Gateway</strong> to ensure all requests are secure and validated. Here is an overview of the entire system scaling in an image :</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754281195211/570680c1-2813-43b0-87a1-48a817ab6c9a.png" alt="system design job portal flowchart" width="600" height="400" loading="lazy"></p>
<p>This example shows how careful planning makes growth smooth. By scaling horizontally, caching smartly, and splitting heavy features into microservices, a job portal like Upstaff can handle millions of users without breaking.</p>
<h3 id="heading-case-study-2-scaling-an-online-gaming-application">Case Study 2: Scaling an Online Gaming Application</h3>
<p>Now let’s flip the script. In a gaming platform like <a target="_blank" href="https://xn--ntcasinoutanlicens-ltb.com">this site</a>, speed and responsiveness matter more than anything. A 1-second delay in a job search is annoying. But in gaming, a 1-second delay can make players quit forever. Unlike job portals, the biggest challenge here is real-time responsiveness. A tiny delay can ruin the user experience.</p>
<h4 id="heading-the-core-components-1">🔹 The Core Components</h4>
<ul>
<li><p><strong>User Management Service</strong>: accounts, profiles, and login.</p>
</li>
<li><p><strong>Game Lobby and Matchmaking</strong>: pair players by skill, region, and latency.</p>
</li>
<li><p><strong>Game Server Manager</strong>: spin up and manage live matches.</p>
</li>
<li><p><strong>Real-Time Communication</strong>: powered by WebSockets or UDP for low latency.</p>
</li>
<li><p><strong>Game State Store (Redis)</strong>: fast sync of health, scores, and positions.</p>
</li>
<li><p><strong>Leaderboard &amp; Stats Engine</strong>: global rankings, achievements, and progress.</p>
</li>
<li><p><strong>In-Game Economy</strong>: coins, tokens, inventory.</p>
</li>
<li><p><strong>Payment Gateway</strong>: subscriptions and purchases.</p>
</li>
<li><p><strong>Anti-Cheat Security Layer</strong>: fairness across all players.</p>
</li>
<li><p><strong>Monitoring and Logging</strong>: server uptime, latency, and crash reports.</p>
</li>
</ul>
<p>Unlike a job portal, every millisecond counts.</p>
<h4 id="heading-step-1-starting-small-1">Step 1: Starting Small</h4>
<p>At first, one powerful server is enough to run both the game logic and user accounts. With just a few players, things run smoothly.</p>
<h4 id="heading-step-2-more-players-more-problems">Step 2: More Players, More Problems</h4>
<p>As millions of players log in, the single server crashes. To fix this, we:</p>
<ul>
<li><p>Add a Game Server Manager that spins up separate servers for each match.</p>
</li>
<li><p>Introduce a load balancer that assigns players to available servers.</p>
</li>
</ul>
<h4 id="heading-step-3-real-time-data-handling">Step 3: Real-Time Data Handling</h4>
<p>In gaming, speed is everything. Instead of slow HTTP, we switch to WebSockets or UDP for instant communication. To keep everyone’s game view in sync:</p>
<ul>
<li><p>Use in-memory databases like Redis for positions, scores, and health.</p>
</li>
<li><p>Update leaderboards in near real time.</p>
</li>
</ul>
<h4 id="heading-step-4-scaling-features">Step 4: Scaling Features</h4>
<p>Other services run in parallel:</p>
<ul>
<li><p><strong>Matchmaking service</strong> pairs players by skill, location, and latency.</p>
</li>
<li><p><strong>Economy service</strong> manages coins, rewards, and in-game items.</p>
</li>
<li><p><strong>Payment gateway</strong> handles subscriptions and purchases securely.</p>
</li>
<li><p><strong>Notification system</strong> sends updates like “new event starting.”</p>
</li>
</ul>
<h4 id="heading-step-5-global-expansion-and-security">Step 5: Global Expansion and Security</h4>
<p>When the game expands worldwide:</p>
<ul>
<li><p>Use a CDN to deliver maps and skins quickly to all regions.</p>
</li>
<li><p>Add an Anti-Cheat layer to detect and block unfair play.</p>
</li>
<li><p>Build an Admin and Monitoring panel to track system health and user behavior.</p>
</li>
</ul>
<p>In gaming, system design focuses less on structured data and more on low latency, real-time communication, and fairness. Scaling here means keeping gameplay smooth and secure, even when millions of players join at once. Here is the image representation of the complete game platform system design</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1754287625776/546f3949-efa1-4a57-9906-ceb3f8a62f63.png" alt="system design game website flowchart" width="600" height="400" loading="lazy"></p>
<h3 id="heading-why-both-case-studies-matter">Why Both Case Studies Matter</h3>
<p>You might wonder – why show two different systems instead of just one? The answer is that system design isn’t “one-size-fits-all.”</p>
<ul>
<li><p>The job portal teaches us how to scale structured, data-heavy applications where reliability and accuracy matter most.</p>
</li>
<li><p>The gaming platform shows us how to design for speed, real-time communication, and fairness under extreme load.</p>
</li>
</ul>
<p>Together, these examples prove that the same system design principles of scaling, caching, monitoring, and microservices apply everywhere. What changes is <em>how you use them</em> to solve the unique challenges of your platform.</p>
<h2 id="heading-qampa">Q&amp;A</h2>
<h3 id="heading-how-to-get-into-system-design-if-you-dont-understand-anything-yet"><strong>How to get into system design if you don’t understand anything (yet)?</strong></h3>
<p>I get this question all the time – and the first thing you need to know is that system design isn’t some separate, elite domain. It’s an <em>additional skill</em> that complements your development journey.</p>
<p>If you're a full-stack developer (or aiming to be one), learning system design gives you a huge edge. After all, building an app isn't just about making it work – it’s about making it <em>work well</em> at scale.</p>
<p>So if you’re just starting and don’t even know how to become a full-stack developer yet, start there. Learn to build applications first, and then system design will start making a lot more sense. Read this guide <a target="_blank" href="https://www.freecodecamp.org/news/become-a-full-stack-developer-and-get-a-job/">How to Become a Full-Stack Developer in 2025 (and Get a Job) – A Handbook for Beginners</a> to learn how to become a full-stack developer.</p>
<h3 id="heading-how-do-you-understand-system-design-concepts"><strong>How do you understand system design concepts?</strong></h3>
<p>The short answer: with time and consistent practice.</p>
<p>Think of it like this: if you know how to use a pencil, it’s up to you whether you use it to sketch or to write. The pencil is just a tool. Similarly, in system design, once you understand the core concepts, it’s about knowing <em>when</em> and <em>where</em> to apply them. The rest – frameworks, tools, and technologies – are just means to an end.</p>
<p>It’s not about memorising patterns, it’s about developing the instinct to use the right building blocks at the right time.</p>
<h3 id="heading-what-tools-should-you-know-before-diving-into-system-design">What tools should you know before diving into system design?</h3>
<p>The truth is, the list keeps growing. New tools and platforms are constantly emerging. But in my experience, having a solid foundation in the following areas makes a huge difference:</p>
<ul>
<li><p><strong>Full Stack Development</strong> – so you understand how both frontend and backend systems interact.</p>
</li>
<li><p><strong>Cloud Platforms</strong> (like AWS, GCP, or Azure) – because most modern systems are cloud-native.</p>
</li>
<li><p><strong>CI/CD Pipelines</strong> – for automating testing, integration, and deployment.</p>
</li>
<li><p><strong>Deployment Strategies</strong> – to know how to roll out new changes with minimal risk.</p>
</li>
</ul>
<p>Mastering these gives you the technical muscle to design systems that are scalable, reliable, and production-ready. I am a frontend developer, why should I know the system design</p>
<h3 id="heading-what-resources-should-i-study-to-learn-system-design">What resources should I study to learn system design?</h3>
<p>In my last <a target="_blank" href="https://www.freecodecamp.org/news/become-a-full-stack-developer-and-get-a-job">article</a>, I shared all the resources that helped me learn system design.</p>
<p>System design is crucial for building reliable, high-performance applications. I explored the following resources:</p>
<ul>
<li><p><a target="_blank" href="https://github.com/donnemartin/system-design-primer"><strong>Free GitHub system design repositor</strong></a><strong>y</strong></p>
</li>
<li><p><a target="_blank" href="https://github.com/ByteByteGoHq/system-design-101"><strong>Another free GitHub system design repositor</strong></a><strong>y</strong></p>
</li>
<li><p><a target="_blank" href="https://www.systemdesignhandbook.com/system-design-interview-handbook/">Free System Design Handbook</a></p>
</li>
<li><p><a target="_blank" href="https://grokkingthesystemdesign.com/intro-to-system-design/">Free Hands-on System Design Learning Platform</a></p>
</li>
<li><p><a target="_blank" href="https://www.educative.io/guide/system-design">Free Intro to System Design Blog</a></p>
</li>
</ul>
<p>Case studies and real-world architectures can also help you understand large-scale systems. You can follow any big tech engineering blog (Uber has a great one).</p>
<p>For high-level concepts, I went through the <a target="_blank" href="https://www.educative.io/courses/grokking-the-system-design-interview"><strong>Grokking System Design</strong></a> course. It’s a paid resource, and I used it to deepen my understanding of system design. It’s not mandatory, but it helped me think about architecture at scale.</p>
<p><strong>Note:</strong> there are other sites and courses out there of course, but I only share what I have personally experienced and used, and I focus on FREE material first.</p>
<h3 id="heading-where-to-practice-system-design">Where to practice system design</h3>
<p>This is where real learning begins. Start by picking any existing application from the internet, just like I did. Google something specific, like “job application portal,” but avoid the results on the first page. Those apps are usually well-optimised and already follow best practices in system design.</p>
<p>Instead, dig deeper and explore results from the second or third page. Look for an app that seems to be in its early stages.</p>
<p>Once you find one, try to understand how the entire application works. Break it down into its core components and then imagine what would happen if that app started receiving 1 million users a day. You’ll naturally begin to see what system design elements are needed to handle that kind of load.</p>
<h2 id="heading-final-notes">Final Notes</h2>
<p>Learning system design becomes much easier when you’ve already built something. Let’s say you’ve created an app and now you're thinking about how to scale it – that’s where real learning begins. The moment you start writing down your requirements (like how your app should behave when it starts getting more traffic), you naturally begin to develop system-level thinking. It’s this process of planning and anticipating real-world usage that turns theory into a practical skill.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>Full Stack + System Design = The Ultimate Developer Stack 🔥</p>
<p>By mastering these skills, you can turn any idea into a real-world product, secure high-paying jobs, and even start your tech venture.</p>
<p>Now it's your turn – what are you building next? Let me know!</p>
<p>That’s all from my side. If you found this article helpful, feel free to share it and connect with me. I’m always open to new opportunities:</p>
<ul>
<li><p>Follow me on X: <a target="_blank" href="https://x.com/prankurpandeyy">Prankur's Twitter</a></p>
</li>
<li><p>Connect with me on LinkedIn: <a target="_blank" href="https://linkedin.com/in/prankurpandeyy">Prankur's LinkedIn</a></p>
</li>
<li><p>Follow me on GitHub: <a target="_blank" href="https://github.com/prankurpandeyy">Prankur’s Github</a></p>
</li>
<li><p>View my Portfolio: <a target="_blank" href="https://prankurpandeyy.netlify.app/">Prankur's Portfolio</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build Production-Ready Full Stack Apps with the MERN Stack ]]>
                </title>
                <description>
                    <![CDATA[ As developers, we’re always looking for more efficient tools. The MERN stack (MongoDB, Express.js, React, and Node.js) stands out for its JavaScript-centric nature, offering a unified language across the entire application. In this guide, you'll buil... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-production-ready-full-stack-apps-with-the-mern-stack/</link>
                <guid isPermaLink="false">686bd0446349e98b57ebc099</guid>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ software development ]]>
                    </category>
                
                    <category>
                        <![CDATA[ clean code ]]>
                    </category>
                
                    <category>
                        <![CDATA[ best practices ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Mohit Menghnani ]]>
                </dc:creator>
                <pubDate>Mon, 07 Jul 2025 13:48:52 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1751502709499/b43b3607-f01b-45c0-9797-75eef92497c6.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>As developers, we’re always looking for more efficient tools. The MERN stack (MongoDB, Express.js, React, and Node.js) stands out for its JavaScript-centric nature, offering a unified language across the entire application.</p>
<p>In this guide, you'll build a complete Task Manager app with user authentication, protected routes, and full CRUD functionality, built with React on the frontend and Express/MongoDB on the backend.</p>
<p>This article will serve as your hands-on, code-first guide to building, securing, and deploying a MERN application, drawing from my own practical experience. Every section has code you can run, and I’ll give concise explanations along the way.</p>
<p>It doesn’t matter if you're just getting started with MERN or looking to level up your architecture and production deployment knowledge – this article is designed to get you from zero to production with confidence.</p>
<h2 id="heading-table-of-contents">Table of Contents</h2>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites">Prerequisites</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-tools-amp-tech-stack">Tools &amp; Tech Stack</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-skills-amp-setup">Skills &amp; Setup</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-project-setup-laying-the-groundwork">Project Setup: Laying the Groundwork</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-project-structure">Project Structure</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-code-quality-linting-and-formatting">Code Quality: Linting and Formatting</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-testing-ensuring-robustness">Testing: Ensuring Robustness</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-backend-testing-nodejsexpressjs">Backend Testing (Node.js/Express.js)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-testing-react-testing-library-cypress">Frontend Testing (React Testing Library + Cypress)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-task-manager">How to Build the Task Manager</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-backend-implementation-nodejsexpressjs">Backend Implementation (Node.js/Express.js)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-implementation-react">Frontend Implementation (React)</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-deployment-from-localhost-to-live">Deployment: From</a> <a target="_blank" href="http://Localhost">Localhost</a> <a class="post-section-overview" href="#heading-deployment-from-localhost-to-live">to Live</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-backend-deployment-nodejsexpressjs">Backend Deployment (Node.js/Express.js)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-frontend-deployment-react">Frontend Deployment (React)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-database-deployment-mongodb-atlas">Database Deployment (MongoDB Atlas)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-1-env-configuration-example">1. .env Configuration Example</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-2-connect-to-mongodb-in-appjs">2. Connect to MongoDB in app.js</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-other-deployment-options">Other Deployment Options</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-security-best-practices-fortifying-your-application">Security Best Practices: Fortifying Your Application</a></p>
<ul>
<li><p><a class="post-section-overview" href="#heading-setup-input-validation-and-sanitization">Setup Input Validation and Sanitization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-add-authentication-and-authorization">Add Authentication and Authorization</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-implement-rate-limiting">Implement Rate Limiting</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-setup-cors-configuration-cross-origin-resource-sharing">Setup CORS Configuration (Cross-Origin Resource Sharing)</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-use-environment-variables">Use Environment Variables</a></p>
</li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-monitoring-and-logging-with-winston-and-morgan">Monitoring and Logging with Winston and Morgan</a></p>
<ul>
<li><a class="post-section-overview" href="#heading-frontend-error-monitoring-sentry">Frontend Error Monitoring (Sentry)</a></li>
</ul>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites">Prerequisites</h2>
<p>Before jumping in the project, here’s what you’ll need to get the most out of this tutorial:</p>
<h3 id="heading-tools-amp-tech-stack">Tools &amp; Tech Stack</h3>
<p>You’ll be using the following technologies throughout the project:</p>
<ul>
<li><p><strong>Node.js &amp; npm</strong> – Backend runtime and package manager</p>
</li>
<li><p><strong>Express.js</strong> – Web framework for Node</p>
</li>
<li><p><strong>MongoDB Atlas</strong> – Cloud-hosted NoSQL database</p>
</li>
<li><p><strong>Mongoose</strong> – ODM for MongoDB</p>
</li>
<li><p><strong>React</strong> – Frontend UI library</p>
</li>
<li><p><strong>React Router</strong> – For client-side routing</p>
</li>
<li><p><strong>Axios</strong> – For making API requests</p>
</li>
<li><p><strong>Jest &amp; Supertest</strong> – For backend tests</p>
</li>
<li><p><strong>React Testing Library &amp; Cypress</strong> – For Frontend unit and E2E tests</p>
</li>
<li><p><strong>ESLint + Prettier</strong> – For code formatting, linting</p>
</li>
<li><p><strong>Husky</strong> – To setup pre-commit hooks</p>
</li>
<li><p><strong>Helmet, Joi, express-rate-limit, cors</strong> – For security, validation, and best practices</p>
</li>
<li><p><strong>PM2 &amp; NGINX</strong> – For backend deployment</p>
</li>
<li><p><strong>Sentry</strong> – For error monitoring</p>
</li>
</ul>
<h3 id="heading-skills-amp-setup">Skills &amp; Setup</h3>
<ul>
<li><p>Basic knowledge of JavaScript, React, and Node.js</p>
</li>
<li><p>Familiarity with REST APIs and HTTP request/response flows</p>
</li>
<li><p>Git and a GitHub account for version control</p>
</li>
<li><p>A free MongoDB Atlas account</p>
</li>
<li><p>Node.js and npm installed locally (Node 18+ recommended)</p>
</li>
</ul>
<h2 id="heading-project-setup-laying-the-groundwork"><strong>Project Setup: Laying the Groundwork</strong></h2>
<p>A well-structured project is crucial for maintainability. We'll adopt a clear separation between the front end and the back end here.</p>
<h3 id="heading-project-structure"><strong>Project Structure</strong></h3>
<p>This structure clearly separates the React front end (client/) from the Node.js/Express.js back end (server/), promoting modularity and easier management.</p>
<pre><code class="lang-javascript">my-mern-app/                # Root folder
├── client/                 # React frontend
│   ├── public/
│   ├── src/
│   │   ├── components/
│   │   ├── pages/
│   │   ├── App.js
│   │   └── index.js
│   └── package.json
├── server/                 # Node.js/Express.js backend
│   ├── config/
│   ├── controllers/
│   ├── models/
│   ├── routes/
│   ├── services/
│   ├── app.js
│   └── package.json
</code></pre>
<h3 id="heading-code-quality-linting-and-formatting"><strong>Code Quality: Linting and Formatting</strong></h3>
<p>Consistency is key when you’re building a production-grad application like this one. We'll use ESLint with Airbnb style and Prettier for automated code quality and formatting.</p>
<p>To install these tools, run this in your terminal:</p>
<pre><code class="lang-bash">npm install --save-dev eslint prettier eslint-config-airbnb-base eslint-plugin-prettier
</code></pre>
<p>And here are some setups with their recommended configurations:</p>
<p>This configuration sets up ESLint for a Node.js project using the Airbnb and Prettier style guides, with custom rules to relax strict linting constraints like allowing <code>console.log</code> and disabling mandatory function names.</p>
<h4 id="heading-eslintrcjs-server-side-example"><strong>.eslintrc.js (server-side example)</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-built_in">module</span>.exports = {

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

    <span class="hljs-attr">node</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-attr">commonjs</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-attr">es2021</span>: <span class="hljs-literal">true</span>,

  },

  <span class="hljs-attr">extends</span>: [<span class="hljs-string">"airbnb-base"</span>, <span class="hljs-string">"prettier"</span>],

  <span class="hljs-attr">plugins</span>: [<span class="hljs-string">"prettier"</span>],

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

    <span class="hljs-attr">ecmaVersion</span>: <span class="hljs-number">12</span>,

  },

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

    <span class="hljs-string">"prettier/prettier"</span>: <span class="hljs-string">"error"</span>,

    <span class="hljs-string">"no-console"</span>: <span class="hljs-string">"off"</span>,

    <span class="hljs-string">"func-names"</span>: <span class="hljs-string">"off"</span>,

    <span class="hljs-string">"no-process-exit"</span>: <span class="hljs-string">"off"</span>,

    <span class="hljs-string">"class-methods-use-this"</span>: <span class="hljs-string">"off"</span>,

    <span class="hljs-string">"import/no-extraneous-dependencies"</span>: <span class="hljs-string">"off"</span>,

  },

};
</code></pre>
<h4 id="heading-prettierrc"><strong>.prettierrc</strong></h4>
<p>This config enforces consistent formatting: add semicolons, use trailing commas where valid, and prefer single quotes for strings.</p>
<pre><code class="lang-json">{

  <span class="hljs-attr">"semi"</span>: <span class="hljs-literal">true</span>,

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

  <span class="hljs-attr">"singleQuote"</span>: <span class="hljs-literal">true</span>

}
</code></pre>
<h3 id="heading-version-control-git-essentials"><strong>Version Control: Git Essentials</strong></h3>
<p>Git is indispensable. You can use feature branches and pull requests for collaborative development, making it easier to work on large projects with coworkers. Consider using Husky for pre-commit hooks to enforce linting and testing.</p>
<h4 id="heading-install-husky">Install Husky:</h4>
<p>Install Husky to easily manage Git hooks, allowing you to automate tasks like linting and testing before commits.</p>
<pre><code class="lang-bash">npm install husky --save-dev
</code></pre>
<h4 id="heading-packagejson-add-script">package.json (add script)</h4>
<p>This <code>package.json</code> file sets up a Node.js project named <code>my-mern-app</code>, and configures a <code>prepare</code> script to install Git hooks using Husky (v7). It's ready for adding pre-commit automation, such as linting or testing.</p>
<pre><code class="lang-json">{

  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"my-mern-app"</span>,

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

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

  <span class="hljs-attr">"main"</span>: <span class="hljs-string">"index.js"</span>,

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

    <span class="hljs-attr">"prepare"</span>: <span class="hljs-string">"husky install"</span>

  },

  <span class="hljs-attr">"keywords"</span>: [],

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

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

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

    <span class="hljs-attr">"husky"</span>: <span class="hljs-string">"^7.0.0"</span>

  }

}
</code></pre>
<h4 id="heading-create-a-pre-commit-hook">Create a pre-commit hook</h4>
<p>The below command sets up a pre-commit hook that automatically runs your tests and linter before each commit, ensuring code quality and preventing errors from entering your codebase.</p>
<pre><code class="lang-json">npx husky add .husky/pre-commit <span class="hljs-string">"npm test &amp;&amp; npm run lint"</span>
</code></pre>
<h2 id="heading-testing-ensuring-robustness"><strong>Testing: Ensuring Robustness</strong></h2>
<p>Automated testing is vital. We'll cover unit, integration, and end-to-end testing in this guide.</p>
<h3 id="heading-backend-testing-nodejsexpressjs">Backend Testing (Node.js/Express.js)</h3>
<p>You’ll use Jest for unit testing and Supertest for API integration tests.</p>
<h5 id="heading-install-them-like-this">Install them like this:</h5>
<pre><code class="lang-bash">npm install --save-dev jest supertest
</code></pre>
<p>You’ll use Jest to write unit tests for your JavaScript code and Supertest to test HTTP requests against your Express.js API.</p>
<h4 id="heading-example-test-servertestsauthtestjs">Example Test (server/tests/auth.test.js):</h4>
<p>This test suite uses Supertest to simulate API calls for user registration and login, asserting that the responses have the expected status codes and properties.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> request = <span class="hljs-built_in">require</span>(<span class="hljs-string">'supertest'</span>);

<span class="hljs-keyword">const</span> app = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../app'</span>); <span class="hljs-comment">// Your Express app instance</span>

describe(<span class="hljs-string">'Auth API'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should register a new user'</span>, <span class="hljs-keyword">async</span> () =&gt; {

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app)

      .post(<span class="hljs-string">'/api/auth/register'</span>)

      .send({

        <span class="hljs-attr">username</span>: <span class="hljs-string">'testuser'</span>,

        <span class="hljs-attr">email</span>: <span class="hljs-string">'test@example.com'</span>,

        <span class="hljs-attr">password</span>: <span class="hljs-string">'password123'</span>,

      });

    expect(res.statusCode).toEqual(<span class="hljs-number">201</span>);

    expect(res.body).toHaveProperty(<span class="hljs-string">'_id'</span>);

  });


  it(<span class="hljs-string">'should login an existing user'</span>, <span class="hljs-keyword">async</span> () =&gt; {

    <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> request(app)

      .post(<span class="hljs-string">'/api/auth/login'</span>)

      .send({

        <span class="hljs-attr">email</span>: <span class="hljs-string">'test@example.com'</span>,

        <span class="hljs-attr">password</span>: <span class="hljs-string">'password123'</span>,

      });

    expect(res.statusCode).toEqual(<span class="hljs-number">200</span>);

    expect(res.headers[<span class="hljs-string">'set-cookie'</span>]).toBeDefined();

  });

});
</code></pre>
<h3 id="heading-frontend-testing-react-testing-library-cypress">Frontend Testing (React Testing Library + Cypress)</h3>
<p>You’ll use Jest and the React Testing Library for unit/integration tests, and Cypress for E2E tests.</p>
<h5 id="heading-you-can-install-these-like-this">You can install these like this:</h5>
<pre><code class="lang-bash">npm install --save-dev @testing-library/react @testing-library/jest-dom jest cypress
</code></pre>
<p>React Testing Library will help you test your React components, and Cypress will provide comprehensive end-to-end testing of your frontend application.</p>
<h4 id="heading-example-component-test-clientsrccomponentsbuttontestjs">Example Component Test (client/src/components/Button.test.js):</h4>
<p>This unit test uses the React Testing Library to render a Button component and verifies that the specified text content is present in the rendered output.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { render, screen } <span class="hljs-keyword">from</span> <span class="hljs-string">'@testing-library/react'</span>;

<span class="hljs-keyword">import</span> Button <span class="hljs-keyword">from</span> <span class="hljs-string">'./Button'</span>;


test(<span class="hljs-string">'renders button with text'</span>, <span class="hljs-function">() =&gt;</span> {

  render(<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Button</span>&gt;</span>Click Me<span class="hljs-tag">&lt;/<span class="hljs-name">Button</span>&gt;</span></span>);

  <span class="hljs-keyword">const</span> buttonElement = screen.getByText(<span class="hljs-regexp">/Click Me/i</span>);

  expect(buttonElement).toBeInTheDocument();

});
</code></pre>
<p>The following Cypress test simulates a complete user authentication flow, from registration to login and logout, asserting expected URL changes and page content.</p>
<pre><code class="lang-javascript">Example E2E Test (cypress/e2e/auth.cy.js)

describe(<span class="hljs-string">'Authentication Flow'</span>, <span class="hljs-function">() =&gt;</span> {

  it(<span class="hljs-string">'should allow a user to register and login'</span>, <span class="hljs-function">() =&gt;</span> {

    cy.visit(<span class="hljs-string">'/register'</span>);

    cy.get(<span class="hljs-string">'input[name="username"]'</span>).type(<span class="hljs-string">'e2euser'</span>);

    cy.get(<span class="hljs-string">'input[name="email"]'</span>).type(<span class="hljs-string">'e2e@example.com'</span>);

    cy.get(<span class="hljs-string">'input[name="password"]'</span>).type(<span class="hljs-string">'password123'</span>);

    cy.get(<span class="hljs-string">'button[type="submit"]'</span>).click();

    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/dashboard'</span>);

    cy.contains(<span class="hljs-string">'Welcome, e2euser'</span>);

    cy.get(<span class="hljs-string">'button'</span>).contains(<span class="hljs-string">'Logout'</span>).click();

    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/login'</span>);

    cy.get(<span class="hljs-string">'input[name="email"]'</span>).type(<span class="hljs-string">'e2e@example.com'</span>);

    cy.get(<span class="hljs-string">'input[name="password"]'</span>).type(<span class="hljs-string">'password123'</span>);

    cy.get(<span class="hljs-string">'button[type="submit"]'</span>).click();

    cy.url().should(<span class="hljs-string">'include'</span>, <span class="hljs-string">'/dashboard'</span>);

  });

});
</code></pre>
<h2 id="heading-how-to-build-the-task-manager"><strong>How to Build the Task Manager</strong></h2>
<p>We'll build a simple Task Manager with user authentication and CRUD operations for tasks so you can see how the whole thing comes together.</p>
<h3 id="heading-backend-implementation-nodejsexpressjs">Backend Implementation (Node.js/Express.js)</h3>
<h4 id="heading-dependencies">Dependencies</h4>
<p>Start by installing our core backend libraries: Express for routing, Mongoose for MongoDB interactions, dotenv for environment variables, bcrypt/jsonwebtoken/cookie-parser for secure authentication, and helmet for setting secure HTTP headers:</p>
<pre><code class="lang-bash">npm install express mongoose dotenv bcryptjs jsonwebtoken cookie-parser
</code></pre>
<h4 id="heading-serverappjs-entry-point">server/app.js (Entry Point)</h4>
<p>Next, we’ll set up the first or the main entry point for the backend. This is the main Express.js application file, which configures middleware, establishes a MongoDB connection, and sets up API routes for authentication and task management.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);

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

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

<span class="hljs-keyword">const</span> cookieParser = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cookie-parser'</span>);

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

<span class="hljs-keyword">const</span> authRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/authRoutes'</span>);

<span class="hljs-keyword">const</span> taskRoutes = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./routes/taskRoutes'</span>);

<span class="hljs-keyword">const</span> { notFound, errorHandler } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./middleware/errorMiddleware'</span>);

dotenv.config();


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

app.use(helmet());

app.use(express.json());

app.use(cookieParser());


mongoose.connect(process.env.MONGO_URI)

  .then(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'MongoDB connected!'</span>))

  .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'MongoDB connection error:'</span>, err));


app.use(<span class="hljs-string">'/api/auth'</span>, authRoutes);

app.use(<span class="hljs-string">'/api/tasks'</span>, taskRoutes);


app.get(<span class="hljs-string">'/'</span>, <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {

  res.send(<span class="hljs-string">'MERN Task Manager API is running!'</span>);

});


app.use(notFound);

app.use(errorHandler);


<span class="hljs-keyword">const</span> PORT = process.env.PORT || <span class="hljs-number">5000</span>;

app.listen(PORT, <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Server running on port <span class="hljs-subst">${PORT}</span>`</span>);

});
</code></pre>
<h4 id="heading-serverenv">server/.env</h4>
<p>To avoid hardcoding secrets, we’ll add a <code>.env</code> file where we can securely store environment variables, such as our database URI and JWT secret. This file stores sensitive environment variables such as your MongoDB connection string, server port, and JWT secret, keeping them secure and separate from your codebase.</p>
<pre><code class="lang-bash">MONGO_URI=your_mongodb_connection_string_here

PORT=5000

JWT_SECRET=supersecretjwtkey
</code></pre>
<h4 id="heading-servermodelsuserjs">server/models/User.js</h4>
<p>Now, let’s define our User model using MongoDB. This schema includes fields for username, email, and password, with pre-save hooks for password hashing and a method for password comparison.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);

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


<span class="hljs-keyword">const</span> UserSchema = <span class="hljs-keyword">new</span> mongoose.Schema({

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

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

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

  },

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

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

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

  },

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

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

  },

});

UserSchema.pre(<span class="hljs-string">'save'</span>, <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">next</span>) </span>{

  <span class="hljs-keyword">if</span> (!<span class="hljs-built_in">this</span>.isModified(<span class="hljs-string">'password'</span>)) {

    next();

  }

  <span class="hljs-keyword">const</span> salt = <span class="hljs-keyword">await</span> bcrypt.genSalt(<span class="hljs-number">10</span>);

  <span class="hljs-built_in">this</span>.password = <span class="hljs-keyword">await</span> bcrypt.hash(<span class="hljs-built_in">this</span>.password, salt);

});


UserSchema.methods.matchPassword = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">enteredPassword</span>) </span>{

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> bcrypt.compare(enteredPassword, <span class="hljs-built_in">this</span>.password);

};


<span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">'User'</span>, UserSchema);
</code></pre>
<h4 id="heading-servermodelstaskjs">server/models/Task.js</h4>
<p>Next, we’ll create the Task model. This schema defines the Task model, which links each task to a user and includes fields for title, description, completion status, and creation timestamp.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);


<span class="hljs-keyword">const</span> TaskSchema = <span class="hljs-keyword">new</span> mongoose.Schema({

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

    <span class="hljs-attr">type</span>: mongoose.Schema.Types.ObjectId,

    <span class="hljs-attr">ref</span>: <span class="hljs-string">'User'</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

  },

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

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">required</span>: <span class="hljs-literal">true</span>,

    <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>,

  },

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

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">String</span>,

    <span class="hljs-attr">trim</span>: <span class="hljs-literal">true</span>,

  },

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

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">Boolean</span>,

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

  },

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

    <span class="hljs-attr">type</span>: <span class="hljs-built_in">Date</span>,

    <span class="hljs-attr">default</span>: <span class="hljs-built_in">Date</span>.now,

  },

});


<span class="hljs-built_in">module</span>.exports = mongoose.model(<span class="hljs-string">'Task'</span>, TaskSchema);
</code></pre>
<h4 id="heading-servercontrollersauthcontrollerjs">server/controllers/authController.js</h4>
<p>Let’s build out the authentication controller. This controller handles user authentication flows, including registration, login, logout, and fetching user profiles, using JWTs and secure HTTP-only cookies.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/User'</span>);

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

<span class="hljs-keyword">const</span> generateToken = <span class="hljs-function">(<span class="hljs-params">id</span>) =&gt;</span> {

  <span class="hljs-keyword">return</span> jwt.sign({ id }, process.env.JWT_SECRET, {

    <span class="hljs-attr">expiresIn</span>: <span class="hljs-string">'1h'</span>,

  });

};

<span class="hljs-built_in">exports</span>.registerUser = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { username, email, password } = req.body;

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

    <span class="hljs-keyword">const</span> userExists = <span class="hljs-keyword">await</span> User.findOne({ email });

    <span class="hljs-keyword">if</span> (userExists) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'User already exists'</span> });

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.create({ username, email, password });

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

      <span class="hljs-keyword">const</span> token = generateToken(user._id);

      res.cookie(<span class="hljs-string">'token'</span>, token, { <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">'production'</span>, <span class="hljs-attr">maxAge</span>: <span class="hljs-number">3600000</span> });

      res.status(<span class="hljs-number">201</span>).json({ <span class="hljs-attr">id</span>: user.id, <span class="hljs-attr">username</span>: user.username, <span class="hljs-attr">email</span>: user.email });

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

      res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid user data'</span> });

    }

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

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.loginUser = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { email, password } = req.body;

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

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findOne({ email });

    <span class="hljs-keyword">if</span> (user &amp;&amp; (<span class="hljs-keyword">await</span> user.matchPassword(password))) {

      <span class="hljs-keyword">const</span> token = generateToken(user._id);

      res.cookie(<span class="hljs-string">'token'</span>, token, { <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">secure</span>: process.env.NODE_ENV === <span class="hljs-string">'production'</span>, <span class="hljs-attr">maxAge</span>: <span class="hljs-number">3600000</span> });

      res.json({ <span class="hljs-attr">id</span>: user.id, <span class="hljs-attr">username</span>: user.username, <span class="hljs-attr">email</span>: user.email });

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

      res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Invalid email or password'</span> });

    }

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

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.logoutUser = <span class="hljs-function">(<span class="hljs-params">req, res</span>) =&gt;</span> {

  res.cookie(<span class="hljs-string">'token'</span>, <span class="hljs-string">''</span>, { <span class="hljs-attr">httpOnly</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">expires</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(<span class="hljs-number">0</span>) });

  res.status(<span class="hljs-number">200</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Logged out successfully'</span> });

};


<span class="hljs-built_in">exports</span>.getUserProfile = <span class="hljs-keyword">async</span> (req, res) =&gt; {

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

    <span class="hljs-keyword">const</span> user = <span class="hljs-keyword">await</span> User.findById(req.user._id).select(<span class="hljs-string">'-password'</span>);

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

      res.json(user);

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

      res.status(<span class="hljs-number">404</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'User not found'</span> });

    }

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

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};
</code></pre>
<h4 id="heading-servercontrollerstaskcontrollerjs">server/controllers/taskController.js</h4>
<p>Now it’s time to implement the task controller. This controller provides the logic for fetching, creating, updating, and deleting tasks, ensuring that users can only interact with their tasks.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> Task = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/Task'</span>);


<span class="hljs-built_in">exports</span>.getTasks = <span class="hljs-keyword">async</span> (req, res) =&gt; {

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

    <span class="hljs-keyword">const</span> tasks = <span class="hljs-keyword">await</span> Task.find({ <span class="hljs-attr">user</span>: req.user._id });

    res.status(<span class="hljs-number">200</span>).json(tasks);

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

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.createTask = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { title, description } = req.body;

  <span class="hljs-keyword">if</span> (!title) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Please add a title'</span> });

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

    <span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> Task.create({ title, description, <span class="hljs-attr">user</span>: req.user._id });

    res.status(<span class="hljs-number">201</span>).json(task);

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

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.updateTask = <span class="hljs-keyword">async</span> (req, res) =&gt; {

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

    <span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> Task.findById(req.params.id);

    <span class="hljs-keyword">if</span> (!task) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Task not found'</span> });

    <span class="hljs-keyword">if</span> (task.user.toString() !== req.user._id.toString()) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Not authorized'</span> });


    <span class="hljs-keyword">const</span> updatedTask = <span class="hljs-keyword">await</span> Task.findByIdAndUpdate(req.params.id, req.body, { <span class="hljs-attr">new</span>: <span class="hljs-literal">true</span>, <span class="hljs-attr">runValidators</span>: <span class="hljs-literal">true</span> });

    res.status(<span class="hljs-number">200</span>).json(updatedTask);

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

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};


<span class="hljs-built_in">exports</span>.deleteTask = <span class="hljs-keyword">async</span> (req, res) =&gt; {

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

    <span class="hljs-keyword">const</span> task = <span class="hljs-keyword">await</span> Task.findById(req.params.id);

    <span class="hljs-keyword">if</span> (!task) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">404</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Task not found'</span> });

    <span class="hljs-keyword">if</span> (task.user.toString() !== req.user._id.toString()) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Not authorized'</span> });


    <span class="hljs-keyword">await</span> Task.deleteOne({ <span class="hljs-attr">_id</span>: req.params.id });

    res.status(<span class="hljs-number">200</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Task removed'</span> });

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

    res.status(<span class="hljs-number">500</span>).json({ <span class="hljs-attr">message</span>: error.message });

  }

};
</code></pre>
<h4 id="heading-servermiddlewareauthmiddlewarejs">server/middleware/authMiddleware.js</h4>
<p>To protect private routes<strong>,</strong> we will create a middleware that verifies the JWT from the request's cookies, ensuring that only authenticated users can access specific endpoints.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> jwt = <span class="hljs-built_in">require</span>(<span class="hljs-string">'jsonwebtoken'</span>);

<span class="hljs-keyword">const</span> User = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../models/User'</span>);

<span class="hljs-built_in">exports</span>.protect = <span class="hljs-keyword">async</span> (req, res, next) =&gt; {

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

  <span class="hljs-keyword">if</span> (req.cookies.token) {

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

      token = req.cookies.token;

      <span class="hljs-keyword">const</span> decoded = jwt.verify(token, process.env.JWT_SECRET);

      req.user = <span class="hljs-keyword">await</span> User.findById(decoded.id).select(<span class="hljs-string">'-password'</span>);

      next();

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

      res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Not authorized, token failed'</span> });

    }

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

    res.status(<span class="hljs-number">401</span>).json({ <span class="hljs-attr">message</span>: <span class="hljs-string">'Not authorized, no token'</span> });

  }

};
</code></pre>
<h4 id="heading-servermiddlewareerrormiddlewarejs">server/middleware/errorMiddleware.js</h4>
<p>To handle errors cleanly across our backend, we’ll add global error-handling middleware that can handle 404 Not Found errors and provide a centralized error-handling mechanism for consistent API error responses.</p>
<pre><code class="lang-javascript"><span class="hljs-built_in">exports</span>.notFound = <span class="hljs-function">(<span class="hljs-params">req, res, next</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> error = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">`Not Found - <span class="hljs-subst">${req.originalUrl}</span>`</span>);

  res.status(<span class="hljs-number">404</span>);

  next(error);

};


<span class="hljs-built_in">exports</span>.errorHandler = <span class="hljs-function">(<span class="hljs-params">err, req, res, next</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> statusCode = res.statusCode === <span class="hljs-number">200</span> ? <span class="hljs-number">500</span> : res.statusCode;

  res.status(statusCode);

  res.json({

    <span class="hljs-attr">message</span>: err.message,

    <span class="hljs-attr">stack</span>: process.env.NODE_ENV === <span class="hljs-string">'production'</span> ? <span class="hljs-literal">null</span> : err.stack,

  });

};
</code></pre>
<h4 id="heading-serverroutesauthroutesjs">server/routes/authRoutes.js</h4>
<p>Next, let’s define our authentication routes. These endpoints enable user authentication and map HTTP methods to their corresponding controller functions.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);

<span class="hljs-keyword">const</span> { registerUser, loginUser, logoutUser, getUserProfile } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../controllers/authController'</span>);

<span class="hljs-keyword">const</span> { protect } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../middleware/authMiddleware'</span>);


<span class="hljs-keyword">const</span> router = express.Router();


router.post(<span class="hljs-string">'/register'</span>, registerUser);

router.post(<span class="hljs-string">'/login'</span>, loginUser);

router.get(<span class="hljs-string">'/logout'</span>, logoutUser);

router.get(<span class="hljs-string">'/profile'</span>, protect, getUserProfile);


<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<h4 id="heading-serverroutestaskroutesjs">server/routes/taskRoutes.js</h4>
<p>Now we’ll add the routes for task operations. This file defines the API routes for task management, applying the protect middleware to secure all task-related operations.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> express = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express'</span>);

<span class="hljs-keyword">const</span> { getTasks, createTask, updateTask, deleteTask } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../controllers/taskController'</span>);

<span class="hljs-keyword">const</span> { protect } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../middleware/authMiddleware'</span>);

<span class="hljs-keyword">const</span> router = express.Router();

router.route(<span class="hljs-string">'/'</span>).get(protect, getTasks).post(protect, createTask);

router.route(<span class="hljs-string">'/:id'</span>).put(protect, updateTask).delete(protect, deleteTask);

<span class="hljs-built_in">module</span>.exports = router;
</code></pre>
<h3 id="heading-frontend-implementation-react">Frontend Implementation (React)</h3>
<h4 id="heading-dependencies-1">Dependencies</h4>
<p>Now, you’ll need to initialize a new React project and install your essential libraries: Axios for HTTP requests, React Router for navigation, and React Toastify for displaying notifications.</p>
<pre><code class="lang-bash">npm install axios react-router-dom react-toastify
</code></pre>
<h4 id="heading-clientsrcindexjs">client/src/index.js</h4>
<p>Let’s start the frontend by setting up the entry point. Here we are rendering the main App component and wrapping it with AuthProvider to provide authentication context globally.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> ReactDOM <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom/client'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'./index.css'</span>;

<span class="hljs-keyword">import</span> App <span class="hljs-keyword">from</span> <span class="hljs-string">'./App'</span>;

<span class="hljs-keyword">import</span> { AuthProvider } <span class="hljs-keyword">from</span> <span class="hljs-string">'./context/AuthContext'</span>;


<span class="hljs-keyword">const</span> root = ReactDOM.createRoot(<span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'root'</span>));

root.render(

  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">React.StrictMode</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">AuthProvider</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">App</span> /&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">AuthProvider</span>&gt;</span>

  <span class="hljs-tag">&lt;/<span class="hljs-name">React.StrictMode</span>&gt;</span></span>

);
</code></pre>
<h4 id="heading-clientsrcappjs">client/src/App.js</h4>
<p>Next, we’ll define our main App component. This sets up the client-side routing for the application, and defines public and private routes, and includes a navigation bar and toast notification system.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { BrowserRouter <span class="hljs-keyword">as</span> Router, Routes, Route } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> { ToastContainer } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">import</span> <span class="hljs-string">'react-toastify/dist/ReactToastify.css'</span>;


<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/Navbar'</span>;

<span class="hljs-keyword">import</span> Register <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Register'</span>;

<span class="hljs-keyword">import</span> Login <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Login'</span>;

<span class="hljs-keyword">import</span> Dashboard <span class="hljs-keyword">from</span> <span class="hljs-string">'./pages/Dashboard'</span>;

<span class="hljs-keyword">import</span> PrivateRoute <span class="hljs-keyword">from</span> <span class="hljs-string">'./components/PrivateRoute'</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">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Router</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">Navbar</span> /&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">ToastContainer</span> /&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"container"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">Routes</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/register"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Register</span> /&gt;</span>} /&gt;

          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/login"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Login</span> /&gt;</span>} /&gt;

          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/dashboard"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">PrivateRoute</span> /&gt;</span>}&gt;

            <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">index</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">Dashboard</span> /&gt;</span>} /&gt;

          <span class="hljs-tag">&lt;/<span class="hljs-name">Route</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">Route</span> <span class="hljs-attr">path</span>=<span class="hljs-string">"/"</span> <span class="hljs-attr">element</span>=<span class="hljs-string">{</span>&lt;<span class="hljs-attr">h1</span>&gt;</span>Welcome to Task Manager!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>} /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">Routes</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">Router</span>&gt;</span></span>

  );

}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> App;
</code></pre>
<h4 id="heading-clientsrccontextauthcontextjs">client/src/context/AuthContext.js</h4>
<p>We’ll create an authentication context that manages the global authentication state. It provides functions for user login, registration, and logout, and automatically loads user data on component mount.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { createContext, useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">const</span> AuthContext = createContext();

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> AuthProvider = <span class="hljs-function">(<span class="hljs-params">{ children }</span>) =&gt;</span> {

  <span class="hljs-keyword">const</span> [user, setUser] = useState(<span class="hljs-literal">null</span>);

  <span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">true</span>);


  useEffect(<span class="hljs-function">() =&gt;</span> {

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

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

        <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'/api/auth/profile'</span>);

        setUser(res.data);

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

        setUser(<span class="hljs-literal">null</span>);

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

        setLoading(<span class="hljs-literal">false</span>);

      }

    };

    loadUser();

  }, []);


  <span class="hljs-keyword">const</span> login = <span class="hljs-keyword">async</span> (email, password) =&gt; {

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

      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">'/api/auth/login'</span>, { email, password });

      setUser(res.data);

      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;

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

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

      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

    }

  };


  <span class="hljs-keyword">const</span> register = <span class="hljs-keyword">async</span> (username, email, password) =&gt; {

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

      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">'/api/auth/register'</span>, { username, email, password });

      setUser(res.data);

      <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;

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

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

      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;

    }

  };


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

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

      <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'/api/auth/logout'</span>);

      setUser(<span class="hljs-literal">null</span>);

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

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

    }

  };


  <span class="hljs-keyword">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">AuthContext.Provider</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">user</span>, <span class="hljs-attr">loading</span>, <span class="hljs-attr">login</span>, <span class="hljs-attr">register</span>, <span class="hljs-attr">logout</span> }}&gt;</span>

      {children}

    <span class="hljs-tag">&lt;/<span class="hljs-name">AuthContext.Provider</span>&gt;</span></span>

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> AuthContext;
</code></pre>
<h4 id="heading-clientsrccomponentsnavbarjs">client/src/components/Navbar.js</h4>
<p>Here’s a dynamic navigation bar component that dynamically displays links based on the user's authentication status, showing either login/register options or a welcome message and logout button.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { Link } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> { user, logout } = useContext(AuthContext);


  <span class="hljs-keyword">return</span> (

    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Task Manager<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>

        {user ? (

          <span class="hljs-tag">&lt;&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>Welcome, {user.username}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{logout}</span>&gt;</span>Logout<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/dashboard"</span>&gt;</span>Dashboard<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>

          <span class="hljs-tag">&lt;/&gt;</span>

        ) : (

          <span class="hljs-tag">&lt;&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/login"</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/register"</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>

          <span class="hljs-tag">&lt;/&gt;</span></span>

        )}

      &lt;/div&gt;

    &lt;/nav&gt;

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar;
</code></pre>
<h4 id="heading-clientsrccomponentsprivateroutejs">client/src/components/PrivateRoute.js</h4>
<p>To protect certain pages, we can create a Private Route component. This will be a guard for private routes, ensuring that only authenticated users can access them and redirecting unauthenticated users to the login page.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { Navigate, Outlet } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> PrivateRoute = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> { user, loading } = useContext(AuthContext);


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

    <span class="hljs-keyword">return</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>Loading...<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>; <span class="hljs-comment">// Or a spinner</span>

  }


  <span class="hljs-keyword">return</span> user ? <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Outlet</span> /&gt;</span></span> : <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Navigate</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/login"</span> <span class="hljs-attr">replace</span> /&gt;</span></span>;

};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> PrivateRoute;
</code></pre>
<h4 id="heading-clientsrcpagesregisterjs">client/src/pages/Register.js</h4>
<p>Now, let’s create the Register component, which provides a user registration form, handles input state and form submission, and displays success or error messages using toast notifications.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> Register = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> [username, setUsername] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> { register } = useContext(AuthContext);

  <span class="hljs-keyword">const</span> navigate = useNavigate();


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

    e.preventDefault();

    <span class="hljs-keyword">const</span> success = <span class="hljs-keyword">await</span> register(username, email, password);

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

      toast.success(<span class="hljs-string">'Registration successful!'</span>);

      navigate(<span class="hljs-string">'/dashboard'</span>);

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

      toast.error(<span class="hljs-string">'Registration failed. Please try again.'</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">h2</span>&gt;</span>Register<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</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">label</span>&gt;</span>Username:<span class="hljs-tag">&lt;/<span class="hljs-name">label</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">value</span>=<span class="hljs-string">{username}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setUsername(e.target.value)} required /&gt;

        <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">label</span>&gt;</span>Email:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)} required /&gt;

        <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">label</span>&gt;</span>Password:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)} required /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">div</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>Register<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>&gt;</span></span>

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Register;
</code></pre>
<h4 id="heading-clientsrcpagesloginjs">client/src/pages/Login.js</h4>
<p>Now, for the login form, it works similarly to the register page but logs users into the system instead. This page manages input fields, handles form submissions, and provides feedback via toast notifications.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> { useNavigate } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-router-dom'</span>;

<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> Login = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> [email, setEmail] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> [password, setPassword] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> { login } = useContext(AuthContext);

  <span class="hljs-keyword">const</span> navigate = useNavigate();


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

    e.preventDefault();

    <span class="hljs-keyword">const</span> success = <span class="hljs-keyword">await</span> login(email, password);

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

      toast.success(<span class="hljs-string">'Login successful!'</span>);

      navigate(<span class="hljs-string">'/dashboard'</span>);

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

      toast.error(<span class="hljs-string">'Login failed. Invalid credentials.'</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">h2</span>&gt;</span>Login<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</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">label</span>&gt;</span>Email:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{email}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setEmail(e.target.value)} required /&gt;

        <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">label</span>&gt;</span>Password:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"password"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{password}</span> <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setPassword(e.target.value)} required /&gt;

        <span class="hljs-tag">&lt;/<span class="hljs-name">div</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>Login<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>&gt;</span></span>

  );

};


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Login;
</code></pre>
<h4 id="heading-clientsrcpagesdashboardjs">client/src/pages/Dashboard.js</h4>
<p>Finally, we’ll build the Dashboard page. This dashboard component displays a user's tasks, allowing them to create new tasks, mark tasks as complete or incomplete, and delete tasks, with real-time updates.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> React, { useState, useEffect, useContext } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;

<span class="hljs-keyword">import</span> axios <span class="hljs-keyword">from</span> <span class="hljs-string">'axios'</span>;

<span class="hljs-keyword">import</span> { toast } <span class="hljs-keyword">from</span> <span class="hljs-string">'react-toastify'</span>;

<span class="hljs-keyword">import</span> AuthContext <span class="hljs-keyword">from</span> <span class="hljs-string">'../context/AuthContext'</span>;


<span class="hljs-keyword">const</span> Dashboard = <span class="hljs-function">() =&gt;</span> {

  <span class="hljs-keyword">const</span> { user } = useContext(AuthContext);

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

  <span class="hljs-keyword">const</span> [newTaskTitle, setNewTaskTitle] = useState(<span class="hljs-string">''</span>);

  <span class="hljs-keyword">const</span> [newTaskDescription, setNewTaskDescription] = useState(<span class="hljs-string">''</span>);


  useEffect(<span class="hljs-function">() =&gt;</span> {

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

      fetchTasks();

    }

  }, [user]);


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

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

      <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> axios.get(<span class="hljs-string">'/api/tasks'</span>);

      setTasks(res.data);

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

      toast.error(<span class="hljs-string">'Failed to fetch tasks.'</span>);

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

    }

  };


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

    e.preventDefault();

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

      <span class="hljs-keyword">await</span> axios.post(<span class="hljs-string">'/api/tasks'</span>, { <span class="hljs-attr">title</span>: newTaskTitle, <span class="hljs-attr">description</span>: newTaskDescription });

      setNewTaskTitle(<span class="hljs-string">''</span>);

      setNewTaskDescription(<span class="hljs-string">''</span>);

      toast.success(<span class="hljs-string">'Task created successfully!'</span>);

      fetchTasks();

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

      toast.error(<span class="hljs-string">'Failed to create task.'</span>);

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

    }

  };


  <span class="hljs-keyword">const</span> handleUpdateTask = <span class="hljs-keyword">async</span> (id, completed) =&gt; {

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

      <span class="hljs-keyword">await</span> axios.put(<span class="hljs-string">`/api/tasks/<span class="hljs-subst">${id}</span>`</span>, { completed });

      toast.success(<span class="hljs-string">'Task updated successfully!'</span>);

      fetchTasks();

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

      toast.error(<span class="hljs-string">'Failed to update task.'</span>);

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

    }

  };


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

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

      <span class="hljs-keyword">await</span> axios.delete(<span class="hljs-string">`/api/tasks/<span class="hljs-subst">${id}</span>`</span>);

      toast.success(<span class="hljs-string">'Task deleted successfully!'</span>);

      fetchTasks();

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

      toast.error(<span class="hljs-string">'Failed to delete task.'</span>);

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

    }

  };


  <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">h2</span>&gt;</span>Welcome, {user ? user.username : 'Guest'}!<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Your Tasks<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleCreateTask}</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">placeholder</span>=<span class="hljs-string">"New Task Title"</span>

          <span class="hljs-attr">value</span>=<span class="hljs-string">{newTaskTitle}</span>

          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setNewTaskTitle(e.target.value)}

          required

        /&gt;

        <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">placeholder</span>=<span class="hljs-string">"Description (optional)"</span>

          <span class="hljs-attr">value</span>=<span class="hljs-string">{newTaskDescription}</span>

          <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setNewTaskDescription(e.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>Add Task<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">ul</span>&gt;</span>

        {tasks.map((task) =&gt; (

          <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{task._id}</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">textDecoration:</span> <span class="hljs-attr">task.completed</span> ? '<span class="hljs-attr">line-through</span>' <span class="hljs-attr">:</span> '<span class="hljs-attr">none</span>' }}&gt;</span>

              {task.title}: {task.description}

            <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> handleUpdateTask(task._id, !task.completed)}&gt;

              {task.completed ? 'Mark Incomplete' : 'Mark Complete'}

            <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">{()</span> =&gt;</span> handleDeleteTask(task._id)}&gt;Delete<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

        ))}

      <span class="hljs-tag">&lt;/<span class="hljs-name">ul</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> Dashboard;
</code></pre>
<h2 id="heading-deployment-from-localhost-to-live"><strong>Deployment: From Localhost to Live</strong></h2>
<p>Deploying a MERN stack application involves deploying the backend API and the frontend React application separately.</p>
<p>Let’s talk about why we do it separately. As you have seen from above, in a MERN stack app, the frontend and backend are separate by design. React handles the UI, while Express and Node handle server logic and API calls. Because they serve different roles, you'll need to deploy them separately.</p>
<p>The backend runs on a Node.js compatible server, which connects to a database such as MongoDB Atlas. The frontend, once it is built, becomes static files that can be hosted from anywhere, from NGINX to hosting platforms like Netlify or Vercel.</p>
<p>This separation provides you with flexibility and improved scalability. Let’s walk through how to deploy each part.</p>
<h3 id="heading-backend-deployment-nodejsexpressjs"><strong>Backend Deployment (Node.js/Express.js)</strong></h3>
<p>For backend deployment, platforms like Heroku, Render, or AWS EC2 are common choices. Here, I’ll outline a general approach for a cloud VM on AWS EC2</p>
<h4 id="heading-1-prepare-for-production">1. Prepare for Production</h4>
<p>To start, set the environment to <code>production</code> and install only the dependencies your app needs to run, optimizing your application's performance. Skipping devDependencies helps reduce its footprint.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">export</span> NODE_ENV=production

npm install --production
</code></pre>
<h4 id="heading-2-process-manager-pm2">2. Process Manager (PM2)</h4>
<p>Next, we’ll set up a process manager to keep our backend server running reliably. PM2 is a popular tool that handles automatic restarts if your Node.js application crashes, manages multiple app instances, and also helps ensure high availability in production environments.</p>
<pre><code class="lang-bash">npm install -g pm2

pm2 start server/app.js --name mern-api

pm2 save

pm2 startup
</code></pre>
<h4 id="heading-3-nginx-as-a-reverse-proxy">3. NGINX as a Reverse Proxy</h4>
<p>Now that our backend is running with PM2, we need a way to handle incoming web traffic. That’s where NGINX comes in. We'll install NGINX to serve as a high-performance reverse proxy directing incoming web traffic to your Node.js backend and serving static frontend files.</p>
<pre><code class="lang-bash">sudo apt update

sudo apt install nginx
</code></pre>
<p>Once NGINX is installed, it’s time to configure it (/etc/nginx/sites-available/default or a new config file). We’ll set it up to forward API requests to the backend and serve the React app, acting as the single entry point. You can update the default configuration file or create a new one:</p>
<pre><code class="lang-nginx"><span class="hljs-comment"># /etc/nginx/sites-available/default</span>
<span class="hljs-section">server</span> {

    <span class="hljs-attribute">listen</span> <span class="hljs-number">80</span>;

    <span class="hljs-attribute">server_name</span> your_domain_or_ip;


    <span class="hljs-attribute">location</span> /api/ {

        <span class="hljs-attribute">proxy_pass</span> http://localhost:5000;

        <span class="hljs-attribute">proxy_http_version</span> <span class="hljs-number">1</span>.<span class="hljs-number">1</span>;

        <span class="hljs-attribute">proxy_set_header</span> Upgrade <span class="hljs-variable">$http_upgrade</span>;

        <span class="hljs-attribute">proxy_set_header</span> Connection <span class="hljs-string">'upgrade'</span>;

        <span class="hljs-attribute">proxy_set_header</span> Host <span class="hljs-variable">$host</span>;

        <span class="hljs-attribute">proxy_cache_bypass</span> <span class="hljs-variable">$http_upgrade</span>;

    }


    <span class="hljs-attribute">location</span> / {

        <span class="hljs-attribute">root</span> /var/www/my-mern-app/client/build; <span class="hljs-comment"># Path to your React build folder</span>

        <span class="hljs-attribute">try_files</span> <span class="hljs-variable">$uri</span> /index.html;

    }

}
</code></pre>
<p>With the NGINX configuration created, we’ll enable it and restart the service to apply the changes, making your application go live:</p>
<pre><code class="lang-bash">sudo ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/

sudo systemctl restart nginx
</code></pre>
<h4 id="heading-4-https-with-certbot-lets-encrypt">4. HTTPS with Certbot (Let's Encrypt)</h4>
<p>To secure your app with HTTPS, we can install Certbot and use it to automatically obtain and configure a free SSL/TLS certificate from Let’s Encrypt, enabling secure HTTPS connections for your domain.</p>
<pre><code class="lang-bash">sudo snap install --classic certbot

sudo certbot --nginx -d your_domain_or_ip
</code></pre>
<h3 id="heading-frontend-deployment-react"><strong>Frontend Deployment (React)</strong></h3>
<p>With the backend deployed, let’s move to the frontend. For the React frontend, we’ll build the application and serve the static files via NGINX (as shown above) or a dedicated static site hosted on platforms like Netlify, Vercel, or AWS S3 + CloudFront.</p>
<h4 id="heading-build-the-react-app">Build the React App</h4>
<p>This command compiles and optimizes your React application into a build folder containing static assets, ready for efficient deployment to any web server or static hosting service.</p>
<pre><code class="lang-bash"><span class="hljs-built_in">cd</span> client

npm run build
</code></pre>
<h3 id="heading-database-deployment-mongodb-atlas"><strong>Database Deployment (MongoDB Atlas)</strong></h3>
<p>For production, we’ll use a managed MongoDB service like MongoDB Atlas. It handles replication, sharding, and backups, simplifying database management significantly.</p>
<h4 id="heading-create-a-cluster-on-mongodb-atlas">Create a Cluster on MongoDB Atlas</h4>
<ul>
<li><p>Sign up/Log in to MongoDB Atlas.</p>
</li>
<li><p>Create a new cluster (choose a cloud provider and region).</p>
</li>
<li><p>Set up a database user with appropriate permissions.</p>
</li>
<li><p>Configure network access (allow connections from your server's IP address).</p>
</li>
<li><p>Get your connection string and update MONGO_URI in your server/.env file.</p>
</li>
</ul>
<h4 id="heading-1-env-configuration-example">1. <code>.env</code> Configuration Example</h4>
<p>After creating the cluster and user in MongoDB Atlas, you’ll receive a connection string. You need to update your <code>.env</code> file with it</p>
<pre><code class="lang-ini"><span class="hljs-comment"># server/.env</span>
<span class="hljs-attr">MONGO_URI</span>=mongodb+srv://yourUser:yourPassword@cluster0.mongodb.net/yourDBName
<span class="hljs-attr">JWT_SECRET</span>=your_secret_jwt_key
<span class="hljs-attr">NODE_ENV</span>=production
</code></pre>
<h4 id="heading-2-connect-to-mongodb-in-appjs">2. Connect to MongoDB in <code>app.js</code></h4>
<p>Next, in the <code>server/app.js</code> file, make sure you're using the connection string from the environment variable:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> mongoose = <span class="hljs-built_in">require</span>(<span class="hljs-string">'mongoose'</span>);
<span class="hljs-keyword">const</span> dotenv = <span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>);
dotenv.config();

mongoose.connect(process.env.MONGO_URI)
  .then(<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'MongoDB connected!'</span>))
  .catch(<span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Connection error:'</span>, err));
</code></pre>
<h3 id="heading-other-deployment-options"><strong>Other Deployment Options</strong></h3>
<p>While this article drives you through manual deployment with EC2 and NGINX, other platforms can simplify the process:</p>
<ul>
<li><p><strong>Render</strong>, <strong>Railway</strong>, and <strong>Heroku</strong> offer easy full-stack deployment with GitHub integration.</p>
</li>
<li><p><strong>Vercel</strong> and <strong>Netlify</strong> are ideal for hosting the React frontend.</p>
</li>
<li><p>You may consider using <strong>Docker</strong> to maintain consistent environments across development and production.</p>
</li>
<li><p>For CI/CD, Linting, Testing, &amp; Deployment can be automated on every push using tools like <strong>GitHub Actions</strong></p>
</li>
</ul>
<p>There is no right or wrong choice here. Select the setup that best suits your project’s scale, team experience, and desired level of control.</p>
<h2 id="heading-security-best-practices-fortifying-your-application"><strong>Security Best Practices: Fortifying Your Application</strong></h2>
<p>Security is paramount. You can implement these best practices to protect your MERN application.</p>
<h3 id="heading-setup-input-validation-and-sanitization"><strong>Setup Input Validation and Sanitization</strong></h3>
<p>Always validate and sanitize input on the server side. You can use libraries like Joi or Zod to make this process easier.</p>
<h4 id="heading-example-with-joi">Example with Joi:</h4>
<p>To validate and sanitize incoming data on the server, we will utilize Joi, a powerful library for defining schemas and enforcing input rules.</p>
<pre><code class="lang-bash">npm install joi
</code></pre>
<p>Now that we’ve installed Joi, we will use it to define strict validation rules for user registration and login inputs. This ensures data quality and prevents common injection attacks.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/validators/authValidator.js</span>

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


<span class="hljs-keyword">const</span> registerSchema = Joi.object({

  <span class="hljs-attr">username</span>: Joi.string().min(<span class="hljs-number">3</span>).max(<span class="hljs-number">30</span>).required(),

  <span class="hljs-attr">email</span>: Joi.string().email().required(),

  <span class="hljs-attr">password</span>: Joi.string().min(<span class="hljs-number">6</span>).required(),

});


<span class="hljs-keyword">const</span> loginSchema = Joi.object({

  <span class="hljs-attr">email</span>: Joi.string().email().required(),

  <span class="hljs-attr">password</span>: Joi.string().required(),

});


<span class="hljs-built_in">module</span>.exports = { registerSchema, loginSchema };
</code></pre>
<p>Next, we’ll integrate these schemas directly into our authentication controller to automatically validate incoming request bodies against predefined schemas.</p>
<pre><code class="lang-javascript"><span class="hljs-comment">// server/controllers/authController.js (snippet)</span>

<span class="hljs-keyword">const</span> { registerSchema, loginSchema } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'../validators/authValidator'</span>);


<span class="hljs-built_in">exports</span>.registerUser = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { error } = registerSchema.validate(req.body);

  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: error.details[<span class="hljs-number">0</span>].message });

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

};


<span class="hljs-built_in">exports</span>.loginUser = <span class="hljs-keyword">async</span> (req, res) =&gt; {

  <span class="hljs-keyword">const</span> { error } = loginSchema.validate(req.body);

  <span class="hljs-keyword">if</span> (error) <span class="hljs-keyword">return</span> res.status(<span class="hljs-number">400</span>).json({ <span class="hljs-attr">message</span>: error.details[<span class="hljs-number">0</span>].message });

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

};
</code></pre>
<h3 id="heading-add-authentication-and-authorization"><strong>Add Authentication and Authorization</strong></h3>
<p>You can use JWTs for authentication and implement middleware for protected routes.</p>
<h4 id="heading-jwt-implementation-covered-in-authcontrollerjs-and-authmiddlewarejs-above">JWT Implementation (covered in authController.js and authMiddleware.js above)</h4>
<p>Key aspects:</p>
<ul>
<li><p>HttpOnly Cookies: Store JWTs in HttpOnly cookies to prevent client-side JavaScript access, mitigating XSS attacks.</p>
</li>
<li><p>Secure Flag: Use secure: true in production to ensure cookies are only sent over HTTPS.</p>
</li>
</ul>
<p>These practices ensure that authentication tokens are securely transmitted and stored, protecting against common web vulnerabilities like Cross-Site Scripting (XSS).</p>
<h3 id="heading-implement-rate-limiting"><strong>Implement Rate Limiting</strong></h3>
<p>To protect our API from abuse and malicious intent, we will implement basic rate limiting. This helps protect against brute-force login attempts and DDoS attacks.</p>
<h4 id="heading-installation">Installation</h4>
<p>We will install express-rate-limit package for it</p>
<pre><code class="lang-bash">npm install express-rate-limit
</code></pre>
<h4 id="heading-serverappjs-snippet">server/app.js (snippet)</h4>
<p>Once it is installed, let’s configure the rate limiter and apply it to all incoming requests. This ensures that no single IP can overwhelm your server with repeated calls. The following middleware limits each IP address to 200 requests within a 15-minute window.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> rateLimit = <span class="hljs-built_in">require</span>(<span class="hljs-string">'express-rate-limit'</span>);

<span class="hljs-keyword">const</span> limiter = rateLimit({

  <span class="hljs-attr">windowMs</span>: <span class="hljs-number">15</span> * <span class="hljs-number">60</span> * <span class="hljs-number">1000</span>, <span class="hljs-comment">// 15 minutes</span>

  <span class="hljs-attr">max</span>: <span class="hljs-number">200</span>, <span class="hljs-comment">// Limit each IP to 200 requests per windowMs</span>

  <span class="hljs-attr">message</span>: <span class="hljs-string">'Too many requests from this IP, please try again after 15 minutes'</span>,

});

app.use(limiter); <span class="hljs-comment">// Apply to all requests</span>
</code></pre>
<h3 id="heading-setup-cors-configuration-cross-origin-resource-sharing"><strong>Setup CORS Configuration (Cross-Origin Resource Sharing)</strong></h3>
<p>Next, we move our focus to enable secure communication between your frontend and backend. By default, all browsers block cross-origin requests, so we need to configure CORS (Cross-Origin Resource Sharing) to permit the React app to communicate with the Express API.</p>
<h4 id="heading-installation-1">Installation</h4>
<pre><code class="lang-bash">npm install cors
</code></pre>
<h4 id="heading-serverappjs-snippet-1">server/app.js (snippet)</h4>
<p>Once installed<strong>,</strong> we can configure CORS for our Express application, specifying allowed origins and enabling credential sharing for secure cross-origin requests. Remember to replace the origin with your actual production URL when deploying.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> cors = <span class="hljs-built_in">require</span>(<span class="hljs-string">'cors'</span>);

app.use(cors({

  <span class="hljs-attr">origin</span>: <span class="hljs-string">'http://localhost:3000'</span>, <span class="hljs-comment">// Replace with your frontend URL in production</span>

  <span class="hljs-attr">credentials</span>: <span class="hljs-literal">true</span>,

}));
</code></pre>
<h3 id="heading-use-environment-variables"><strong>Use Environment Variables</strong></h3>
<p>To keep sensitive information secure and out of your codebase, we will use environment variables. This allows us to efficiently manage secrets, such as database connection strings and JWT keys, without hardcoding them or including them in the source code.</p>
<p>Create a <code>.env</code> file in your <code>server/</code> directory:</p>
<h4 id="heading-env-example">.env (example)</h4>
<p>This .env file stores sensitive configuration details like database connection strings and API keys</p>
<pre><code class="lang-ini"><span class="hljs-attr">MONGO_URI</span>=your_mongodb_connection_string

<span class="hljs-attr">JWT_SECRET</span>=your_super_secret_jwt_key

<span class="hljs-attr">NODE_ENV</span>=production
</code></pre>
<h2 id="heading-monitoring-and-logging-with-winston-and-morgan"><strong>Monitoring and Logging with Winston and Morgan</strong></h2>
<p>Once the application is live, it's critical to monitor the behavior and catch issues promptly. Monitoring and logging help you measure performance, find bugs, and keep a log of all server activity.</p>
<p>We’ll use Morgan for logging HTTP requests and Winston for more general-purpose application logging.</p>
<h3 id="heading-installation-2">Installation</h3>
<p>We will install Morgan for logging HTTP requests and Winston for comprehensive and customizable application logging.</p>
<pre><code class="lang-bash">npm install morgan winston
</code></pre>
<h3 id="heading-serverconfigloggerjs">server/config/logger.js</h3>
<p>Next, let’s configure Winston to handle our application logs. This will output logs to the console by default, with options to enable file-based logging for errors and general information.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> winston = <span class="hljs-built_in">require</span>(<span class="hljs-string">'winston'</span>);

<span class="hljs-keyword">const</span> logger = winston.createLogger({

  <span class="hljs-attr">level</span>: <span class="hljs-string">'info'</span>,

  <span class="hljs-attr">format</span>: winston.format.combine(

    winston.format.timestamp(),

    winston.format.json()

  ),

  <span class="hljs-attr">transports</span>: [

    <span class="hljs-keyword">new</span> winston.transports.Console(),

    <span class="hljs-comment">// new winston.transports.File({ filename: 'error.log', level: 'error' }),</span>

    <span class="hljs-comment">// new winston.transports.File({ filename: 'combined.log', level: 'info' }),</span>

  ],

});

<span class="hljs-built_in">module</span>.exports = logger;
</code></pre>
<h3 id="heading-serverappjs-snippet-2">server/app.js (snippet)</h3>
<p>With Winston and Morgan set up, now let’s integrate them into our <code>app.js</code> file. We’ll use Morgan for request logging during development and replace standard <code>console.log</code> calls with Winston logs for structured and configurable application logging.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> morgan = <span class="hljs-built_in">require</span>(<span class="hljs-string">'morgan'</span>);

<span class="hljs-keyword">const</span> logger = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./config/logger'</span>);

<span class="hljs-keyword">if</span> (process.env.NODE_ENV === <span class="hljs-string">'development'</span>) {

  app.use(morgan(<span class="hljs-string">'dev'</span>));

}

<span class="hljs-comment">// Replace console.log with logger.info for database connection</span>

mongoose.connect(process.env.MONGO_URI)

  .then(<span class="hljs-function">() =&gt;</span> logger.info(<span class="hljs-string">'MongoDB connected!'</span>))

  .catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> logger.error(<span class="hljs-string">'MongoDB connection error:'</span>, err));


<span class="hljs-comment">// Replace console.log in app.listen</span>

app.listen(PORT, <span class="hljs-function">() =&gt;</span> {

  logger.info(<span class="hljs-string">`Server running on port <span class="hljs-subst">${PORT}</span>`</span>);

});
</code></pre>
<h3 id="heading-frontend-error-monitoring-sentry"><strong>Frontend Error Monitoring (Sentry)</strong></h3>
<p>To monitor errors in the frontend, we’ll integrate Sentry. It’s a fantastic tool for tracking exceptions and performance issues in real time. It helps us capture and report client-side errors.</p>
<h4 id="heading-installation-3">Installation</h4>
<pre><code class="lang-bash">npm install @sentry/react @sentry/tracing
</code></pre>
<h4 id="heading-clientsrcindexjs-snippet">client/src/index.js (snippet)</h4>
<p>After installation, let’s initialize Sentry in the React application so that it can automatically capture errors and performance data. We’ll add this to our <code>index.js</code> file.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> Sentry <span class="hljs-keyword">from</span> <span class="hljs-string">'@sentry/react'</span>;

<span class="hljs-keyword">import</span> { BrowserTracing } <span class="hljs-keyword">from</span> <span class="hljs-string">'@sentry/tracing'</span>;


Sentry.init({

  <span class="hljs-attr">dsn</span>: <span class="hljs-string">"YOUR_SENTRY_DSN"</span>, <span class="hljs-comment">// Replace with your Sentry DSN</span>

  <span class="hljs-attr">integrations</span>: [<span class="hljs-keyword">new</span> BrowserTracing()],

  <span class="hljs-attr">tracesSampleRate</span>: <span class="hljs-number">1.0</span>,

  <span class="hljs-attr">environment</span>: process.env.NODE_ENV,

});
</code></pre>
<p>And that’s it! Congratulations on building and deploying a full-stack MERN app.</p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>This article provided a code-first walkthrough of building, securing, and deploying a MERN stack application. By focusing on practical code examples and essential configurations, you now have a solid foundation for your MERN projects.</p>
<p>Remember, continuous learning and adaptation are key in the ever-evolving world of web development. Happy coding!</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Replit Clone with Socket.io, Monaco Editor, and Copilotkit ]]>
                </title>
                <description>
                    <![CDATA[ I’ve been coding for about a decade now. And over the years, I’ve tried my fair share of development tools—especially IDEs like Sublime Text, Atom, and even NetBeans back in my college days. But when VS Code came along, it completely changed the game... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/how-to-build-a-replit-clone-with-socketio-monaco-editor-and-copilotkit/</link>
                <guid isPermaLink="false">67b7b7b46d2a73d146b3a978</guid>
                
                    <category>
                        <![CDATA[ AI ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Prankur Pandey ]]>
                </dc:creator>
                <pubDate>Thu, 20 Feb 2025 23:16:04 +0000</pubDate>
                <media:content url="https://cdn.hashnode.com/res/hashnode/image/upload/v1740064335866/a058fbf3-2d89-4e95-9d3b-07224f3985be.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>I’ve been coding for about a decade now. And over the years, I’ve tried my fair share of development tools—especially IDEs like Sublime Text, Atom, and even NetBeans back in my college days. But when VS Code came along, it completely changed the game for me. It’s lightweight, fast, and packed with features that just make life easier as a developer. It quickly became my favourite tool.</p>
<p>With all the recent advancements in AI, I wanted to build something that’s not just fun but also a meaningful learning experience. That’s how this project was born—a simple Replit-inspired clone for the web. It combines AI to generate code, lets you run React files, and displays the output seamlessly, just like Replit. On top of that, you can edit files and save your work in real-time, so nothing ever gets lost.</p>
<h3 id="heading-what-well-cover"><strong>What we’ll cover:</strong></h3>
<ul>
<li><p><a class="post-section-overview" href="#heading-prerequisites-amp-tools">Prerequisites &amp; Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-what-well-do-here">What We’ll Do Here:</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-does-the-app-work">How Does the App Work?</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-set-up-your-tools">How to Set Up Your Tools</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-structure-and-features-of-the-app">Structure and Features of the App</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-back-end">How to Build the Back End</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-how-to-build-the-front-end">How to Build the Front End</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-playing-with-the-replit-clone">Playing with the Replit Clone</a></p>
</li>
<li><p><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></p>
</li>
</ul>
<h2 id="heading-prerequisites-amp-tools">Prerequisites &amp; Tools</h2>
<p>In this tutorial, we’ll build an AI-powered Replit clone—a web-based IDE. This IDE will enable you to generate React code files, edit them in a VSCode-like environment, preview the final output, and save the code in real time. It will also support CRUD operations on the generated files.</p>
<p>For this project, I’ll leverage some tools I’ve used in the past, including those from my <a target="_blank" href="https://www.freecodecamp.org/news/talk-to-databases-using-ai-build-a-sql-query-data-extractor">SQL Query Data Extractor project</a>. Below are the tools and technologies we’ll use:</p>
<h3 id="heading-database">Database</h3>
<p>The database is the backbone of any application—it stores data and serves it as needed. For this project, I’ll use my all-time favourite, MongoDB Atlas.</p>
<p>I chose MongoDB Atlas because it integrates seamlessly with Next.js, and since it’s a cloud-based database, I don’t need to host it manually—making it a plug-and-play solution. Performing CRUD operations with MongoDB Atlas is straightforward and efficient.</p>
<h3 id="heading-code-editor">Code Editor</h3>
<p>The code editor is the core of this project, as it powers the IDE experience. For this, I’ll use the legendary Monaco Editor, the same editor that drives VSCode. Monaco Editor handles files effortlessly and supports a wide range of file types. In this project, it will allow users to view and edit code files.</p>
<h3 id="heading-code-preview">Code Preview</h3>
<p>Once we generate and edit code in the Monaco Editor, we’ll need a way to preview its output. For this, I’ll use CodeSandbox’s Sandpack, a free and powerful tool for live code previews.</p>
<p>Sandpack supports various frameworks and file types, whether you’re working with static HTML/CSS files or frameworks like React. It displays files and their real-time output seamlessly.</p>
<h3 id="heading-ai-agent">AI Agent</h3>
<p>The AI Agent will be responsible for generating code using Natural Language Processing (NLP). Acting as a bridge between your ideas and the code, it will take user prompts and translate them into functional code files.</p>
<p>For this, I’ll use CopilotKit, my favourite free and open-source tool for AI-powered code generation. CopilotKit will take your ideas and create the corresponding code files based on your input.</p>
<h3 id="heading-ai-model">AI Model</h3>
<p>The AI Agent relies on an underlying AI model to process user inputs and generate code. For this project, I’ll use GroqAI, a flexible and reliable platform that supports various popular AI models. GroqAI’s versatility makes it perfect for this project’s requirements.</p>
<h3 id="heading-nextjs"><strong>Next.js</strong></h3>
<p>To build a robust web application that combines both frontend and backend functionalities, I’ll use Next.js. It’s an excellent framework for creating scalable applications, offering server-side rendering and other powerful features that are ideal for this project.</p>
<h3 id="heading-deployment"><strong>Deployment</strong></h3>
<p>For deployment, you can choose any service. I prefer Vercel, as it integrates seamlessly with Next.js and is free for hobby projects.</p>
<p>By combining these tools, you’ll build a powerful, user-friendly application that effortlessly produces the code and provides a live preview like Replit does.</p>
<h2 id="heading-what-well-do-here"><strong>What We’ll Do Here</strong></h2>
<p>In this tutorial, you’ll follow these steps to build our app:</p>
<p><strong>Step 1 – Set Up the Database:</strong></p>
<p>Set up a database either locally or on the cloud. For seamless integration, use an online database tool that supports data access and extraction via REST APIs.</p>
<p><strong>Step 2 – Obtain Cloud API Keys:</strong></p>
<p>Retrieve the necessary API keys for your AI model to enable smooth and secure integration.</p>
<p><strong>Step 3 – Build the Web Application:</strong></p>
<p>Develop a web application and configure the backend to integrate CopilotKit. Ensure it’s properly set up for efficient functionality.</p>
<p><strong>Step 4 – Train CopilotKit with Your Database:</strong></p>
<p>Provide your database data to CopilotKit so it can understand and utilize the information for natural language processing.</p>
<p><strong>Step 5 – Integrate the CopilotKit Chat Interface:</strong></p>
<p>Embed the CopilotKit chat interface into your application and configure it to work seamlessly with your app’s workflow.</p>
<p><strong>Step 6 – Test Locally:</strong></p>
<p>Run the application on your local machine, thoroughly testing each feature to identify and resolve any issues.</p>
<p><strong>Step 7 – Deploy the Application:</strong></p>
<p>Once testing is complete and the app is working as expected, deploy it to a hosting platform for public use.</p>
<h2 id="heading-how-does-the-app-work"><strong>How Does the App Work?</strong></h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739811798424/af2b925d-95d9-422c-8318-fe0d9d37962b.png" alt="working-app-explanation" class="image--center mx-auto" width="791" height="611" loading="lazy"></p>
<p>This project is a fun experiment and a step toward my long-term goal of building something around code editors, particularly inspired by VSCode.</p>
<p>The real magic happens with CopilotKit. As soon as you input an idea into CopilotKit, it uses predefined system prompts that adapt to your project requirements. These prompts allow CopilotKit to interpret plain English instructions and transform them into meaningful outputs. In this tutorial, I’ll show you how to configure these system prompts effectively to maximize results.</p>
<p>For example, if you enter the idea <em>“build a simple React app”</em>, CopilotKit passes that idea to the integrated AI model. The AI model, working in coordination with CopilotKit’s system prompts, generates the necessary code files based on your input.</p>
<p>The generated files are then displayed in the File Explorer on the left side of the screen. You can easily browse through the files created by CopilotKit.</p>
<p>To preview the code, simply click on a file like <code>App.js</code>. The file’s code will load into the Monaco Editor on the left, while the Sandpack preview on the right will render a real-time output of the file.</p>
<p>You can now experiment with the files—tweak the code, change colours, fonts, or text, and even write your own logic, just like working with regular HTML, CSS, or React files. Any changes you make will be saved in real time to the database. So even if you accidentally close the project, your progress will be intact. Simply refresh the page, and your code will be right where you left it.</p>
<h2 id="heading-how-to-set-up-your-tools"><strong>How to Set Up Your Tools</strong></h2>
<p>Now we’ll go through everything you need to set up the project.</p>
<h3 id="heading-install-nextjs-and-dependencies"><strong>Install Next.js and dependencies:</strong></h3>
<p>First, you’ll need to create a Next.js app. Go to the terminal and run the following command:</p>
<pre><code class="lang-javascript">npx create-next-app@latest my-next-app
</code></pre>
<p>Replace <code>my-next-app</code> with your desired project name and use TypeScript.</p>
<p>Navigate to the project folder:</p>
<pre><code class="lang-javascript">cd my-next-app
</code></pre>
<p>Start the development server:</p>
<pre><code class="lang-javascript">npm run dev
</code></pre>
<p>Open your browser and navigate to <a target="_blank" href="http://localhost:3000/"><code>http://localhost:3000</code></a> to see your Next.js app in action.</p>
<h3 id="heading-install-copilotkit-and-dependencies"><strong>Install CopilotKit and dependencies</strong></h3>
<p>Navigate to the project’s root folder in the terminal and run the following command. This will install all the necessary dependencies for CopilotKit along with other essential packages, such as dotenv, groq-sdk, sandpack, Monaco Editor, Lucide React, Socket and Mongoose.</p>
<pre><code class="lang-javascript">npm install @copilotkit/react-ui @copilotkit/react-core
npm install dotenv
npm install groq-sdk
npm install @codesandbox/sandpack-react
npm install @monaco-editor/react
npm install lucide-react
npm install mongoose
npm install socket.io
npm install socket.io-client
</code></pre>
<ul>
<li><p><strong>CopilotKit</strong>: This dependency handles all operations and configurations related to CopilotKit.</p>
</li>
<li><p><strong>Dotenv</strong>: Used for managing environment variables, and keeping sensitive keys secure within the project.</p>
</li>
<li><p><strong>GroqSDK</strong>: Facilitates access to various LLM models through a single API key.</p>
</li>
<li><p><strong>CodeSandbox Sandpack (React)</strong>: Provides the ability to display real-time previews of the code.</p>
</li>
<li><p><strong>Monaco Editor</strong>: Powers the VSCode-like environment, enabling real-time code editing.</p>
</li>
<li><p><strong>Lucide React</strong>: An icon library used to display icons for files and folders.</p>
</li>
<li><p><strong>Mongoose</strong>: Manages MongoDB schemas for storing and retrieving data from the database.</p>
</li>
<li><p><strong>socket.io:</strong> A very powerful tool for real-time data syncing between client and server.</p>
</li>
<li><p><strong>socket.io client</strong>: Extra socket.io client package for data communication.</p>
</li>
</ul>
<h3 id="heading-set-up-the-llm-for-action"><strong>Set Up the LLM for Action:</strong></h3>
<p>This step is crucial for the project, as it involves setting up the LLM (Large Language Model) to convert natural language (plain English) queries into a React framework working code.</p>
<p>There are many LLMs available, each with its unique strengths. Some are free, while others are paid, making the selection process for this project a bit challenging.</p>
<p>After thorough experimentation, I chose the Groq Adapter because:</p>
<ul>
<li><p>It integrates multiple LLMs into a single platform.</p>
</li>
<li><p>It offers access via a unified API key.</p>
</li>
<li><p>It’s fully compatible with CopilotKit.</p>
</li>
</ul>
<h4 id="heading-how-to-set-up-groq-cloud"><strong>How to Set Up Groq Cloud</strong></h4>
<p>To get started with Groq Cloud, visit its website and either log in if you already have an account or create a new account if you’re new. Once logged in, navigate to the Groq Dashboard.</p>
<p>This is the homepage of Groq cloud:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736352733541/92012af5-b3c4-4277-a50f-834c1900a2de.png" alt="groq cloud homepage" width="1366" height="768" loading="lazy"></p>
<p>Once logged in, a new page will open that’ll look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736353229314/67313c60-47b8-4f23-b3c0-e46fcdd5201a.png" alt="groq cloud dahsboard page " width="1366" height="768" loading="lazy"></p>
<p>As you can see, the sidebar has an API Keys link. Click on it, and it will open a new page as shown in the image below. You can also select any LLM of your choice which is given at the top right before the view code option.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736353347970/3406fa54-ddc2-4a00-8b27-22536486fc64.png" alt="groqcloud api section" width="1366" height="768" loading="lazy"></p>
<p>Here, click on the Create API Key button it will open a pop up like you see below. Just enter the name of your API key and click on Submit to create a new API key for you. Then copy this API key and paste it inside your <code>.env</code> file.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736353563741/cd1a185a-2c77-470a-a5ce-eca564cf524a.png" alt="groq cloud api key creation page " width="1366" height="768" loading="lazy"></p>
<p>To enable seamless access to various LLMs on Groq Cloud, generate an API key by going to the Groq API Keys section. Create a new API key specifically for the LLM, ensuring that it is properly configured.</p>
<p>With the LLM set-up and all components ready, you are now prepared to build the project.</p>
<h3 id="heading-how-to-setup-the-database">How to setup the database</h3>
<h3 id="heading-step-1-create-a-mongodb-atlas-account"><strong>Step 1: Create a MongoDB Atlas Account</strong></h3>
<ol>
<li><p>Go to the <a target="_blank" href="https://www.mongodb.com/cloud/atlas">MongoDB Atlas website</a>.</p>
</li>
<li><p>Click on <strong>"Try Free"</strong> or <strong>"Sign Up"</strong>.</p>
</li>
<li><p>Fill in your details (name, email, password) to create an account.</p>
</li>
<li><p>Verify your email address by clicking the link sent to your inbox.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738858230073/c150c2be-8c4c-4e79-af44-3c0792b764e3.png" alt="mongodb signup/login page" class="image--center mx-auto" width="1366" height="768" loading="lazy"></p>
<p> <strong>Step 2: Create a New Project</strong></p>
</li>
<li><p>After logging in, you will be directed to the MongoDB Atlas dashboard.</p>
</li>
<li><p>Click on the "New Project" button. This will take you to the Create a Project page.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739188846705/a25e1885-6f7d-4b00-907e-f03e4198d131.png" alt="mongodb create project page" class="image--center mx-auto" width="1366" height="768" loading="lazy"></p>
</li>
<li><p>Fill in the Project name and click on the next button, and it will open a new page to show the project owner's information.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739189031867/42ae7fda-2a8e-4bd1-92f3-5c3dae4cc502.png" alt="mongodb create project page" class="image--center mx-auto" width="1366" height="768" loading="lazy"></p>
</li>
<li><p>Now click on the Create Project Button. This will take you to the main dashboard of the project where you will get the option to create the database.</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739189159505/35ecaac7-bbc4-4ee8-8ba0-196da2eaa311.png" alt="mongodb overview page" class="image--center mx-auto" width="1366" height="768" loading="lazy"></p>
</li>
<li><p>Now Click on the Create Button to open a new page with details of deploying your cluster.</p>
</li>
<li><p>Choose a cloud provider (AWS, Google Cloud, or Azure) and a region closest to your location.</p>
</li>
<li><p>Select the "Free Tier" (free forever, but with limited resources) or a paid tier for larger projects.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739189327083/46c38f50-519e-473c-b302-b0239ab409f8.png" alt="mongodb cluster selection page" class="image--center mx-auto" width="1366" height="768" loading="lazy"></p>
</li>
<li><p>Give your cluster a name (for example, <code>MyCluster</code>).</p>
</li>
<li><p>Click "Create Deployment". It will take a few minutes for the cluster to be provisioned.</p>
</li>
<li><p>Then It will ask you to connect your cluster to the database through a service. You should see your username and password – keep this somewhere.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739189624879/68a26902-861a-44b2-8d01-d04ceef55ddc.png" alt="mogodb database connection page" class="image--center mx-auto" width="1366" height="768" loading="lazy"></p>
</li>
<li><p>Here, you will have to make yourself a database user, so click on the Create Database user button.</p>
</li>
<li><p>It will take a few seconds to complete this process. Once it’s done, close the pop-up and return back to the dashboard.</p>
</li>
<li><p>On the dashboard page you can see Get Connection String button. Go on and click on it.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739189943883/84621420-312f-45e7-995e-18bf68245b1d.png" alt="mongodb db setup page" class="image--center mx-auto" width="1366" height="768" loading="lazy"></p>
</li>
<li><p>It will open a new popup containing your MongoDB atlas URI. Simply copy the string, put it into your <code>.env</code> file and use the password you created in step 14.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1739190119546/2dab2beb-02aa-4450-9272-fb7aac99c313.png" alt="mongodb URL view/hide page" class="image--center mx-auto" width="1366" height="768" loading="lazy"></p>
</li>
</ol>
<h3 id="heading-example-use-case">Example use case :</h3>
<pre><code class="lang-javascript"><span class="hljs-comment">//.env file</span>
MONGODB_URI=<span class="hljs-string">'YOUR MONGODB URL'</span>
</code></pre>
<h2 id="heading-structure-and-features-of-the-app"><strong>Structure and Features of the App</strong></h2>
<p>The focus of this project is on simplicity and functionality, to replicate Replit's core features like code editing and real-time previews. The idea is to create a straightforward web application that lets you:</p>
<ul>
<li><p>Host three essential components: a File Explorer, the Monaco Editor, and a Sandbox.</p>
</li>
<li><p>Open files generated by CopilotKit in the Monaco Editor and perform CRUD operations on them.</p>
</li>
<li><p>See the real-time output of your code as you work.</p>
</li>
<li><p>Chat with the CopilotKit chatbot, which will be fully integrated into the front end.</p>
</li>
</ul>
<p>The plan is to keep the implementation clean and practical while delivering a smooth coding experience.</p>
<h3 id="heading-webpage-structure"><strong>Webpage Structure</strong></h3>
<p>Since we’ve already set up the Next.js app, the next step is to create a minimalistic webpage with the following components:</p>
<ol>
<li><p><strong>File Explorer:</strong> Displays the files generated by CopilotKit.</p>
</li>
<li><p><strong>Monaco Editor:</strong> A versatile code editor that handles various file types and displays the content.</p>
</li>
<li><p><strong>Sandbox:</strong> Shows the real-time output of the code.</p>
</li>
<li><p><strong>CopilotKit Chatbot:</strong> Generates code files based on natural language prompts.</p>
</li>
</ol>
<h3 id="heading-key-features"><strong>Key Features</strong></h3>
<ul>
<li><p><strong>Error Handling:</strong> Any failures, such as API or database issues, will be highlighted with red text for immediate visibility.</p>
</li>
<li><p><strong>Data Presentation:</strong> Data is presented in two parts: first in the File Explorer for code files, and second in the Monaco Editor for viewing the content of those files.</p>
</li>
<li><p><strong>CopilotKit Chatbot Integration:</strong> The chatbot will allow natural language interactions with the database. The blue-coloured ball on the page represents the CopilotKit chatbot, which serves as the key interface for interacting with the database.</p>
<ul>
<li><p>Users can ask questions about the database using natural language.</p>
</li>
<li><p>The chatbot processes these queries, converts them into SQL, and fetches the results seamlessly.</p>
</li>
</ul>
</li>
</ul>
<p>The front end will look something like this: <a target="_blank" href="https://replit-mongodb.vercel.app/">https://replit-mongodb.vercel.app/</a></p>
<h2 id="heading-how-to-build-the-back-end"><strong>How to Build the Back End</strong></h2>
<p>Before we start building the back end, you’ll need to put all important credentials into your <code>.env</code> file, which should look like this:</p>
<pre><code class="lang-xml">NEXT_PUBLIC_GROQ_CLOUD_API_KEY=<span class="hljs-tag">&lt;<span class="hljs-name">your-key-here</span>&gt;</span>
MONGODB_URI=<span class="hljs-tag">&lt;<span class="hljs-name">your-mongodb-url</span>&gt;</span>
</code></pre>
<p>These are environment variables used to configure sensitive or environment-specific settings in an application:</p>
<ol>
<li><p><code>NEXT_PUBLIC_GROQ_CLOUD_API_KEY</code>:</p>
<ul>
<li><p>This is a public API key for accessing the Groq Cloud API.</p>
</li>
<li><p>It is prefixed with <code>NEXT_PUBLIC_</code>, which means it is exposed to the client-side code in a Next.js application.</p>
</li>
<li><p>Replace <code>&lt;your-key-here&gt;</code> with the actual API key provided by Groq Cloud.</p>
</li>
</ul>
</li>
<li><p><code>MONGODB_URI</code>:</p>
<ul>
<li><p>This is the connection string for a MongoDB database.</p>
</li>
<li><p>It includes the database URL, credentials, and other connection details.</p>
</li>
<li><p>Replace <code>&lt;your-mongodb-url&gt;</code> with the actual MongoDB connection string.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-how-to-configure-the-copilotkit-back-end"><strong>How to Configure the CopilotKit Back End</strong></h3>
<p>Open your Next.js app in any code editor—I prefer VSCode—and go to the root folder, which looks like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738858655371/f648cb98-62a0-4bdd-9d03-994c7bbf758f.png" alt="f648cb98-62a0-4bdd-9d03-994c7bbf758f" class="image--center mx-auto" width="317" height="490" loading="lazy"></p>
<p>Inside the <code>app</code> folder, make a new folder called <code>api</code>. Inside the API folder, make another folder called <code>copilotkit</code>. Then in there, make a new file called <code>route.js</code> and paste this code inside the file:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> {
  CopilotRuntime,
  GroqAdapter,
  copilotRuntimeNextJSAppRouterEndpoint,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@copilotkit/runtime"</span>;

<span class="hljs-keyword">import</span> Groq <span class="hljs-keyword">from</span> <span class="hljs-string">"groq-sdk"</span>;

<span class="hljs-keyword">const</span> groq = <span class="hljs-keyword">new</span> Groq({ <span class="hljs-attr">apiKey</span>: process.env.NEXT_PUBLIC_GROQ_CLOUD_API_KEY });

<span class="hljs-keyword">const</span> copilotKit = <span class="hljs-keyword">new</span> CopilotRuntime({
  <span class="hljs-keyword">async</span> onResponse({ message, context }) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Extract any file operations from the message and process them</span>
      <span class="hljs-keyword">const</span> fileBlocks = message.content.split(<span class="hljs-string">"---"</span>);
      <span class="hljs-keyword">if</span> (fileBlocks.length &gt; <span class="hljs-number">0</span>) {
        <span class="hljs-comment">// Format the response to use processFiles action</span>
        <span class="hljs-keyword">return</span> {
          <span class="hljs-attr">content</span>: <span class="hljs-string">`@processFiles(response: \\`</span>${message.content}\\<span class="hljs-string">`)`</span>,
        };
      }
      <span class="hljs-keyword">return</span> message;
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error in onResponse:"</span>, error);
      <span class="hljs-keyword">return</span> message;
    }
  },
});

<span class="hljs-keyword">const</span> serviceAdapter = <span class="hljs-keyword">new</span> GroqAdapter({
  groq,
  <span class="hljs-attr">model</span>: <span class="hljs-string">"llama-3.3-70b-versatile"</span>,
  <span class="hljs-attr">systemPrompt</span>: <span class="hljs-string">`You are an AI-powered code generator integrated into a web-based IDE. Your task is to generate project files and code based on user commands.

When generating files, use this exact format:

FILE: filename.ext
CODE:
[code content here]

For multiple files, separate them with "---".

Example response:
I'll create a React component:

FILE: Button.jsx
CODE:
import React from 'react';

const Button = () =&gt; {
  return (
    &lt;button className="btn"&gt;Click me&lt;/button&gt;
  );
};

export default Button;

Important rules:
- Always include both FILE: and CODE: markers
- Use appropriate file extensions
- Generate complete, working code
- Maintain proper indentation
- Explain what you're creating before showing the files
- Make sure code is syntactically correct`</span>,
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> POST = <span class="hljs-keyword">async</span> (req) =&gt; {
  <span class="hljs-keyword">const</span> { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
    <span class="hljs-attr">runtime</span>: copilotKit,
    serviceAdapter,
    <span class="hljs-attr">endpoint</span>: <span class="hljs-string">"/api/copilotkit"</span>,
  });

  <span class="hljs-keyword">return</span> handleRequest(req);
};
</code></pre>
<p>Here’s a detailed explanation of each part:</p>
<p>This code defines a CopilotKit Runtime integration with Next.js, designed to process requests for generating and managing code files in a web-based IDE environment. It connects to the <code>Groq</code> cloud service for additional functionalities and processes file-based outputs from AI-generated responses.</p>
<p>This code sets up a <code>CopilotRuntime</code> integration with Groq's AI model to generate and process code files in response to user requests. Here's a breakdown:</p>
<h3 id="heading-key-components"><strong>Key Components</strong>:</h3>
<ol>
<li><p><strong>Groq Initialization</strong>:</p>
<ul>
<li><p>The <code>Groq</code> SDK is initialized using the <code>NEXT_PUBLIC_GROQ_CLOUD_API_KEY</code> environment variable.</p>
</li>
<li><p>The model used is <code>llama-3.3-70b-versatile</code>.</p>
</li>
</ul>
</li>
<li><p><strong>CopilotRuntime</strong>:</p>
<ul>
<li><p>A <code>CopilotRuntime</code> instance is created with a custom <code>onResponse</code> handler.</p>
</li>
<li><p>The <code>onResponse</code> function processes the AI's response:</p>
<ul>
<li><p>Extracts file blocks (separated by <code>---</code>) from the message.</p>
</li>
<li><p>Formats the response to trigger a <code>processFiles</code> action if file blocks are detected.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>GroqAdapter</strong>:</p>
<ul>
<li><p>A <code>GroqAdapter</code> is configured to interact with the Groq API.</p>
</li>
<li><p>It includes a system prompt that instructs the AI to generate code files in a specific format:</p>
<ul>
<li><p>Files are marked with <code>FILE:</code> and <code>CODE:</code>.</p>
</li>
<li><p>Multiple files are separated by <code>---</code>.</p>
</li>
<li><p>The AI is instructed to generate complete, syntactically correct code with proper explanations.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>API Endpoint</strong>:</p>
<ul>
<li><p>A <code>POST</code> the endpoint is exposed using Next.js App Router.</p>
</li>
<li><p>It uses <code>copilotRuntimeNextJSAppRouterEndpoint</code> to handle incoming requests, passing them to the <code>CopilotRuntime</code> and <code>GroqAdapter</code>.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-example-usage">Example Usage</h3>
<ol>
<li><p><strong>Request</strong></p>
<ul>
<li><p>A POST request to <code>/api/copilotkit</code> might look like this:</p>
<pre><code class="lang-bash">  curl -X POST http://localhost:3000/api/copilotkit \
  -H <span class="hljs-string">"Content-Type: application/json"</span> \
  -d <span class="hljs-string">'{"command": "Create a React component for a button"}'</span>
</code></pre>
</li>
</ul>
</li>
<li><p><strong>AI Response (Processed by</strong> <code>onResponse</code>)</p>
<ul>
<li><p>AI might return this response:</p>
<pre><code class="lang-plaintext">  FILE: Button.jsx
  CODE:
  import React from 'react';

  const Button = () =&gt; {
    return &lt;button&gt;Click me&lt;/button&gt;;
  };

  export default Button;
</code></pre>
</li>
</ul>
</li>
<li><p><strong>Response to Client</strong></p>
<ul>
<li><p>The API wraps the response into the formatted structure:</p>
<pre><code class="lang-json">  {
    <span class="hljs-attr">"content"</span>: <span class="hljs-string">"@processFiles(response: `FILE: Button.jsx\nCODE:\nimport React from 'react';\n\nconst Button = () =&gt; {\n  return &lt;button&gt;Click me&lt;/button&gt;;\n};\n\nexport default Button;`)"</span>
  }
</code></pre>
</li>
</ul>
</li>
</ol>
<h3 id="heading-key-features-1">Key Features</h3>
<ol>
<li><p><strong>AI-Powered Code Generation with copilotkit popup</strong>:</p>
<ul>
<li><p>The system generates complete project files based on user instructions.</p>
</li>
<li><p>Ensures proper formatting (for example, <code>FILE:</code> and <code>CODE:</code> markers).</p>
</li>
</ul>
</li>
<li><p><strong>File Handling</strong>:</p>
<ul>
<li><p>Splits multi-file responses into manageable blocks using <code>---</code>.</p>
</li>
<li><p>Supports actions like <code>@processFiles</code> for integration with the IDE.</p>
</li>
</ul>
</li>
<li><p><strong>Scalable API</strong>:</p>
<ul>
<li>Modular design with <code>CopilotRuntime</code> and <code>GroqAdapter</code> allows easy extension and customization.</li>
</ul>
</li>
<li><p><strong>Error Handling</strong>:</p>
<ul>
<li><p>Logs errors without interrupting the workflow.</p>
</li>
<li><p>Defaults to returning the unprocessed message on failure.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-making-routes-for-crud-operation">Making Routes for CRUD Operation</h3>
<p>So far, we've covered how to integrate CopilotKit into the backend. Now, we need to handle file operations, so we'll create another route to manage files with the database.</p>
<p>To develop the backend for file handling, I'll create a new folder inside the API folder and name it <code>files</code>. Inside the <code>files</code> folder, I’ll create a simple <code>route.js</code> file. Here’s the code I’ll be using inside the file:</p>
<ul>
<li><p><code>app/api/files/route.tsx</code></p>
</li>
<li><pre><code class="lang-javascript">
  <span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
  <span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>;
  <span class="hljs-keyword">import</span> { connectDB, File } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/lib/mongodb"</span>;

  <span class="hljs-comment">// Type for the request body</span>
  interface FileRequestBody {
    id?: string;
    name?: string;
    content?: string;
  }interface FileCreateRequest {
    <span class="hljs-attr">name</span>: string;
    content: string;
  }

  interface FileUpdateRequest {
    <span class="hljs-attr">id</span>: string;
    name?: string;
    content?: string;
  }

  <span class="hljs-comment">// Fetch all files (GET /api/files)</span>
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Response</span>&gt; </span>{
  <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> connectDB(); <span class="hljs-comment">// Ensure DB connection</span>
      <span class="hljs-keyword">const</span> files = <span class="hljs-keyword">await</span> File.find({});
      <span class="hljs-keyword">return</span> NextResponse.json(files, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">return</span> NextResponse.json(
        { <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to fetch files"</span> },
        { <span class="hljs-attr">status</span>: <span class="hljs-number">500</span> }
      );
    }
  }

  <span class="hljs-comment">// Create a new file (POST /api/files)</span>
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: Request</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Response</span>&gt; </span>{
  <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> connectDB(); <span class="hljs-comment">// Ensure DB connection is successful</span>
      <span class="hljs-comment">// Parse the request body</span>
      <span class="hljs-keyword">const</span> { name, content }: FileRequestBody = <span class="hljs-keyword">await</span> req.json();
      <span class="hljs-keyword">if</span> (!name || !content) {
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Missing required fields: name or content"</span>);
      }

      <span class="hljs-comment">// Log the incoming data for debugging</span>
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Creating file with data:"</span>, { name, content });

      <span class="hljs-comment">// Create a new file in the database</span>
      <span class="hljs-keyword">const</span> newFile = <span class="hljs-keyword">new</span> File({ name, content });
      <span class="hljs-keyword">await</span> newFile.save();

      <span class="hljs-comment">// Return the newly created file</span>
      <span class="hljs-keyword">return</span> NextResponse.json(newFile, { <span class="hljs-attr">status</span>: <span class="hljs-number">201</span> });
    } <span class="hljs-keyword">catch</span> (error: any) {
      <span class="hljs-comment">// Log the error for debugging</span>
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error creating file:"</span>, error);

      <span class="hljs-keyword">return</span> NextResponse.json(
        { <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to create file"</span>, <span class="hljs-attr">message</span>: error.message },
        { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
      );
    }
  }

  <span class="hljs-comment">// Update file content (PUT /api/files)</span>
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">PUT</span>(<span class="hljs-params">req: Request</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Response</span>&gt; </span>{
  <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> connectDB(); <span class="hljs-comment">// Ensure DB connection</span>
      <span class="hljs-keyword">const</span> { id, name, content }: FileRequestBody = <span class="hljs-keyword">await</span> req.json();

      <span class="hljs-comment">// Validate ID format</span>
      <span class="hljs-keyword">if</span> (!id || !mongoose.Types.ObjectId.isValid(id)) {
        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Invalid file ID"</span> }, { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> });
      }

      <span class="hljs-comment">// Update file name or content if provided</span>
      <span class="hljs-keyword">const</span> updatedFile = <span class="hljs-keyword">await</span> File.findByIdAndUpdate(
        id,
        { ...(name &amp;&amp; { name }), ...(content &amp;&amp; { content }) },
        { <span class="hljs-attr">new</span>: <span class="hljs-literal">true</span> }
      );

      <span class="hljs-keyword">if</span> (!updatedFile) {
        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"File not found"</span> }, { <span class="hljs-attr">status</span>: <span class="hljs-number">404</span> });
      }

      <span class="hljs-keyword">return</span> NextResponse.json(updatedFile, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
    } <span class="hljs-keyword">catch</span> (error: any) {
      <span class="hljs-keyword">return</span> NextResponse.json(
        { <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to update file"</span> },
        { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
      );
    }
  }

  <span class="hljs-comment">// Delete a file (DELETE /api/files)</span>
  <span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">DELETE</span>(<span class="hljs-params">req: Request</span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Response</span>&gt; </span>{
  <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">await</span> connectDB(); <span class="hljs-comment">// Ensure DB connection</span>
      <span class="hljs-keyword">const</span> { id }: FileRequestBody = <span class="hljs-keyword">await</span> req.json();

      <span class="hljs-comment">// Validate ID format</span>
      <span class="hljs-keyword">if</span> (!id || !mongoose.Types.ObjectId.isValid(id)) {
        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Invalid file ID"</span> }, { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> });
      }

      <span class="hljs-keyword">await</span> File.findByIdAndDelete(id);
      <span class="hljs-keyword">return</span> NextResponse.json(
        { <span class="hljs-attr">message</span>: <span class="hljs-string">"File deleted successfully"</span> },
        { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> }
      );
    } <span class="hljs-keyword">catch</span> (error: any) {
      <span class="hljs-keyword">return</span> NextResponse.json(
        { <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to delete file"</span> },
        { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
      );
    }
  }
</code></pre>
</li>
</ul>
<h3 id="heading-code-explanation"><strong>Code explanation</strong> :</h3>
<p>This code defines API routes for handling CRUD operations (Create, Read, Update, Delete) on a MongoDB collection called <code>File</code> in a Next.js application. Each route is connected to MongoDB using Mongoose and we used <code>NextResponse</code> to format the responses.</p>
<h3 id="heading-code-breakdown">Code Breakdown</h3>
<h4 id="heading-1-imports">1. <strong>Imports</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">import</span> mongoose <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>;
<span class="hljs-keyword">import</span> connectDB <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/lib/mongodb"</span>; <span class="hljs-comment">//we made this file</span>
<span class="hljs-keyword">import</span> File <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/lib/models/File"</span>; <span class="hljs-comment">//we made this file</span>
</code></pre>
<ul>
<li><p><code>NextResponse</code>: Used for creating HTTP responses in Next.js API routes.</p>
</li>
<li><p><code>connectDB</code>: Connects to the MongoDB database.</p>
</li>
<li><p><code>File</code>: A Mongoose model representing the <code>File</code> collection.</p>
</li>
</ul>
<h4 id="heading-2-fetch-all-files-get">2. <strong>Fetch All Files (GET)</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">await</span> connectDB(); <span class="hljs-comment">// Ensure DB connection</span>
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> files = <span class="hljs-keyword">await</span> File.find({}); <span class="hljs-comment">// Fetch all files</span>
    <span class="hljs-keyword">return</span> NextResponse.json(files, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> }); <span class="hljs-comment">// Return files in JSON format</span>
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> NextResponse.json(
      { <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to fetch files"</span> },
      { <span class="hljs-attr">status</span>: <span class="hljs-number">500</span> }
    );
  }
}
</code></pre>
<ul>
<li><p><strong>Purpose</strong>: Retrieves all documents in the <code>File</code> collection.</p>
</li>
<li><p><strong>Flow</strong>:</p>
<ol>
<li><p>Connects to MongoDB.</p>
</li>
<li><p>Uses <code>File.find({})</code> to fetch all files.</p>
</li>
<li><p>Returns the files with a <code>200</code> status or an error with a <code>500</code> status.</p>
</li>
</ol>
</li>
</ul>
<h4 id="heading-3-create-a-new-file-post">3. <strong>Create a New File (POST)</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req</span>) </span>{
  <span class="hljs-keyword">await</span> connectDB(); <span class="hljs-comment">// Ensure DB connection</span>

  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { name, content } = <span class="hljs-keyword">await</span> req.json(); <span class="hljs-comment">// Parse the request body</span>
    <span class="hljs-keyword">if</span> (!name || !content) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Missing required fields: name or content"</span>);
    }

    <span class="hljs-keyword">const</span> newFile = <span class="hljs-keyword">new</span> File({ name, content }); <span class="hljs-comment">// Create new file</span>
    <span class="hljs-keyword">await</span> newFile.save(); <span class="hljs-comment">// Save to MongoDB</span>

    <span class="hljs-keyword">return</span> NextResponse.json(newFile, { <span class="hljs-attr">status</span>: <span class="hljs-number">201</span> }); <span class="hljs-comment">// Return the new file</span>
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> NextResponse.json(
      { <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to create file"</span>, <span class="hljs-attr">message</span>: error.message },
      { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
    );
  }
}
</code></pre>
<ul>
<li><p><strong>Purpose</strong>: Creates a new document in the <code>File</code> collection.</p>
</li>
<li><p><strong>Flow</strong>:</p>
<ol>
<li><p>Parses the <code>name</code> and <code>content</code> fields from the request body.</p>
</li>
<li><p>Validates the required fields.</p>
</li>
<li><p>Creates and save the file in MongoDB.</p>
</li>
<li><p>Returns the new file with a <code>201</code> status or an error with a <code>400</code> status.</p>
</li>
</ol>
</li>
</ul>
<h4 id="heading-4-update-a-file-put">4. <strong>Update a File (PUT)</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">PUT</span>(<span class="hljs-params">req</span>) </span>{
  <span class="hljs-keyword">await</span> connectDB(); <span class="hljs-comment">// Ensure DB connection</span>
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { id, name, content } = <span class="hljs-keyword">await</span> req.json();

    <span class="hljs-keyword">if</span> (!mongoose.Types.ObjectId.isValid(id)) {
      <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Invalid file ID"</span> }, { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> });
    }

    <span class="hljs-keyword">const</span> updatedFile = <span class="hljs-keyword">await</span> File.findByIdAndUpdate(
      id,
      { ...(name &amp;&amp; { name }), ...(content &amp;&amp; { content }) },
      { <span class="hljs-attr">new</span>: <span class="hljs-literal">true</span> }
    );

    <span class="hljs-keyword">if</span> (!updatedFile) {
      <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"File not found"</span> }, { <span class="hljs-attr">status</span>: <span class="hljs-number">404</span> });
    }

    <span class="hljs-keyword">return</span> NextResponse.json(updatedFile, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> NextResponse.json(
      { <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to update file"</span> },
      { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
    );
  }
}
</code></pre>
<ul>
<li><p><strong>Purpose</strong>: Updates an existing document in the <code>File</code> collection.</p>
</li>
<li><p><strong>Flow</strong>:</p>
<ol>
<li><p>Parses the <code>id</code>, <code>name</code>, and <code>content</code> from the request body.</p>
</li>
<li><p>Validates the <code>id</code> using Mongoose's <code>ObjectId</code>.</p>
</li>
<li><p>Updates the document using <code>File.findByIdAndUpdate()</code> (updates only provided fields).</p>
</li>
<li><p>Returns the updated document with a <code>200</code> status, or an error with <code>400</code> or <code>404</code>.</p>
</li>
</ol>
</li>
</ul>
<h4 id="heading-5-delete-a-file-delete">5. <strong>Delete a File (DELETE)</strong></h4>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">DELETE</span>(<span class="hljs-params">req</span>) </span>{
  <span class="hljs-keyword">await</span> connectDB(); <span class="hljs-comment">// Ensure DB connection</span>
  <span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> { id } = <span class="hljs-keyword">await</span> req.json();

    <span class="hljs-keyword">if</span> (!mongoose.Types.ObjectId.isValid(id)) {
      <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">error</span>: <span class="hljs-string">"Invalid file ID"</span> }, { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> });
    }

    <span class="hljs-keyword">await</span> File.findByIdAndDelete(id); <span class="hljs-comment">// Delete the document</span>
    <span class="hljs-keyword">return</span> NextResponse.json(
      { <span class="hljs-attr">message</span>: <span class="hljs-string">"File deleted successfully"</span> },
      { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> }
    );
  } <span class="hljs-keyword">catch</span> (error) {
    <span class="hljs-keyword">return</span> NextResponse.json(
      { <span class="hljs-attr">error</span>: <span class="hljs-string">"Failed to delete file"</span> },
      { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
    );
  }
}
</code></pre>
<ul>
<li><p><strong>Purpose</strong>: Deletes a document from the <code>File</code> collection.</p>
</li>
<li><p><strong>Flow</strong>:</p>
<ol>
<li><p>Parses the <code>id</code> from the request body.</p>
</li>
<li><p>Validates the <code>id</code> format.</p>
</li>
<li><p>Uses <code>File.findByIdAndDelete()</code> to remove the document.</p>
</li>
<li><p>Returns a success message with a <code>200</code> status or an error with <code>400</code>.</p>
</li>
</ol>
</li>
</ul>
<h3 id="heading-example-api-requests-how-to-test-api-in-local">Example API Requests: How to test API in local</h3>
<ol>
<li><p><strong>GET All Files</strong></p>
<pre><code class="lang-bash"> curl -X GET http://localhost:3000/api/files
</code></pre>
</li>
<li><p><strong>Create a File (POST)</strong></p>
<pre><code class="lang-bash"> curl -X POST http://localhost:3000/api/files \
 -H <span class="hljs-string">"Content-Type: application/json"</span> \
 -d <span class="hljs-string">'{"name": "example.txt", "content": "This is a test file"}'</span>
</code></pre>
</li>
<li><p><strong>Update a File (PUT)</strong></p>
<pre><code class="lang-bash"> curl -X PUT http://localhost:3000/api/files \
 -H <span class="hljs-string">"Content-Type: application/json"</span> \
 -d <span class="hljs-string">'{"id": "64ffab67c728f51234567890", "name": "updated.txt", "content": "Updated content"}'</span>
</code></pre>
</li>
<li><p><strong>Delete a File (DELETE)</strong></p>
<pre><code class="lang-bash"> curl -X DELETE http://localhost:3000/api/files \
 -H <span class="hljs-string">"Content-Type: application/json"</span> \
 -d <span class="hljs-string">'{"id": "64ffab67c728f51234567890"}'</span>
</code></pre>
</li>
</ol>
<h3 id="heading-creating-mongodb-schemas">Creating MongoDB Schemas</h3>
<p>Now, create a <code>lib</code> folder inside the <code>app</code> folder. This <code>lib</code> folder will handle essential database tasks, such as database schema and connectivity. Inside the <code>lib</code> folder, create another folder named <code>models</code>. Within this <code>models</code> folder, create a new file called <code>File.js</code> and paste the following code into it.</p>
<p>This version simplifies the instructions and improves clarity while maintaining the original meaning.</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> mongoose, { Schema, Document, Model } <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>;

<span class="hljs-comment">// Define an interface for the file document</span>
<span class="hljs-keyword">interface</span> IFile <span class="hljs-keyword">extends</span> Document {
  name: <span class="hljs-built_in">string</span>;
  content: <span class="hljs-built_in">string</span>;
  createdAt: <span class="hljs-built_in">Date</span>;
  updatedAt: <span class="hljs-built_in">Date</span>;
}

<span class="hljs-comment">// Define the schema for the file model</span>
<span class="hljs-keyword">const</span> fileSchema = <span class="hljs-keyword">new</span> Schema&lt;IFile&gt;(
  {
    _id: { <span class="hljs-keyword">type</span>: Schema.Types.ObjectId, auto: <span class="hljs-literal">true</span> }, <span class="hljs-comment">// MongoDB default _id</span>
    name: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> }, <span class="hljs-comment">// Removed unique constraint</span>
    content: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> },
  },
  { timestamps: <span class="hljs-literal">true</span> } <span class="hljs-comment">// Automatically adds createdAt &amp; updatedAt</span>
);

<span class="hljs-comment">// Export the File model with type safety</span>
<span class="hljs-keyword">const</span> File: Model&lt;IFile&gt; =
  mongoose.models.File || mongoose.model&lt;IFile&gt;(<span class="hljs-string">"File"</span>, fileSchema);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> File;
</code></pre>
<h3 id="heading-code-explanation-1">Code Explanation</h3>
<p>This code defines a Mongoose schema and model for a <code>File</code> collection in a MongoDB database. It specifies the structure and rules for documents in the collection.</p>
<p>This code defines a Mongoose schema and model for a File document in MongoDB. Here's a brief explanation:</p>
<h3 id="heading-key-components-1"><strong>Key Components</strong>:</h3>
<ol>
<li><p><strong>Interface (</strong><code>IFile</code>):</p>
<ul>
<li><p>Defines the structure of a <code>File</code> document with:</p>
<ul>
<li><p><code>name</code> (string, required).</p>
</li>
<li><p><code>content</code> (string, required).</p>
</li>
<li><p><code>createdAt</code> and <code>updatedAt</code> (automatically managed by Mongoose).</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Schema (</strong><code>fileSchema</code>):</p>
<ul>
<li><p>Maps the <code>IFile</code> interface to a MongoDB schema.</p>
</li>
<li><p>Includes:</p>
<ul>
<li><p><code>_id</code>: Auto-generated MongoDB ObjectId.</p>
</li>
<li><p><code>name</code> and <code>content</code>: Required fields.</p>
</li>
<li><p><code>timestamps: true</code>: Automatically adds <code>createdAt</code> and <code>updatedAt</code> fields.</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p><strong>Model (</strong><code>File</code>):</p>
<ul>
<li><p>Creates or retrieves the Mongoose model for the <code>File</code> collection.</p>
</li>
<li><p>Ensures type safety using the <code>IFile</code> interface.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-connecting-to-database">Connecting to database</h3>
<p>Half of the work is done! Now, it’s time to connect our app to the database. To do this, I’ll create a new file inside the <code>lib</code> folder, where we previously created the database schema. I’ll name the file <code>mongodb.tsx</code> and paste the following code inside it:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> mongoose, { Schema, Model, Connection } <span class="hljs-keyword">from</span> <span class="hljs-string">"mongoose"</span>;
<span class="hljs-keyword">import</span> { IFile } <span class="hljs-keyword">from</span> <span class="hljs-string">"../types"</span>;

<span class="hljs-comment">// Define mongoose connection URI</span>
<span class="hljs-keyword">const</span> MONGODB_URI = process.env.MONGODB_URI;

<span class="hljs-keyword">if</span> (!MONGODB_URI) {
  <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(
    <span class="hljs-string">"Please define the MONGODB_URI environment variable inside .env.local"</span>
  );
}

<span class="hljs-keyword">let</span> cached: {
  conn: Connection | <span class="hljs-literal">null</span>;
  promise: <span class="hljs-built_in">Promise</span>&lt;Connection&gt; | <span class="hljs-literal">null</span>;
} = { conn: <span class="hljs-literal">null</span>, promise: <span class="hljs-literal">null</span> };

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">connectDB</span>(<span class="hljs-params"></span>): <span class="hljs-title">Promise</span>&lt;<span class="hljs-title">Connection</span>&gt; </span>{
  <span class="hljs-keyword">if</span> (cached.conn) {
    <span class="hljs-keyword">return</span> cached.conn;
  }

  <span class="hljs-keyword">if</span> (!cached.promise) {
    cached.promise = mongoose.connect(MONGODB_URI!).then(<span class="hljs-function">(<span class="hljs-params">mongoose</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> mongoose.connection;
    });
  }

  <span class="hljs-keyword">try</span> {
    cached.conn = <span class="hljs-keyword">await</span> cached.promise;
  } <span class="hljs-keyword">catch</span> (e) {
    cached.promise = <span class="hljs-literal">null</span>;
    <span class="hljs-keyword">throw</span> e;
  }

  <span class="hljs-keyword">return</span> cached.conn;
}

<span class="hljs-comment">// Define the schema for the file model</span>
<span class="hljs-keyword">const</span> fileSchema = <span class="hljs-keyword">new</span> Schema&lt;IFile&gt;(
  {
    name: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> },
    content: { <span class="hljs-keyword">type</span>: <span class="hljs-built_in">String</span>, required: <span class="hljs-literal">true</span> },
  },
  {
    timestamps: <span class="hljs-literal">true</span>,
  }
);

<span class="hljs-comment">// Export the File model with type safety</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> File = (mongoose.models.File ||
mongoose.model&lt;IFile&gt;(<span class="hljs-string">"File"</span>, fileSchema)) <span class="hljs-keyword">as</span> Model&lt;IFile&gt;;
</code></pre>
<h3 id="heading-code-explanation-2">Code Explanation</h3>
<p>This code sets up a MongoDB connection using the <code>mongoose</code> library in a Node.js or Next.js application. It ensures that the database is connected efficiently and prevents redundant connections.</p>
<p>This code sets up a MongoDB connection and defines a Mongoose schema and model for a <code>File</code> document. Here's a brief explanation:</p>
<h3 id="heading-key-components-2"><strong>Key Components</strong>:</h3>
<ol>
<li><p><strong>MongoDB Connection</strong>:</p>
<ul>
<li><p>Uses the <code>MONGODB_URI</code> environment variable to connect to MongoDB.</p>
</li>
<li><p>Implements a caching mechanism to reuse the connection and avoid multiple connections.</p>
</li>
<li><p>Throws an error if <code>MONGODB_URI</code> is not defined.</p>
</li>
</ul>
</li>
<li><p><strong>Schema (</strong><code>fileSchema</code>):</p>
<ul>
<li><p>Defines the structure of a <code>File</code> document with:</p>
<ul>
<li><p><code>name</code> (string, required).</p>
</li>
<li><p><code>content</code> (string, required).</p>
</li>
</ul>
</li>
<li><p>Automatically adds <code>createdAt</code> and <code>updatedAt</code> timestamps.</p>
</li>
</ul>
</li>
<li><p><strong>Model (</strong><code>File</code>):</p>
<ul>
<li><p>Creates or retrieves the Mongoose model for the <code>File</code> collection.</p>
</li>
<li><p>Ensures type safety using the <code>IFile</code> interface.</p>
</li>
</ul>
</li>
</ol>
<h4 id="heading-example-directory-structure">Example Directory Structure:</h4>
<pre><code class="lang-javascript">/project
  /pages
    /api
      test.js
  /utils
    connectDB.js
  .env.local
</code></pre>
<h3 id="heading-final-notes">Final Notes:</h3>
<ul>
<li><p>This code avoids multiple MongoDB connections by checking the <code>readyState</code>.</p>
</li>
<li><p>It's reusable and modular, making it easy to maintain.</p>
</li>
<li><p>Always secure the <code>MONGODB_URI</code> in-environment variables to avoid exposing sensitive credentials.</p>
</li>
</ul>
<h3 id="heading-ensuring-type-safety">Ensuring type safety</h3>
<p>Since we are using TypeScript, we will have to declare the file type <code>files</code> , <code>socket</code> and an <code>index</code>. To do so, create a new folder in root directory of the project and name it <code>types</code> and make three files <code>socket.ts</code> ,<code>files.ts</code> and <code>index.ts</code> inside the folder. Inside each file, paste the given code for their respective file.</p>
<pre><code class="lang-typescript"><span class="hljs-comment">//index.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> IFile {
  _id: <span class="hljs-built_in">string</span>;
  name: <span class="hljs-built_in">string</span>;
  content: <span class="hljs-built_in">string</span>;
  createdAt?: <span class="hljs-built_in">Date</span>;
  updatedAt?: <span class="hljs-built_in">Date</span>;
}
<span class="hljs-comment">//socket.ts</span>
<span class="hljs-keyword">import</span> { FileData } <span class="hljs-keyword">from</span> <span class="hljs-string">'../types/file'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ServerToClientEvents {
<span class="hljs-string">"new-file"</span>: <span class="hljs-function">(<span class="hljs-params">file: FileData</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
<span class="hljs-string">"delete-file"</span>: <span class="hljs-function">(<span class="hljs-params">fileId: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
<span class="hljs-string">"file-update"</span>: <span class="hljs-function">(<span class="hljs-params">data: { fileId: <span class="hljs-built_in">string</span>; content: <span class="hljs-built_in">string</span> }</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> ClientToServerEvents {
<span class="hljs-string">"new-file"</span>: <span class="hljs-function">(<span class="hljs-params">file: FileData</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
<span class="hljs-string">"delete-file"</span>: <span class="hljs-function">(<span class="hljs-params">fileId: <span class="hljs-built_in">string</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
<span class="hljs-string">"file-update"</span>: <span class="hljs-function">(<span class="hljs-params">data: { fileId: <span class="hljs-built_in">string</span>; content: <span class="hljs-built_in">string</span> }</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}
<span class="hljs-comment">//file.ts</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> FileData {
_id: <span class="hljs-built_in">string</span>;
name: <span class="hljs-built_in">string</span>;
content: <span class="hljs-built_in">string</span>;
}
</code></pre>
<h2 id="heading-how-to-build-the-front-end"><strong>How to Build the Front End</strong></h2>
<p>For the front end, we’ll keep it simple, aiming for a UI that closely resembles Replit. The key components we need for this project are a File Explorer, Monaco Editor, and Sandbox component.</p>
<ul>
<li><p><strong>File Explorer</strong>: This component will display and manage the code files, positioned on the left side of the screen.</p>
</li>
<li><p><strong>Monaco Editor</strong>: This component will allow users to view and edit the content of the code files.</p>
</li>
<li><p><strong>Sandbox</strong>: This component will render the live preview of the content inside the code files.</p>
</li>
</ul>
<p>To build these components, we won’t use any third-party UI libraries; instead, we’ll rely solely on TailwindCSS, which is pre-configured with Next.js.</p>
<p>Now, let’s build the components:</p>
<ol>
<li><p>Open your VS Code.</p>
</li>
<li><p>Open the Next.js folder where you created your project.</p>
<p> Since I work without a <code>src</code> folder, you’ll find only an <code>app</code> folder. Inside the <code>app</code> folder, create a new folder called components.</p>
</li>
</ol>
<ul>
<li><p>After creating the folder, your project structure should look something like this:</p>
</li>
<li><p><strong>FileExplorer.js -</strong>This is our file explorer</p>
</li>
<li><p><strong>ScreenOne.js-</strong> This is our Monaco editor</p>
</li>
<li><p><strong>LivePreview.js-</strong> This is our sandbox component</p>
</li>
</ul>
<p>Let’s see how I build these components and you can too,</p>
<h3 id="heading-fileexploertsx">FileExploer.tsx</h3>
<p>The <code>FileExplorer</code> is a React component that displays a list of files fetched from a backend (MongoDB) and allows users to select, create, edit, and delete files. It uses React Hooks for state management and lifecycle effects, Tailwind CSS for styling, and <code>lucide-react</code> icons for UI actions.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Plus, Trash2, Pencil } <span class="hljs-keyword">from</span> <span class="hljs-string">"lucide-react"</span>;
<span class="hljs-keyword">import</span> io, { Socket } <span class="hljs-keyword">from</span> <span class="hljs-string">"socket.io-client"</span>;
<span class="hljs-keyword">import</span> { FileData } <span class="hljs-keyword">from</span> <span class="hljs-string">'../types/file'</span>;

<span class="hljs-keyword">interface</span> FileExplorerProps {
files: FileData[];
onFileSelect: <span class="hljs-function">(<span class="hljs-params">file: FileData</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
currentFile: FileData | <span class="hljs-literal">null</span>;
}

<span class="hljs-keyword">const</span> FileExplorer: React.FC&lt;FileExplorerProps&gt; = <span class="hljs-function">(<span class="hljs-params">{
files: initialFiles,
onFileSelect,
currentFile,
}</span>) =&gt;</span> {
<span class="hljs-keyword">const</span> [files, setFiles] = useState&lt;FileData[]&gt;(initialFiles);
  <span class="hljs-keyword">const</span> [socket, setSocket] = useState&lt;Socket | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">true</span>);
  <span class="hljs-keyword">const</span> [error, setError] = useState&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [newFileName, setNewFileName] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [editingFile, setEditingFile] = useState&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [editedFileName, setEditedFileName] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);

  <span class="hljs-comment">// Initialize socket connection</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> socketInstance = io(<span class="hljs-string">"http://localhost:3000"</span>, {
      reconnection: <span class="hljs-literal">true</span>,
      reconnectionAttempts: <span class="hljs-number">5</span>,
      reconnectionDelay: <span class="hljs-number">1000</span>,
    });

    socketInstance.on(<span class="hljs-string">"connect"</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Connected to Socket.IO server"</span>);
    });

    socketInstance.on(<span class="hljs-string">"connect_error"</span>, <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Socket connection error:"</span>, error);
    });

    socketInstance.on(<span class="hljs-string">"disconnect"</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Disconnected from Socket.IO server"</span>);
    });

    setSocket(socketInstance);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">if</span> (socketInstance) {
        socketInstance.disconnect();
      }
    };
  }, []);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!socket) <span class="hljs-keyword">return</span>;

    <span class="hljs-comment">// Listen for real-time updates</span>
    socket.on(<span class="hljs-string">"new-file"</span>, <span class="hljs-function">(<span class="hljs-params">newFile: FileData</span>) =&gt;</span> {
      setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (!prevFiles.some(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> file._id === newFile._id)) {
          <span class="hljs-keyword">return</span> [...prevFiles, newFile];
        }
        <span class="hljs-keyword">return</span> prevFiles;
      });
    });

    socket.on(<span class="hljs-string">"delete-file"</span>, <span class="hljs-function">(<span class="hljs-params">fileId: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
      setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span> prevFiles.filter(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> file._id !== fileId));
    });

    socket.on(<span class="hljs-string">"update-file"</span>, <span class="hljs-function">(<span class="hljs-params">updatedFile: FileData</span>) =&gt;</span> {
      setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span>
        prevFiles.map(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span>
          file._id === updatedFile._id
            ? { ...file, name: updatedFile.name }
            : file
        )
      );
    });

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      socket.off(<span class="hljs-string">"new-file"</span>);
      socket.off(<span class="hljs-string">"delete-file"</span>);
      socket.off(<span class="hljs-string">"update-file"</span>);
    };
  }, [socket]);

  <span class="hljs-comment">// Fetch initial files</span>
  <span class="hljs-keyword">const</span> fetchFiles = <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">"/api/files"</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">"Failed to fetch files"</span>);
    <span class="hljs-keyword">const</span> data: FileData[] = <span class="hljs-keyword">await</span> response.json();
      setFiles(data);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error fetching files:"</span>, error);
      setError(<span class="hljs-string">"Failed to load files"</span>);
    } <span class="hljs-keyword">finally</span> {
      setLoading(<span class="hljs-literal">false</span>);
    }
  };

  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetchFiles();
  }, []);

  <span class="hljs-comment">// Create new file</span>
  <span class="hljs-keyword">const</span> createNewFile = <span class="hljs-keyword">async</span> () =&gt; {
    <span class="hljs-keyword">if</span> (!newFileName.trim()) <span class="hljs-keyword">return</span>;
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/files"</span>, {
        method: <span class="hljs-string">"POST"</span>,
        headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
        body: <span class="hljs-built_in">JSON</span>.stringify({ name: newFileName }),
      });

      <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 create file"</span>);

    <span class="hljs-keyword">const</span> newFile: FileData = <span class="hljs-keyword">await</span> response.json();
      socket?.emit(<span class="hljs-string">"new-file"</span>, newFile);
      setNewFileName(<span class="hljs-string">""</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error creating file:"</span>, error);
    }
  };

  <span class="hljs-keyword">const</span> handleDeleteFile = <span class="hljs-keyword">async</span> (e: React.MouseEvent, id: <span class="hljs-built_in">string</span>) =&gt; {
    e.stopPropagation();
    setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span> prevFiles.filter(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> file._id !== id)); <span class="hljs-comment">// Optimistic update</span>

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/files"</span>, {
        method: <span class="hljs-string">"DELETE"</span>,
        headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
        body: <span class="hljs-built_in">JSON</span>.stringify({ id }),
      });

      <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 delete file"</span>);

      socket?.emit(<span class="hljs-string">"delete-file"</span>, id);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error deleting file:"</span>, error);
      <span class="hljs-keyword">await</span> fetchFiles(); <span class="hljs-comment">// Revert state if API call fails</span>
    }
  };

<span class="hljs-keyword">const</span> handleEditStart = <span class="hljs-function">(<span class="hljs-params">e: React.MouseEvent, file: FileData</span>) =&gt;</span> {
    e.stopPropagation();
    setEditingFile(file._id);
    setEditedFileName(file.name);
  };

  <span class="hljs-keyword">const</span> handleEditSave = <span class="hljs-keyword">async</span> (
    e: React.FocusEvent | React.KeyboardEvent,
    id: <span class="hljs-built_in">string</span>
  ) =&gt; {
    e.preventDefault();
    <span class="hljs-keyword">if</span> (!editedFileName.trim()) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">const</span> previousFiles = [...files];
    setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span>
      prevFiles.map(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span>
        file._id === id ? { ...file, name: editedFileName } : file
      )
    ); <span class="hljs-comment">// Optimistic update</span>

    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/files"</span>, {
        method: <span class="hljs-string">"PUT"</span>,
        headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
        body: <span class="hljs-built_in">JSON</span>.stringify({ id, name: editedFileName }),
      });

      <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 update file"</span>);

    <span class="hljs-keyword">const</span> updatedFile: FileData = <span class="hljs-keyword">await</span> response.json();
      socket?.emit(<span class="hljs-string">"update-file"</span>, updatedFile);
      setEditingFile(<span class="hljs-literal">null</span>);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error updating file:"</span>, error);
      setFiles(previousFiles); <span class="hljs-comment">// Revert state if API call fails</span>
    }
  };

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"w-64 bg-gray-900 p-4 h-full text-white rounded-lg shadow-lg flex flex-col"</span>&gt;
      &lt;h2 className=<span class="hljs-string">"text-lg font-semibold mb-4"</span>&gt;Files&lt;/h2&gt;

      {loading ? (
        &lt;div className=<span class="hljs-string">"text-gray-400 text-sm"</span>&gt;Loading files...&lt;/div&gt;
      ) : error ? (
        &lt;div className=<span class="hljs-string">"text-red-500 text-sm"</span>&gt;{error}&lt;/div&gt;
      ) : files.length === <span class="hljs-number">0</span> ? (
        &lt;div className=<span class="hljs-string">"text-gray-400 text-sm"</span>&gt;No files yet&lt;/div&gt;
      ) : (
        &lt;div className=<span class="hljs-string">"space-y-2 overflow-y-auto flex-grow"</span>&gt;
          {files.map(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> (
            &lt;div
              key={file._id}
              className={<span class="hljs-string">`cursor-pointer flex justify-between items-center p-2 rounded text-white transition-all duration-200 <span class="hljs-subst">${
                currentFile?._id === file._id
                  ? <span class="hljs-string">"bg-blue-600"</span>
                  : <span class="hljs-string">"hover:bg-gray-700"</span>
              }</span>`</span>}
              onClick={<span class="hljs-function">() =&gt;</span> onFileSelect(file)}
            &gt;
              {editingFile === file._id ? (
                &lt;input
                  <span class="hljs-keyword">type</span>=<span class="hljs-string">"text"</span>
                  value={editedFileName}
                onChange={<span class="hljs-function">(<span class="hljs-params">e: React.ChangeEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> setEditedFileName(e.target.value)}
                onBlur={<span class="hljs-function">(<span class="hljs-params">e: React.FocusEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span> handleEditSave(e, file._id)}
                onKeyDown={<span class="hljs-function">(<span class="hljs-params">e: React.KeyboardEvent&lt;HTMLInputElement&gt;</span>) =&gt;</span>
                e.key === <span class="hljs-string">"Enter"</span> ? handleEditSave(e, file._id) : <span class="hljs-literal">null</span>
                }
                  autoFocus
                  className=<span class="hljs-string">"bg-gray-800 text-white p-1 rounded outline-none w-32"</span>
                /&gt;
              ) : (
                &lt;span className=<span class="hljs-string">"truncate flex-grow"</span>&gt;📄 {file.name}&lt;/span&gt;
              )}

              &lt;div className=<span class="hljs-string">"flex items-center gap-2"</span>&gt;
                &lt;button
                onClick={<span class="hljs-function">(<span class="hljs-params">e: React.MouseEvent</span>) =&gt;</span> handleEditStart(e, file)}
                  className=<span class="hljs-string">"text-yellow-400 hover:text-yellow-600 p-1 rounded"</span>
                &gt;
                  &lt;Pencil size={<span class="hljs-number">16</span>} /&gt;
                &lt;/button&gt;
                &lt;button
                onClick={<span class="hljs-function">(<span class="hljs-params">e: React.MouseEvent</span>) =&gt;</span> handleDeleteFile(e, file._id)}
                  className=<span class="hljs-string">"text-red-400 hover:text-red-600 p-1 rounded"</span>
                &gt;
                  &lt;Trash2 size={<span class="hljs-number">16</span>} /&gt;
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          ))}
        &lt;/div&gt;
      )}


    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> FileExplorer;
</code></pre>
<h3 id="heading-code-explanation-3"><strong>Code Explanation</strong></h3>
<h3 id="heading-brief-explanation-of-the-code">Brief Explanation of the Code</h3>
<p>The <code>FileExplorer</code> component is a React component designed to manage and display a list of files in real time. It uses <a target="_blank" href="http://Socket.IO"><strong>Socket.IO</strong></a> for real-time updates, allowing users to add, delete, and edit files dynamically.</p>
<h3 id="heading-key-features-2">Key Features:</h3>
<h4 id="heading-1-state-management">1. <strong>State Management</strong></h4>
<p>The component uses React's <code>useState</code> Hook to manage the following states:</p>
<ul>
<li><p><code>files</code>: List of files displayed in the File Explorer.</p>
</li>
<li><p><code>socket</code>: The active <a target="_blank" href="http://Socket.IO">Socket.IO</a> connection.</p>
</li>
<li><p><code>loading</code> &amp; <code>error</code>: Manage the state of file loading.</p>
</li>
<li><p><code>newFileName</code>, <code>editingFile</code>, and <code>editedFileName</code>: Manage file creation and editing processes.</p>
</li>
</ul>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> [files, setFiles] = useState([]);
<span class="hljs-keyword">const</span> [socket, setSocket] = useState(<span class="hljs-literal">null</span>);
<span class="hljs-keyword">const</span> [loading, setLoading] = useState(<span class="hljs-literal">true</span>);
<span class="hljs-keyword">const</span> [error, setError] = useState(<span class="hljs-literal">null</span>);
<span class="hljs-keyword">const</span> [newFileName, setNewFileName] = useState(<span class="hljs-string">""</span>);
<span class="hljs-keyword">const</span> [editingFile, setEditingFile] = useState(<span class="hljs-literal">null</span>);
<span class="hljs-keyword">const</span> [editedFileName, setEditedFileName] = useState(<span class="hljs-string">""</span>);
</code></pre>
<h4 id="heading-2-socketiohttpsocketio-integration">2. <a target="_blank" href="http://Socket.IO"><strong>Socket.IO</strong></a> <strong>Integration</strong></h4>
<ul>
<li><p>Establishes a real-time connection to a <a target="_blank" href="http://Socket.IO">Socket.IO</a> server.</p>
</li>
<li><p>Listens for events like <code>new-file</code>, <code>delete-file</code>, and <code>update-file</code> to update the file list dynamically.</p>
</li>
<li><p>Cleans up the socket connection when the component unmounts.</p>
</li>
</ul>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> socketInstance = io(<span class="hljs-string">"http://localhost:3000"</span>, {
    <span class="hljs-attr">reconnection</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">reconnectionAttempts</span>: <span class="hljs-number">5</span>,
    <span class="hljs-attr">reconnectionDelay</span>: <span class="hljs-number">1000</span>,
  });

  socketInstance.on(<span class="hljs-string">"connect"</span>, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Connected to Socket.IO server"</span>);
  });

  setSocket(socketInstance);

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (socketInstance) socketInstance.disconnect();
  };
}, []);
</code></pre>
<h4 id="heading-3-real-time-file-updates">3. <strong>Real-Time File Updates</strong></h4>
<p>Handles real-time events for creating, deleting, and updating files.</p>
<pre><code class="lang-javascript">useEffect(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">if</span> (!socket) <span class="hljs-keyword">return</span>;

  socket.on(<span class="hljs-string">"new-file"</span>, <span class="hljs-function">(<span class="hljs-params">newFile</span>) =&gt;</span> {
    setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span>
      prevFiles.some(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> file._id === newFile._id)
        ? prevFiles
        : [...prevFiles, newFile]
    );
  });

  socket.on(<span class="hljs-string">"delete-file"</span>, <span class="hljs-function">(<span class="hljs-params">fileId</span>) =&gt;</span> {
    setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span> prevFiles.filter(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> file._id !== fileId));
  });

  socket.on(<span class="hljs-string">"update-file"</span>, <span class="hljs-function">(<span class="hljs-params">updatedFile</span>) =&gt;</span> {
    setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span>
      prevFiles.map(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span>
        file._id === updatedFile._id
          ? { ...file, <span class="hljs-attr">name</span>: updatedFile.name }
          : file
      )
    );
  });

  <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
    socket.off(<span class="hljs-string">"new-file"</span>);
    socket.off(<span class="hljs-string">"delete-file"</span>);
    socket.off(<span class="hljs-string">"update-file"</span>);
  };
}, [socket]);
</code></pre>
<h4 id="heading-4-crud-operations">4. <strong>CRUD Operations</strong></h4>
<ul>
<li><p><strong>Fetch Files</strong>: Retrieves the initial list of files from the server.</p>
</li>
<li><p><strong>Create File</strong>: Sends a POST request to add a new file.</p>
</li>
<li><p><strong>Delete File</strong>: Sends a DELETE request to remove a file and updates the UI optimistically.</p>
</li>
<li><p><strong>Edit File</strong>: Sends a PUT request to update a file's name and performs optimistic updates.</p>
<p>  This code defines a FileExplorer React component that allows users to manage and interact with files in a file system.</p>
<p>  <strong>Note: All the operations on the application get saved in real-time for the demo you can refresh the page to see the changes don’t get removed.</strong></p>
</li>
</ul>
<h3 id="heading-screenonetsx"><strong>ScreenOne.tsx</strong></h3>
<p>The <code>ScreenOne</code> component is a code editor panel that dynamically displays and updates code for a selected file. It integrates the Monaco Editor to highlight syntax based on the file type (for example, JavaScript, HTML, CSS).</p>
<p>The component shows the selected file's name, allows users to edit its content, and sends updates back to the database in real time. It also offers a clean, user-friendly interface with a dark theme and configurable editor options. This is ideal for coding environments like IDEs or code playgrounds.</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { Editor } <span class="hljs-keyword">from</span> <span class="hljs-string">"@monaco-editor/react"</span>;

<span class="hljs-keyword">interface</span> File {
  name: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">interface</span> ScreenOneProps {
  selectedFile: File | <span class="hljs-literal">null</span>;
  code: <span class="hljs-built_in">string</span>;
  onChange: <span class="hljs-function">(<span class="hljs-params">newCode: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}

<span class="hljs-keyword">const</span> ScreenOne: React.FC&lt;ScreenOneProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ selectedFile, code, onChange }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [language, setLanguage] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">"javascript"</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!selectedFile) <span class="hljs-keyword">return</span>;
    setLanguage(getLanguageForFile(selectedFile.name));
  }, [selectedFile]);

  <span class="hljs-keyword">const</span> getLanguageForFile = (filename: <span class="hljs-built_in">string</span>): <span class="hljs-function"><span class="hljs-params">string</span> =&gt;</span> {
    <span class="hljs-keyword">const</span> extension = filename.split(<span class="hljs-string">"."</span>).pop()?.toLowerCase();
    <span class="hljs-keyword">switch</span> (extension) {
      <span class="hljs-keyword">case</span> <span class="hljs-string">"js"</span>:
      <span class="hljs-keyword">case</span> <span class="hljs-string">"jsx"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"javascript"</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">"html"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"html"</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">"css"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"css"</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-string">"json"</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"json"</span>;
      <span class="hljs-keyword">default</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-string">"plaintext"</span>;
    }
  };

  <span class="hljs-keyword">const</span> handleCodeChange = (newCode: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span>): <span class="hljs-function"><span class="hljs-params">void</span> =&gt;</span> {
    onChange(newCode); <span class="hljs-comment">// Call the onChange prop to update the code in LivePreview</span>
  };

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"flex-1 flex flex-col bg-[#2d2d2d] text-white p-6 rounded-lg shadow-md"</span>&gt;
      &lt;div className=<span class="hljs-string">"flex items-center justify-between mb-4"</span>&gt;
        &lt;h2 className=<span class="hljs-string">"text-2xl font-semibold"</span>&gt;Code Editor&lt;/h2&gt;
        &lt;div className=<span class="hljs-string">"text-sm text-gray-400"</span>&gt;
          {selectedFile ? selectedFile.name : <span class="hljs-string">"No file selected"</span>}
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div className=<span class="hljs-string">"flex-grow bg-gray-800 rounded-lg shadow-inner"</span>&gt;
        &lt;Editor
          height=<span class="hljs-string">"calc(100vh - 160px)"</span>
          language={language}
          value={code}
          onChange={handleCodeChange} <span class="hljs-comment">// Update the code on change</span>
          theme=<span class="hljs-string">"vs-dark"</span>
          options={{
            minimap: { enabled: <span class="hljs-literal">false</span> },
            lineNumbers: <span class="hljs-string">"on"</span>,
            wordWrap: <span class="hljs-string">"on"</span>,
            scrollBeyondLastLine: <span class="hljs-literal">false</span>,
          }}
        /&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ScreenOne;
</code></pre>
<h3 id="heading-code-explanation-4"><strong>Code Explanation</strong></h3>
<h3 id="heading-explanation-of-the-code">Explanation of the Code:</h3>
<p>This React component, <code>ScreenOne</code>, represents a code editor screen where users can edit files in a specified programming language. The editor dynamically adjusts its syntax highlighting based on the selected file's type. It uses the <code>@monaco-editor/react</code> library for the editor interface.</p>
<p>This code defines a ScreenOne React component, which is a code editor using the Monaco Editor (used in VS Code). Here's a brief explanation:</p>
<h3 id="heading-key-features-3"><strong>Key Features</strong>:</h3>
<ol>
<li><p><strong>Code Editor</strong>:</p>
<ul>
<li><p>Uses the <code>@monaco-editor/react</code> library to render a code editor.</p>
</li>
<li><p>Supports syntax highlighting for various languages (for example, JavaScript, HTML, CSS, JSON).</p>
</li>
</ul>
</li>
<li><p><strong>Dynamic Language Detection</strong>:</p>
<ul>
<li><p>Detects the programming language based on the selected file's extension (for example, <code>.js</code> → JavaScript).</p>
</li>
<li><p>Defaults to <code>plaintext</code> for unsupported file types.</p>
</li>
</ul>
</li>
<li><p><strong>Props</strong>:</p>
<ul>
<li><p><code>selectedFile</code>: The currently selected file (contains the file name).</p>
</li>
<li><p><code>code</code>: The current code content to display in the editor.</p>
</li>
<li><p><code>onChange</code>: A callback function to handle code changes.</p>
</li>
</ul>
</li>
<li><p><strong>UI</strong>:</p>
<ul>
<li><p>Displays the file name and a title ("Code Editor").</p>
</li>
<li><p>Styled with Tailwind CSS for a dark theme and rounded corners.</p>
</li>
</ul>
</li>
<li><p><strong>Editor Configuration</strong>:</p>
<ul>
<li><p>Uses the <code>vs-dark</code> theme.</p>
</li>
<li><p>Disables the mini-map and enables line numbers and word wrap.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-livepreviewtsx"><strong>LivePreview.tsx</strong></h3>
<p>The <code>LivePreview</code> component dynamically generates a live code preview for either static projects (HTML, CSS, JS) or React-based projects. It detects the type of project, and sets up the required files (for example, <code>index.html</code>, <code>App.js</code>), and renders a real-time preview using CodeSandbox's Sandpack. The preview adapts to the selected file and updates as the code changes, providing a seamless coding experience.</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> React, { useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {
  SandpackProvider,
  SandpackLayout,
  SandpackCodeEditor,
  SandpackPreview,
  SandpackThemeProvider,
} <span class="hljs-keyword">from</span> <span class="hljs-string">"@codesandbox/sandpack-react"</span>;

<span class="hljs-keyword">interface</span> FileAccumulator {
[key: <span class="hljs-built_in">string</span>]: { code: <span class="hljs-built_in">string</span> };
}

<span class="hljs-keyword">interface</span> File {
  name: <span class="hljs-built_in">string</span>;
  content: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">interface</span> LivePreviewProps {
files: File[];
currentFile: File | <span class="hljs-literal">null</span>;
code: <span class="hljs-built_in">string</span>;
onCodeChange?: <span class="hljs-function">(<span class="hljs-params">value: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>;
}
<span class="hljs-keyword">interface</span> SandpackFile {
  code: <span class="hljs-built_in">string</span>;
}

<span class="hljs-keyword">interface</span> SandpackFiles {
  [key: <span class="hljs-built_in">string</span>]: SandpackFile;
}

<span class="hljs-keyword">const</span> LivePreview: React.FC&lt;LivePreviewProps&gt; = <span class="hljs-function">(<span class="hljs-params">{ files, currentFile, code }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> [sandboxFiles, setSandboxFiles] = useState&lt;Record&lt;<span class="hljs-built_in">string</span>, { code: <span class="hljs-built_in">string</span> }&gt;&gt;({});
  <span class="hljs-keyword">const</span> [template, setTemplate] = useState&lt;<span class="hljs-string">"vanilla"</span> | <span class="hljs-string">"react"</span>&gt;(<span class="hljs-string">"vanilla"</span>); <span class="hljs-comment">// Default to static</span>
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">true</span>);

  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">if</span> (!currentFile || !files) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">const</span> newFiles: SandpackFiles = files.reduce(<span class="hljs-function">(<span class="hljs-params">acc: FileAccumulator, file</span>) =&gt;</span> {
    acc[<span class="hljs-string">`/<span class="hljs-subst">${file.name}</span>`</span>] = { code: file.content };
    <span class="hljs-keyword">return</span> acc;
    }, {});

    <span class="hljs-comment">// Check if the project is React-based</span>
    <span class="hljs-keyword">const</span> isReactProject = files.some(
      <span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span>
        file.name.endsWith(<span class="hljs-string">".jsx"</span>) ||
        (file.name.endsWith(<span class="hljs-string">".js"</span>) &amp;&amp; file.content.includes(<span class="hljs-string">"React"</span>))
    );

    <span class="hljs-keyword">if</span> (isReactProject) {
      setTemplate(<span class="hljs-string">"react"</span>);

      <span class="hljs-comment">// Ensure App.js exists</span>
      <span class="hljs-keyword">if</span> (!newFiles[<span class="hljs-string">"/App.js"</span>]) {
        newFiles[<span class="hljs-string">"/App.js"</span>] = {
          code: <span class="hljs-string">`
            import React from "react";
            function App() { return &lt;h1&gt;Hello, React!&lt;/h1&gt;; }
            export default App;
          `</span>,
        };
      }

      <span class="hljs-comment">// Ensure index.js exists</span>
      newFiles[<span class="hljs-string">"/index.js"</span>] = {
        code: <span class="hljs-string">`
          import React from "react";
          import ReactDOM from "react-dom/client";
          import App from "./App";

          const root = document.getElementById("root");
          if (root) {
            ReactDOM.createRoot(root).render(&lt;App /&gt;);
          } else {
            console.error("Root element not found!");
          }
        `</span>,
      };

      <span class="hljs-comment">// Ensure index.html exists</span>
      newFiles[<span class="hljs-string">"/index.html"</span>] = {
        code: <span class="hljs-string">`
          &lt;!DOCTYPE html&gt;
          &lt;html lang="en"&gt;
          &lt;head&gt;
            &lt;meta charset="UTF-8"&gt;
            &lt;meta name="viewport" content="width=device-width, initial-scale=1.0"&gt;
            &lt;title&gt;React App&lt;/title&gt;
          &lt;/head&gt;
          &lt;body&gt;
            &lt;div id="root"&gt;&lt;/div&gt;
          &lt;/body&gt;
          &lt;/html&gt;
        `</span>,
      };

      <span class="hljs-comment">// Ensure package.json exists</span>
      newFiles[<span class="hljs-string">"/package.json"</span>] = {
        code: <span class="hljs-built_in">JSON</span>.stringify(
          {
            main: <span class="hljs-string">"/index.js"</span>,
            dependencies: {
              react: <span class="hljs-string">"18.2.0"</span>,
              <span class="hljs-string">"react-dom"</span>: <span class="hljs-string">"18.2.0"</span>,
            },
          },
          <span class="hljs-literal">null</span>,
          <span class="hljs-number">2</span>
        ),
      };
    } <span class="hljs-keyword">else</span> {
      setTemplate(<span class="hljs-string">"vanilla"</span>);

      <span class="hljs-keyword">const</span> htmlFile = files.find(<span class="hljs-function">(<span class="hljs-params">f</span>) =&gt;</span> f.name.endsWith(<span class="hljs-string">".html"</span>));
      <span class="hljs-keyword">if</span> (htmlFile) {
        <span class="hljs-keyword">let</span> htmlContent = htmlFile.content;

        <span class="hljs-comment">// Inject CSS files</span>
        files
          .filter(<span class="hljs-function">(<span class="hljs-params">f</span>) =&gt;</span> f.name.endsWith(<span class="hljs-string">".css"</span>))
          .forEach(<span class="hljs-function">(<span class="hljs-params">cssFile</span>) =&gt;</span> {
            htmlContent = htmlContent.replace(
              <span class="hljs-string">"&lt;/head&gt;"</span>,
              <span class="hljs-string">`&lt;link rel="stylesheet" href="<span class="hljs-subst">${cssFile.name}</span>"&gt;&lt;/head&gt;`</span>
            );
          });

        <span class="hljs-comment">// Inject JS files</span>
        files
          .filter(<span class="hljs-function">(<span class="hljs-params">f</span>) =&gt;</span> f.name.endsWith(<span class="hljs-string">".js"</span>))
          .forEach(<span class="hljs-function">(<span class="hljs-params">jsFile</span>) =&gt;</span> {
            htmlContent = htmlContent.replace(
              <span class="hljs-string">"&lt;/body&gt;"</span>,
              <span class="hljs-string">`&lt;script src="<span class="hljs-subst">${jsFile.name}</span>"&gt;&lt;/script&gt;&lt;/body&gt;`</span>
            );
          });

        newFiles[<span class="hljs-string">"/index.html"</span>] = { code: htmlContent };
      }
    }

    <span class="hljs-comment">// Ensure the current file is included</span>
    newFiles[<span class="hljs-string">`/<span class="hljs-subst">${currentFile.name}</span>`</span>] = { code };
    setSandboxFiles(newFiles);
    setLoading(<span class="hljs-literal">false</span>);
  }, [files, currentFile, code]);

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"flex-1 bg-white border-l border-gray-300"</span>&gt;
      {loading ? (
        &lt;div className=<span class="hljs-string">"h-full flex items-center justify-center text-gray-500"</span>&gt;
          Loading...
        &lt;/div&gt;
      ) : currentFile ? (
        &lt;SandpackProvider
        template={template}
        files={sandboxFiles}
        options={{ activeFile: <span class="hljs-string">`/<span class="hljs-subst">${currentFile.name}</span>`</span> }}
        &gt;
        &lt;SandpackThemeProvider&gt;
            &lt;SandpackLayout&gt;
            &lt;SandpackCodeEditor showTabs={<span class="hljs-literal">false</span>}  /&gt;
            &lt;SandpackPreview style={{ height: <span class="hljs-string">"600px"</span>, border: <span class="hljs-string">"none"</span> }} /&gt;
            &lt;/SandpackLayout&gt;
        &lt;/SandpackThemeProvider&gt;
        &lt;/SandpackProvider&gt;
      ) : (
        &lt;div className=<span class="hljs-string">"h-full flex items-center justify-center text-gray-500"</span>&gt;
          Select a file to preview
        &lt;/div&gt;
      )}
    &lt;/div&gt;
  );
};

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> LivePreview;
</code></pre>
<h3 id="heading-code-explanation-5">Code Explanation</h3>
<h3 id="heading-explanation-of-the-code-1">Explanation of the Code:</h3>
<p>The <code>LivePreview</code> component provides a real-time live preview environment for static HTML/CSS/JS files or React-based projects. It uses the CodeSandbox Sandpack library to dynamically render code files in a browser-like preview window. The component automatically adjusts its behavior based on the file types and content to determine if the project is static or React-based.</p>
<p>This code defines a LivePreview React component that uses Sandpack (from CodeSandbox) to provide a live code editor and preview environment. Here's a brief explanation:</p>
<h3 id="heading-key-features-4"><strong>Key Features</strong>:</h3>
<ol>
<li><p><strong>Sandpack Integration</strong>:</p>
<ul>
<li><p>Uses <code>@codesandbox/sandpack-react</code> to render a code editor and live preview.</p>
</li>
<li><p>Supports both React and vanilla JavaScript/HTML/CSS projects.</p>
</li>
</ul>
</li>
<li><p><strong>Dynamic File Handling</strong>:</p>
<ul>
<li><p>Converts a list of files (<code>files</code> prop) into a format compatible with Sandpack.</p>
</li>
<li><p>Automatically detects if the project is React-based (e.g., contains <code>.jsx</code> or React imports).</p>
</li>
<li><p>Ensures necessary files (for example, <code>App.js</code>, <code>index.js</code>, <code>index.html</code>, <code>package.json</code>) exist for React projects.</p>
</li>
</ul>
</li>
<li><p><strong>Template Switching</strong>:</p>
<ul>
<li>Sets the Sandpack template to <code>"react"</code> for React projects or <code>"vanilla"</code> for static HTML/CSS/JS projects.</li>
</ul>
</li>
<li><p><strong>Code Injection</strong>:</p>
<ul>
<li>For vanilla projects, injects linked CSS and JS files into the HTML file.</li>
</ul>
</li>
<li><p><strong>Loading State</strong>:</p>
<ul>
<li>Displays a loading message while processing files.</li>
</ul>
</li>
<li><p><strong>UI</strong>:</p>
<ul>
<li><p>Shows a code editor and live preview side by side.</p>
</li>
<li><p>Displays a message if no file is selected.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-running-sockets-in-the-app">Running sockets in the app</h3>
<p>Now it is time to eat that big frog. As you know we are using sockets for real-time data communication so we need a <code>server.tsx</code> file in the root directory of the app (outside of the <code>src</code> folder) and paste this code:</p>
<pre><code class="lang-typescript">
<span class="hljs-keyword">import</span> { createServer, IncomingMessage, ServerResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"http"</span>;
<span class="hljs-keyword">import</span> { parse, UrlWithParsedQuery } <span class="hljs-keyword">from</span> <span class="hljs-string">"url"</span>;
<span class="hljs-keyword">import</span> next <span class="hljs-keyword">from</span> <span class="hljs-string">"next"</span>;
<span class="hljs-keyword">import</span> { Server, Socket } <span class="hljs-keyword">from</span> <span class="hljs-string">"socket.io"</span>;

<span class="hljs-keyword">const</span> dev: <span class="hljs-built_in">boolean</span> = process.env.NODE_ENV !== <span class="hljs-string">"production"</span>;
<span class="hljs-keyword">const</span> app = next({ dev });
<span class="hljs-keyword">const</span> handle = app.getRequestHandler();

app.prepare().then(<span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> httpServer = createServer(<span class="hljs-function">(<span class="hljs-params">req: IncomingMessage, res: ServerResponse</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> parsedUrl: UrlWithParsedQuery = parse(req.url || <span class="hljs-string">""</span>, <span class="hljs-literal">true</span>);
    handle(req, res, parsedUrl);
  });

  <span class="hljs-comment">// Determine the CORS origin dynamically</span>
  <span class="hljs-keyword">const</span> allowedOrigin: <span class="hljs-built_in">string</span> = dev
    ? <span class="hljs-string">"http://localhost:3000"</span> <span class="hljs-comment">// Local development</span>
    : <span class="hljs-string">"*"</span>; <span class="hljs-comment">// Vercel deployment</span>

  <span class="hljs-comment">// Initialize Socket.IO with dynamic CORS configuration</span>
  <span class="hljs-keyword">const</span> io = <span class="hljs-keyword">new</span> Server(httpServer, {
    cors: {
      origin: allowedOrigin,
      methods: [<span class="hljs-string">"GET"</span>, <span class="hljs-string">"POST"</span>],
      credentials: <span class="hljs-literal">true</span>,
    },
  });

  <span class="hljs-comment">// Socket.IO event handlers</span>
  io.on(<span class="hljs-string">"connection"</span>, <span class="hljs-function">(<span class="hljs-params">socket: Socket</span>) =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Client connected"</span>);

    <span class="hljs-comment">// Handle new file creation</span>
    socket.on(<span class="hljs-string">"new-file"</span>, <span class="hljs-function">(<span class="hljs-params">newFile: { id: <span class="hljs-built_in">string</span>; name: <span class="hljs-built_in">string</span>; content: <span class="hljs-built_in">string</span> }</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"New file created:"</span>, newFile);
      <span class="hljs-comment">// Broadcast to all clients except sender</span>
      socket.broadcast.emit(<span class="hljs-string">"new-file"</span>, newFile);
    });

    <span class="hljs-comment">// Handle file deletion</span>
    socket.on(<span class="hljs-string">"delete-file"</span>, <span class="hljs-function">(<span class="hljs-params">fileId: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"File deleted:"</span>, fileId);
      socket.broadcast.emit(<span class="hljs-string">"delete-file"</span>, fileId);
    });

    <span class="hljs-comment">// Handle file update</span>
    socket.on(<span class="hljs-string">"update-file"</span>, <span class="hljs-function">(<span class="hljs-params">updatedFile: { id: <span class="hljs-built_in">string</span>; name: <span class="hljs-built_in">string</span>; content: <span class="hljs-built_in">string</span> }</span>) =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"File updated:"</span>, updatedFile);
      socket.broadcast.emit(<span class="hljs-string">"update-file"</span>, updatedFile);
    });

    <span class="hljs-comment">// Handle client disconnect</span>
    socket.on(<span class="hljs-string">"disconnect"</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Client disconnected"</span>);
    });
  });

  <span class="hljs-comment">// Start the server on the specified port</span>
  <span class="hljs-keyword">const</span> PORT: <span class="hljs-built_in">number</span> | <span class="hljs-built_in">string</span> = process.env.PORT || <span class="hljs-number">3000</span>;
  httpServer.listen(PORT, <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`&gt; Ready on <span class="hljs-subst">${dev ? <span class="hljs-string">"http://localhost:3000"</span> : allowedOrigin}</span>`</span>);
  });
});
</code></pre>
<h3 id="heading-code-explanation-6">Code Explanation</h3>
<p>This code sets up a <strong>Next.js server</strong> with <a target="_blank" href="http://Socket.IO"><strong>Socket.IO</strong></a> for real-time communication. It:</p>
<ol>
<li><p>Initializes a Next.js app and prepares it to handle HTTP requests.</p>
</li>
<li><p>Configures CORS dynamically for <a target="_blank" href="http://Socket.IO">Socket.IO</a>, allowing connections from <a target="_blank" href="http://localhost:3000"><code>localhost:3000</code></a> in development or all origins in production.</p>
</li>
<li><p><strong>Sets up</strong> <a target="_blank" href="http://Socket.IO"><strong>Socket.IO</strong></a> to handle real-time events like:</p>
<ul>
<li><p>New file creation</p>
</li>
<li><p>File deletion</p>
</li>
<li><p>File updates</p>
</li>
<li><p>Client disconnection</p>
</li>
</ul>
</li>
<li><p>Broadcasts events to all connected clients except the sender.</p>
</li>
<li><p>Starts the server on a specified port (default: 3000).</p>
</li>
</ol>
<p>It’s a basic real-time server for file management with Next.js and <a target="_blank" href="http://Socket.IO">Socket.IO</a>.</p>
<h3 id="heading-clubbing-all-components-together">Clubbing all components together</h3>
<p>To do this we will have to tweak the <code>page.tsx</code>. Just copy the given code and paste it into the <code>page.js</code></p>
<pre><code class="lang-typescript">
<span class="hljs-string">"use client"</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> { useCopilotAction, useCopilotReadable } <span class="hljs-keyword">from</span> <span class="hljs-string">"@copilotkit/react-core"</span>;
<span class="hljs-keyword">import</span> { CopilotPopup } <span class="hljs-keyword">from</span> <span class="hljs-string">"@copilotkit/react-ui"</span>;
<span class="hljs-keyword">import</span> ScreenOne <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/ScreenOne"</span>;
<span class="hljs-keyword">import</span> FileExplorer <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/FileExplorer"</span>;
<span class="hljs-keyword">import</span> LivePreview <span class="hljs-keyword">from</span> <span class="hljs-string">"./components/LivePreview"</span>;
<span class="hljs-keyword">import</span> io, { Socket } <span class="hljs-keyword">from</span> <span class="hljs-string">"socket.io-client"</span>;

<span class="hljs-keyword">interface</span> File {
  _id: <span class="hljs-built_in">string</span>;
  name: <span class="hljs-built_in">string</span>;
  content: <span class="hljs-built_in">string</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Page</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [files, setFiles] = useState&lt;File[]&gt;([]);
  <span class="hljs-keyword">const</span> [currentFile, setCurrentFile] = useState&lt;File | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [code, setCode] = useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">"// Select or create a file"</span>);
  <span class="hljs-keyword">const</span> [socket, setSocket] = useState&lt;Socket | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
  <span class="hljs-keyword">const</span> [fileContents, setFileContents] = useState&lt;Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt;&gt;({});

  <span class="hljs-comment">// Initialize socket connection</span>
  useEffect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> socketInstance = io(<span class="hljs-string">"http://localhost:3000"</span>, {
      reconnection: <span class="hljs-literal">true</span>,
      reconnectionAttempts: <span class="hljs-number">5</span>,
      reconnectionDelay: <span class="hljs-number">1000</span>,
    });

    socketInstance.on(<span class="hljs-string">"connect"</span>, <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Connected to Socket.IO server"</span>);
    });

    <span class="hljs-comment">// Handle real-time file updates</span>
    socketInstance.on(
      <span class="hljs-string">"file-update"</span>,
      <span class="hljs-function">(<span class="hljs-params">{ fileId, content }: { fileId: <span class="hljs-built_in">string</span>; content: <span class="hljs-built_in">string</span> }</span>) =&gt;</span> {
        setFileContents(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> ({
          ...prev,
          [fileId]: content,
        }));

        <span class="hljs-keyword">if</span> (currentFile &amp;&amp; currentFile._id === fileId) {
          setCode(content);
        }
      }
    );

    socketInstance.on(<span class="hljs-string">"new-file"</span>, <span class="hljs-function">(<span class="hljs-params">newFile: File</span>) =&gt;</span> {
      setFiles(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> [...prev, newFile]);
      setFileContents(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> ({
        ...prev,
        [newFile._id]: newFile.content,
      }));
    });

    socketInstance.on(<span class="hljs-string">"delete-file"</span>, <span class="hljs-function">(<span class="hljs-params">fileId: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
      setFiles(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> prev.filter(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> file._id !== fileId));
      setFileContents(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> updated = { ...prev };
        <span class="hljs-keyword">delete</span> updated[fileId];
        <span class="hljs-keyword">return</span> updated;
      });
    });

    setSocket(socketInstance);

    <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> {
      <span class="hljs-keyword">if</span> (socketInstance) {
        socketInstance.disconnect();
      }
    };
  }, [currentFile]);

useCopilotReadable({
description: <span class="hljs-string">"Current state of the workspace"</span>,
value: {
    files: files.map(<span class="hljs-function">(<span class="hljs-params">f</span>) =&gt;</span> f.name),
    currentFile: currentFile?.name,
    currentCode: code,
},
});

  <span class="hljs-keyword">const</span> fetchFiles = <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">"/api/files"</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">"Failed to fetch files"</span>);
      <span class="hljs-keyword">const</span> data: File[] = <span class="hljs-keyword">await</span> response.json();

      <span class="hljs-comment">// Store all file contents in state</span>
      <span class="hljs-keyword">const</span> contents: Record&lt;<span class="hljs-built_in">string</span>, <span class="hljs-built_in">string</span>&gt; = {};
      data.forEach(<span class="hljs-function">(<span class="hljs-params">file</span>) =&gt;</span> {
        contents[file._id] = file.content;
      });

      setFiles(data);
      setFileContents(contents);
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error fetching files:"</span>, error);
    }
  };

  useEffect(<span class="hljs-function">() =&gt;</span> {
    fetchFiles();
  }, []);

  <span class="hljs-keyword">const</span> handleFileSelect = <span class="hljs-keyword">async</span> (file: File) =&gt; {
    setCurrentFile(file);

    <span class="hljs-comment">// Use cached content if available</span>
    <span class="hljs-keyword">if</span> (fileContents[file._id]) {
      setCode(fileContents[file._id]);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`/api/files/<span class="hljs-subst">${file._id}</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">"Failed to fetch file content"</span>);
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> response.json();

        setFileContents(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> ({
          ...prev,
          [file._id]: data.content,
        }));
        setCode(data.content);
      } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error fetching file content:"</span>, error);
      }
    }
  };

  <span class="hljs-keyword">const</span> handleCodeChange = <span class="hljs-keyword">async</span> (value: <span class="hljs-built_in">string</span> | <span class="hljs-literal">undefined</span>) =&gt; {
    <span class="hljs-keyword">if</span> (!currentFile || !value) <span class="hljs-keyword">return</span>;

    <span class="hljs-comment">// Update local state immediately</span>
    setCode(value);
    setFileContents(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> ({
      ...prev,
      [currentFile._id]: value,
    }));

    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Emit the change to other clients</span>
      <span class="hljs-keyword">if</span> (socket) {
        socket.emit(<span class="hljs-string">"file-update"</span>, {
          fileId: currentFile._id,
          content: value,
        });
      }

      <span class="hljs-comment">// Save to backend</span>
      <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/files"</span>, {
        method: <span class="hljs-string">"PUT"</span>,
        headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
        body: <span class="hljs-built_in">JSON</span>.stringify({ id: currentFile._id, content: value }),
      });
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error updating file:"</span>, error);
    }
  };

  <span class="hljs-keyword">const</span> processFiles = <span class="hljs-keyword">async</span> ({ response }: { response: <span class="hljs-built_in">string</span> }) =&gt; {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-keyword">const</span> filePattern =
        <span class="hljs-regexp">/FILE:\s*([\w.\-\/]+)\s*\nCODE:\s*([\s\S]*?)(?=\nFILE:|$)/g</span>;
      <span class="hljs-keyword">let</span> match;
      <span class="hljs-keyword">const</span> newFiles: File[] = [];

      <span class="hljs-keyword">while</span> ((match = filePattern.exec(response)) !== <span class="hljs-literal">null</span>) {
        <span class="hljs-keyword">const</span> fileName = match[<span class="hljs-number">1</span>].trim();
        <span class="hljs-keyword">const</span> fileContent = match[<span class="hljs-number">2</span>].trim();

        <span class="hljs-keyword">if</span> (fileName &amp;&amp; fileContent) {
          <span class="hljs-keyword">const</span> res = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"/api/files"</span>, {
            method: <span class="hljs-string">"POST"</span>,
            headers: { <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span> },
            body: <span class="hljs-built_in">JSON</span>.stringify({ name: fileName, content: fileContent }),
          });

          <span class="hljs-keyword">if</span> (res.ok) {
            <span class="hljs-keyword">const</span> savedFile: File = <span class="hljs-keyword">await</span> res.json();
            newFiles.push(savedFile);

            <span class="hljs-comment">// Update local state</span>
            setFileContents(<span class="hljs-function">(<span class="hljs-params">prev</span>) =&gt;</span> ({
              ...prev,
              [savedFile._id]: savedFile.content,
            }));

            <span class="hljs-comment">// Emit new file to other clients</span>
            <span class="hljs-keyword">if</span> (socket) {
              socket.emit(<span class="hljs-string">"new-file"</span>, savedFile);
            }
          }
        }
      }

      setFiles(<span class="hljs-function">(<span class="hljs-params">prevFiles</span>) =&gt;</span> [...prevFiles, ...newFiles]);
      <span class="hljs-keyword">return</span> <span class="hljs-string">`Files saved successfully: <span class="hljs-subst">${newFiles
        .map((f) =&gt; f.name)
        .join(<span class="hljs-string">", "</span>)}</span>`</span>;
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-built_in">console</span>.error(<span class="hljs-string">"Error processing files:"</span>, error);
      <span class="hljs-keyword">return</span> <span class="hljs-string">"Failed to save files."</span>;
    }
  };

  useCopilotAction({
    name: <span class="hljs-string">"processFiles"</span>,
    description: <span class="hljs-string">"Processes AI-generated files and saves them to MongoDB"</span>,
    parameters: [{ name: <span class="hljs-string">"response"</span>, <span class="hljs-keyword">type</span>: <span class="hljs-string">"string"</span>, required: <span class="hljs-literal">true</span> }],
    handler: processFiles,
  });

  <span class="hljs-keyword">return</span> (
    &lt;div className=<span class="hljs-string">"h-screen flex bg-gray-100"</span>&gt;
      &lt;FileExplorer
        files={files}
        onFileSelect={handleFileSelect}
        currentFile={currentFile}
      /&gt;
      &lt;div className=<span class="hljs-string">"flex-1 flex flex-col p-4"</span>&gt;
        &lt;ScreenOne
          selectedFile={currentFile}
          code={code}
          onChange={handleCodeChange}
        /&gt;
      &lt;/div&gt;
      &lt;LivePreview
        files={files}
        currentFile={currentFile}
        code={code}
        onCodeChange={handleCodeChange}
      /&gt;
      &lt;CopilotPopup
        instructions={<span class="hljs-string">`
    You are an AI-powered code generator. Use the following actions:

    1. @processFiles - To create new files, use this format:
    @processFiles(response: \`
    FILE: filename.ext
    CODE:
    [file content]
    \`)

    - Store new files in MongoDB using /api/files.
    - Then immediately fetch the files from database and show the files to FileExplorer
    - Correctly classify and separate different file types:
      - Static: HTML, CSS, JS
      - React: JSX, JS (React components)
    - For React projects:
      - Ensure the presence of index.js as the entry point.
      - Ensure there is a App.css file for styling
      - Ensure index.html contains a root &lt;div id="root"&gt;&lt;/div&gt;.
      - Separate components correctly (e.g., App.js, Header.jsx).
      - Include a package.json file with necessary React dependencies.
      - Ensure all React files follow ES6+ syntax and React best practices.

    2. @updateFile - To update existing files:
    @updateFile(filename: "file.ext", content: "new content")

    - Maintain compatibility with React environment.
    - Ensure any updated files do not break existing imports.
  `</span>}
        labels={{
          title: <span class="hljs-string">"Project Assistant"</span>,
          initial: <span class="hljs-string">"What would you like to create?"</span>,
        }}
      /&gt;
    &lt;/div&gt;
  );
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Page;
</code></pre>
<h3 id="heading-code-explanation-7">Code explanation</h3>
<ol>
<li><p><strong>State Management</strong>:</p>
<ul>
<li>Tracks files (<code>files</code>), the currently selected file (<code>currentFile</code>), code in the selected file (<code>code</code>), real-time file contents (<code>fileContents</code>), and a socket connection (<code>socket</code>).</li>
</ul>
</li>
<li><p><a target="_blank" href="http://Socket.IO"><strong>Socket.IO</strong></a>:</p>
<ul>
<li><p>Establishes a connection to a server using <a target="_blank" href="http://Socket.IO"><code>Socket.IO</code></a>, and handling real-time file updates like file creation, updates, and deletions.</p>
</li>
<li><p>Listens for events such as <code>file-update</code>, <code>new-file</code>, and <code>delete-file</code> to update the UI and propagate changes across users.</p>
</li>
</ul>
</li>
<li><p><strong>Fetching Files</strong>:</p>
<ul>
<li><p>On component mount, it fetches all files from an API and populates the state with the files and their contents.</p>
</li>
<li><p>Upon selecting a file, the content is either retrieved from the cached state or fetched from the server.</p>
</li>
</ul>
</li>
<li><p><strong>Code Changes</strong>:</p>
<ul>
<li>When code in the Monaco Editor is updated, the new content is saved locally and sent to the server and other connected clients via the socket.</li>
</ul>
</li>
<li><p><strong>File Processing by AI</strong>:</p>
<ul>
<li>When AI generates code, the <code>processFiles</code> function parses the generated content, creates files on the backend, and updates the frontend. These files are stored in MongoDB and synchronized with the client via <a target="_blank" href="http://Socket.IO">Socket.IO</a>.</li>
</ul>
</li>
<li><p><strong>CopilotKit Integration</strong>:</p>
<ul>
<li><p>Uses the <code>useCopilotAction</code> hook to integrate AI-driven file generation and updating functionality.</p>
</li>
<li><p>Provides instructions for creating and updating files via the <code>CopilotPopup</code>.</p>
</li>
<li><p>Always give detailed instructions to the <code>CopilotPopup</code> .</p>
</li>
</ul>
</li>
<li><p><strong>UI Components</strong>:</p>
<ul>
<li><p>The UI includes components like <code>FileExplorer</code>, <code>ScreenOne</code>, and <code>LivePreview</code>, which displays files, allows editing, and provides a live preview of the code.</p>
</li>
<li><p>The <code>CopilotPopup</code> acts as an assistant to guide the AI in generating or updating files.</p>
</li>
</ul>
</li>
</ol>
<p>This setup creates a collaborative, real-time code editing environment with support for both static and React-based projects.</p>
<h3 id="heading-configuring-copilotkit-for-the-whole-app"><strong>Configuring CopilotKit for the Whole App</strong></h3>
<p>This is going to be the last step of building the application. Navigate to the <code>layout.ts</code>x file and add this code:</p>
<pre><code class="lang-jsx"><span class="hljs-keyword">import</span> { CopilotKit } <span class="hljs-keyword">from</span> <span class="hljs-string">"@copilotkit/react-core"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"./globals.css"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@copilotkit/react-ui/styles.css"</span>;
<span class="hljs-keyword">import</span> { ReactNode } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> metadata = {
  <span class="hljs-attr">title</span>: <span class="hljs-string">"Replit Clone"</span>,
  <span class="hljs-attr">description</span>: <span class="hljs-string">"Copilotkit Replit Clone"</span>,
};

<span class="hljs-comment">// Define props type for RootLayout</span>
interface RootLayoutProps {
  <span class="hljs-attr">children</span>: ReactNode;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{ children }: RootLayoutProps</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">CopilotKit</span> <span class="hljs-attr">runtimeUrl</span>=<span class="hljs-string">"/api/copilotkit"</span>&gt;</span>{children}<span class="hljs-tag">&lt;/<span class="hljs-name">CopilotKit</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span>
  );
}
</code></pre>
<p>Here’s what’s going on in this code:</p>
<p>This code defines a <code>RootLayout</code> component, which serves as the root layout for a Next.js application. Here's a brief explanation:</p>
<h3 id="heading-key-features-5"><strong>Key Features</strong>:</h3>
<ol>
<li><p><strong>CopilotKit Integration</strong>:</p>
<ul>
<li><p>Wraps the entire application with the <code>CopilotKit</code> component from <code>@copilotkit/react-core</code>.</p>
</li>
<li><p>Configures the <code>runtimeUrl</code> to <code>/api/copilotkit</code>, which is the endpoint for handling Copilot-related functionality.</p>
</li>
</ul>
</li>
<li><p><strong>Global Styles</strong>:</p>
<ul>
<li>Imports global CSS styles (<code>globals.css</code>) and CopilotKit UI styles (<code>@copilotkit/react-ui/styles.css</code>).</li>
</ul>
</li>
<li><p><strong>Metadata</strong>:</p>
<ul>
<li>Sets metadata for the application (title: <code>"Replit Clone"</code>, description: <code>"Copilotkit Replit Clone"</code>).</li>
</ul>
</li>
<li><p><strong>Layout Structure</strong>:</p>
<ul>
<li><p>Uses the <code>html</code> and <code>body</code> tags to structure the document.</p>
</li>
<li><p>Renders <code>children</code> (the rest of the application) inside the <code>CopilotKit</code> wrapper.</p>
</li>
</ul>
</li>
</ol>
<h3 id="heading-some-important-notes"><strong>Some Important Notes</strong></h3>
<p>Designing and deploying a database can vary depending on the tools and requirements. For this project, I’ve chosen the simplest and most accessible approach.</p>
<h3 id="heading-why-copilotkit"><strong>Why CopilotKit?</strong></h3>
<p>CopilotKit is a powerful tool that converts natural language processing (NLP) queries into actionable backend code that runs on meta LLM. If you have an alternative that serves a similar purpose, feel free to use it. It effectively bridges the gap between natural language input and technical execution, making it an ideal choice for projects like this.</p>
<h3 id="heading-why-groqcloud"><strong>Why GroqCloud?</strong></h3>
<p>I selected GroqCloud because it’s free and offers access to multiple large language models (LLMs) with a single API key. While alternatives like ChatGPT are available, they may require paid plans. GroqCloud’s versatility and affordability make it the perfect fit for this tutorial.</p>
<h3 id="heading-security-best-practices"><strong>Security Best Practices</strong></h3>
<p>Never expose your credentials publicly. Always store sensitive information like API keys in an <code>.env</code> file to keep your project secure.</p>
<h3 id="heading-future-enhancements"><strong>Future Enhancements</strong></h3>
<p>While this tutorial focuses on setting up and working with React files, CopilotKit has a much broader range of capabilities that I will explain in the upcoming blog posts.</p>
<p>I aim to build at least 15 AI products in 2025.</p>
<p>Support for static files is coming soon.</p>
<p>As promised in the previous tutorial, I’ve implemented the CopilotKit CRUD feature in this tutorial as well.</p>
<p>In my next tutorial, I will demonstrate how to build something more cool with CopilotKit to create a more dynamic and functional application.</p>
<h2 id="heading-playing-with-the-replit-clone"><strong>Playing with the Replit Clone</strong></h2>
<p>You can explore the live project via the following link and ask the chatbot to build something in React. <a target="_blank" href="https://replit-mongodb.vercel.app/">Live Project Link</a>.</p>
<p>For a deeper understanding of the code, check out the GitHub repository here: <a target="_blank" href="https://github.com/prankurpandeyy/replit-mongodb">GitHub Repository</a>.</p>
<p>Also, here’s a screenshot showcasing its practical use. In this example, instead of manually creating project files, you can simply ask the <strong>CopilotKit</strong> chatbot to generate those files for you. You can then edit and play around with them.</p>
<p>For example, you can give the CopilotKit chatbot commands like: “Create a React app”.</p>
<h3 id="heading-handling-errors">Handling Errors</h3>
<ul>
<li><p><strong>File Explorer Delays</strong>: Occasionally, due to database or Vercel deployment issues, files may be generated but not immediately visible in the File Explorer. In such cases, simply refresh the page, and the missing components will appear. This applies to all CRUD operations on files and their content as well.</p>
</li>
<li><p><strong>Real-Time Saving</strong>: Any changes you make to files are saved to the database in real-time, ensuring that your work is never lost.</p>
</li>
<li><p><strong>Command Errors</strong>: If the chatbot shows an error when processing your commands, simply retry the command until you receive a response.</p>
</li>
<li><p><strong>Adding Extra Files:</strong>To add new files to the current project, simply ask the chatbot:<br>  <em>“Add a new file to the current project with the file name and extension.”</em></p>
<p>  For example: <em>“Add a new file named</em> <code>readme.md</code> in this project”</p>
</li>
</ul>
<h3 id="heading-video-demo">Video Demo</h3>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/AjnzEDmiu2Y" 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>
<p> </p>
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p>I hope you’ve enjoyed building this simple Replit AI clone. In this project, we used a MongoDB database, but the approach can easily be applied to other databases, as long as you can retrieve the data.</p>
<p>I plan to create many more projects involving AI and other cutting-edge tools. AI is truly a game-changer in the IT field, and I’m excited to share more insights and practical implementations of the latest technologies.</p>
<p>That’s all from my side. If you found this article helpful, feel free to share it and connect with me. I’m always open to new opportunities:</p>
<ul>
<li><p>Follow me on X: <a target="_blank" href="https://x.com/prankurpandeyy">Prankur's Twitter</a></p>
</li>
<li><p>Connect with me on LinkedIn: <a target="_blank" href="https://linkedin.com/in/prankurpandeyy">Prankur's LinkedIn</a></p>
</li>
<li><p>Follow me on Github: <a target="_blank" href="https://github.com/prankurpandeyy">Prankur’s Github</a></p>
</li>
<li><p>View my Portfolio: <a target="_blank" href="https://prankurpandeyy.netlify.app/">Prankur's Portfolio</a></p>
</li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build an Invoice SaaS App with Next.js, Resend, Clerk and Neon Postgres ]]>
                </title>
                <description>
                    <![CDATA[ In this tutorial, you will learn how to build an invoicing web app that allows users to add their bank information, manage a list of customers, and create and send invoices to customers. You'll also learn how to print and send React components as inv... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-an-invoice-saas-app-with-next-js-and-neon-postgres/</link>
                <guid isPermaLink="false">66c3754f9a8c3b7871c66a49</guid>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ankur Tyagi ]]>
                </dc:creator>
                <pubDate>Thu, 01 Aug 2024 20:58:01 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/Orange---Yellow-Gradient-Make-Design-Blog-Banner--79-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>In this tutorial, you will learn how to build an invoicing web app that allows users to add their bank information, manage a list of customers, and create and send invoices to customers. You'll also learn how to print and send React components as invoices and email templates directly from the application to the customer's email.</p>
<p>This will be a great project to help you learn how to put together full stack apps, and how to create an app where the backend can communicate with the frontend in real time.</p>
<p>While building the application, you will gain hands-on experience working with the following developer tools:</p>
<ul>
<li><a target="_blank" href="https://neon.tech/docs/introduction"><strong>Neon</strong></a>: a Postgres database that enables us to store and retrieve data easily within the application.</li>
<li><a target="_blank" href="https://clerk.com/"><strong>Clerk</strong></a>: a complete authentication system that ensures only authenticated users can perform specific actions within the application.</li>
<li><a target="_blank" href="https://www.npmjs.com/package/react-to-print"><strong>React-to-print</strong></a>: a package that allows us to convert and print React components as PDF files.</li>
<li><a target="_blank" href="https://resend.com/"><strong>Resend</strong></a> <strong>and</strong> <a target="_blank" href="https://react.email/docs/integrations/resend"><strong>React Email</strong></a>: for sending beautifully designed digital invoices directly to the customers' email.</li>
</ul>
<p><a target="_blank" href="https://github.com/tyaga001/invoice-saas-app-nextjs-neon-postgres">Here is the source code</a> (remember to give it a star ⭐).</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents</strong></h2>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-neon">What is</a> Neon?</li>
<li><a class="post-section-overview" href="#heading-building-the-invoice-application-with-nextjs">Building the Invoice Application with Next.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-authenticate-users-using-clerk">How to Authenticate Users Using Clerk</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-neon-to-a-nextjs-app">How to Add Neon to a Next.js app</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-neon-serverless-driver-with-drizzle-orm-in-nextjs">How to Set Up Neon Serverless Driver with Drizzle ORM in Next.js</a></li>
<li><a class="post-section-overview" href="#heading-creating-the-api-endpoints-for-the-application">Creating the API endpoints for the application</a></li>
<li><a class="post-section-overview" href="#heading-how-to-print-and-download-invoices-in-nextjs">How to Print and Download Invoices in Next.js</a></li>
<li><a class="post-section-overview" href="#id=&quot;how-to-send-digital-invoices-with-resend-and-react-email&quot;">How to Send Digital Invoices with Resend and React Email</a></li>
<li><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></li>
</ol>
<h2 id="heading-what-is-neon"><strong>What is Neon?</strong></h2>
<p><a target="_blank" href="https://github.com/neondatabase/neon">Neon</a> is an open-source, scalable, and efficient Postgres DB that separates compute from storage. This means that database computation processes (queries, transactions, and so on) are handled by one set of resources (compute), while the data itself is stored on a separate set of resources (storage).</p>
<p>This architecture allows for greater scalability and performance, making Neon a solid choice for modern web applications.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcnJDCduaAEwKDQL2fc2lHsMj6g68thVN_txmoGMyD1ep-x1sWa5d-eiZ3AWjq4xkmGlF7JWxuEvrO9Os5qcEXbzBLep6tCpv-RSuCJjbLwe3hzP9870mfL6LcsH0HvV1x-ymzJ-PU1YjTFuQcihvwEUgeB?key=QrOqhkDtPIneanOaExEDaA" alt="Neon - a serverless Postgres database" width="1600" height="416" loading="lazy">
<em><a target="_blank" href="https://github.com/neondatabase/neon?tab=readme-ov-file">Neon - a serverless Postgres database</a></em></p>
<h2 id="heading-building-the-invoice-application-with-nextjs"><strong>Building the Invoice Application with Next.js</strong></h2>
<p>In this section, I'll guide you through building the various pages of the invoicing application using Next.js. The application is divided into six key pages, each serving a specific purpose:</p>
<ul>
<li><strong>Home Page</strong>: This is the landing page. It provides an overview of the application and signs users into the application.</li>
<li><strong>Settings Page</strong>: Here, users can update their bank information as it will be displayed on the invoices.</li>
<li><strong>Customers Page</strong>: This page allows users to manage their customer base, and add or delete customers when needed.</li>
<li><strong>Dashboard</strong>: The core of the application where users can create new invoices. Users can select a customer, enter the title and description of the invoice, and generate invoices.</li>
<li><strong>History Page</strong>: This page displays recently created invoices. It includes links that enable users to preview each invoice, providing a quick way to review past transactions.</li>
<li><strong>Print and Send Invoice Page</strong>: This page allows users to print and send invoices to customers.</li>
</ul>
<p>Before we proceed, create a TypeScript Next.js project by running the following code snippet in your terminal:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npx create-next-app invoice-app-</span><span>with</span><span>-neon</span></p></td></tr></tbody></table>

<p>Add a <strong>types.d.ts</strong> file within the project folder. It will contain the type declarations for the variables within the application.</p>
<pre><code class="lang-javascript">interface Item {
    <span class="hljs-attr">id</span>: string;
    name: string;
    cost: number;
    quantity: number;
    price: number;
}

interface Invoice {
    id?: string,
    created_at?: string,
    <span class="hljs-attr">user_id</span>:  string,
    <span class="hljs-attr">customer_id</span>: number,
    <span class="hljs-attr">title</span>: string,
    <span class="hljs-attr">items</span>: string,
    <span class="hljs-attr">total_amount</span>: number,
}

interface Customer {
    <span class="hljs-attr">user_id</span>: string,
    <span class="hljs-attr">name</span>: string,
    <span class="hljs-attr">email</span>: string,
    <span class="hljs-attr">address</span>: string
}

interface BankInfo {
    <span class="hljs-attr">user_id</span>: string,
    <span class="hljs-attr">account_name</span>: string,
    <span class="hljs-attr">account_number</span>: number,
    <span class="hljs-attr">bank_name</span>: string,
    <span class="hljs-attr">currency</span>: string
}
</code></pre>
<h3 id="heading-home-page"><strong>Home Page</strong></h3>
<p>Copy the code snippet below into the <strong>app/page.tsx</strong> file. It displays brief information about the application and a button that redirects users to the dashboard or login page, depending on their authentication status.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'p-8 h-[90vh] md:w-2/3 mx-auto text-center w-full flex flex-col items-center justify-center'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-3xl font-bold mb-4 md:text-4xl'</span>&gt;</span>
          Create invoices for your customers
        <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-70 mb-4 text-sm md:text-base leading-loose'</span>&gt;</span>
          Invoicer is an online invoicing software that helps you craft and
          print professional invoices for your customers for free! Keep your
          business and clients with one invoicing software.
        <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Link</span>
          <span class="hljs-attr">href</span>=<span class="hljs-string">'/dashboard'</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">'rounded w-[200px] px-2 py-3 bg-blue-500 text-gray-50'</span>
        &gt;</span>
          LOG IN
        <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
  );
}
</code></pre>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfxl_8niZbdRmGGgjCG66VCVO3dIZHO-oQ4TtSDjBRFqrU7qb6yGrVOBK4xqPYeFpYgddmDPA3hcw8X5bE1eqtdUP2Un9BHn_IM2CsjII17qap-VnDD8Qyo6ZW0TwFkTgWWNxXmxST6xcvr-KxIRYjK_2xg?key=QrOqhkDtPIneanOaExEDaA" alt="Invoice-app-home-page" width="1600" height="922" loading="lazy">
<em>Invoice-app-home-page</em></p>
<h3 id="heading-settings-page"><strong>Settings Page</strong></h3>
<p>Add a <strong>settings</strong> folder containing a <strong>page.tsx</strong> file within the Next.js app directory and copy the following code snippet into the file:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { ChangeEvent, useEffect, useState, useCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> SideNav <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/components/SideNav"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Settings</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-comment">//👇🏻 default bank info</span>
    <span class="hljs-keyword">const</span> [bankInfo, setBankInfo] = useState({
        <span class="hljs-attr">account_name</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">account_number</span>: <span class="hljs-number">1234567890</span>,
        <span class="hljs-attr">bank_name</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">currency</span>: <span class="hljs-string">""</span>,
 });

    <span class="hljs-comment">//👇🏻 bank info from the form entries</span>
    <span class="hljs-keyword">const</span> [inputBankInfo, setInputBankInfo] = useState({
        <span class="hljs-attr">accountName</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">accountNumber</span>: <span class="hljs-number">1234567890</span>,
        <span class="hljs-attr">bankName</span>: <span class="hljs-string">""</span>,
        <span class="hljs-attr">currency</span>: <span class="hljs-string">""</span>,
 });

    <span class="hljs-comment">//👇🏻 updates the form entries state</span>
    <span class="hljs-keyword">const</span> handleUpdateBankInfo = <span class="hljs-function">(<span class="hljs-params">
        e: ChangeEvent&lt;HTMLInputElement | HTMLSelectElement&gt;
 </span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> { name, value } = e.target;
        setInputBankInfo(<span class="hljs-function">(<span class="hljs-params">prevState</span>) =&gt;</span> ({
 ...prevState,
 [name]: value,
 }));
 };

    <span class="hljs-comment">//👇🏻 updates the bank info</span>
    <span class="hljs-keyword">const</span> handleSubmit = <span class="hljs-function">(<span class="hljs-params">e: React.FormEvent&lt;HTMLFormElement&gt;</span>) =&gt;</span> {
        e.preventDefault();
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Tries to update bank info..."</span>);
 };
<span class="hljs-keyword">return</span> ()
}
</code></pre>
<p>The code snippet above shows that the page displays the user’s bank information and also allows the user to update it when necessary.</p>
<p>Return the UI elements below from the component:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Settings</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">//…React states and functions</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">'w-full'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'min-h-[90vh] flex items-start'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">SideNav</span> /&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'md:w-5/6 w-full h-full p-6'</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-2xl font-bold'</span>&gt;</span>Bank Information<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-70 mb-4'</span>&gt;</span>
            Update your bank account information
          <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex md:flex-row flex-col items-start justify-between w-full md:space-x-4'</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'md:w-1/3 w-full bg-blue-50 h-full p-3 rounded-md space-y-3'</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm opacity-75'</span>&gt;</span>
                Account Name: {bankInfo.account_name}
              <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm opacity-75'</span>&gt;</span>
                Account Number: {bankInfo.account_number}
              <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm opacity-75'</span>&gt;</span>
                Bank Name: {bankInfo.bank_name}
              <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm opacity-75'</span>&gt;</span>
                Currency: {bankInfo.currency}
              <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">form</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">'md:w-2/3 w-full p-3 flex flex-col'</span>
              <span class="hljs-attr">method</span>=<span class="hljs-string">'POST'</span>
              <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleSubmit}</span>
            &gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'accountName'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm'</span>&gt;</span>
                Account Name
              <span class="hljs-tag">&lt;/<span class="hljs-name">label</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">name</span>=<span class="hljs-string">'accountName'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'accountName'</span>
                <span class="hljs-attr">className</span>=<span class="hljs-string">'border-[1px] p-2 rounded mb-3'</span>
                <span class="hljs-attr">required</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{inputBankInfo.accountName}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleUpdateBankInfo}</span>
              /&gt;</span>

              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'accountNumber'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm'</span>&gt;</span>
                Account Number
              <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                <span class="hljs-attr">type</span>=<span class="hljs-string">'number'</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'accountNumber'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'accountNumber'</span>
                <span class="hljs-attr">className</span>=<span class="hljs-string">'border-[1px] p-2 rounded mb-3'</span>
                <span class="hljs-attr">required</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{inputBankInfo.accountNumber}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleUpdateBankInfo}</span>
              /&gt;</span>

              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'bankName'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm'</span>&gt;</span>
                Bank Name
              <span class="hljs-tag">&lt;/<span class="hljs-name">label</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">name</span>=<span class="hljs-string">'bankName'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'bankName'</span>
                <span class="hljs-attr">className</span>=<span class="hljs-string">'border-[1px] p-2 rounded mb-3'</span>
                <span class="hljs-attr">required</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{inputBankInfo.bankName}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleUpdateBankInfo}</span>
              /&gt;</span>

              <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'currency'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm'</span>&gt;</span>
                Currency
              <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">select</span>
                <span class="hljs-attr">name</span>=<span class="hljs-string">'currency'</span>
                <span class="hljs-attr">id</span>=<span class="hljs-string">'currency'</span>
                <span class="hljs-attr">className</span>=<span class="hljs-string">'border-[1px] p-2 rounded mb-3'</span>
                <span class="hljs-attr">required</span>
                <span class="hljs-attr">value</span>=<span class="hljs-string">{inputBankInfo.currency}</span>
                <span class="hljs-attr">onChange</span>=<span class="hljs-string">{handleUpdateBankInfo}</span>
              &gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">''</span>&gt;</span>Select<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'$'</span>&gt;</span>USD<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'€'</span>&gt;</span>EUR<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">'£'</span>&gt;</span>GBP<span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
              <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex items-center justify-end'</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>
                  <span class="hljs-attr">className</span>=<span class="hljs-string">'bg-blue-500 text-white p-2 w-[200px] rounded'</span>
                &gt;</span>
                  Update Bank Info
                <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">form</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">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfjj47wp06nbinvyDFg1Zl8udWgJWenfeu3wQ_b8_6KWP9bJAH69wCMsX5v0_XVm5-PF2K9mR_zyP7tJHLvmp2L2aLopuRQ8NiAVUVEa6WcSKV3gQOjGb2Va0227mk5OTCxQrro1uIQdwE7vyWI-rnqUkC6?key=QrOqhkDtPIneanOaExEDaA" alt="Invoice-app-settings-page" width="1600" height="836" loading="lazy">
<em>Invoice-app-settings-page</em></p>
<h3 id="heading-customers-page"><strong>Customers Page</strong></h3>
<p>Add a <strong>customers</strong> folder containing a <strong>page.tsx</strong> file within the Next.js directory and copy the code snippet below into the file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> CustomersTable <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/CustomersTable"</span>;
<span class="hljs-keyword">import</span> { useCallback, useEffect, useState } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> SideNav <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/components/SideNav"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Customers</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> [customerName, setCustomerName] = useState&lt;string&gt;(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [customerEmail, setCustomerEmail] = useState&lt;string&gt;(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [customerAddress, setCustomerAddress] = useState&lt;string&gt;(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [loading, setLoading] = useState&lt;boolean&gt;(<span class="hljs-literal">false</span>);
  <span class="hljs-keyword">const</span> [customers, setCustomers] = useState([]);

  <span class="hljs-keyword">const</span> handleAddCustomer = <span class="hljs-function">(<span class="hljs-params">e: React.FormEvent&lt;HTMLFormElement&gt;</span>) =&gt;</span> {
    e.preventDefault();
    <span class="hljs-comment">// 👉🏻 createCustomer();</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">'w-full'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'min-h-[90vh] flex items-start'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">SideNav</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'md:w-5/6 w-full h-full p-6'</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-2xl font-bold'</span>&gt;</span>Customers<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-70 mb-4'</span>&gt;</span>Create and view all your customers<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full'</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleAddCustomer}</span> <span class="hljs-attr">method</span>=<span class="hljs-string">'POST'</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full flex items-center space-x-4 mb-3'</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-1/2'</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Customer<span class="hljs-symbol">&amp;apos;</span>s Name<span class="hljs-tag">&lt;/<span class="hljs-name">label</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">className</span>=<span class="hljs-string">'w-full p-2 border border-gray-200 rounded-sm'</span>
                  <span class="hljs-attr">value</span>=<span class="hljs-string">{customerName}</span>
                  <span class="hljs-attr">required</span>
                  <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setCustomerName(e.target.value)}
                /&gt;
              <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

              <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-1/2'</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Email Address<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                  <span class="hljs-attr">type</span>=<span class="hljs-string">'email'</span>
                  <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full p-2 border border-gray-200 rounded-sm'</span>
                  <span class="hljs-attr">value</span>=<span class="hljs-string">{customerEmail}</span>
                  <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setCustomerEmail(e.target.value)}
                  required
                /&gt;
              <span class="hljs-tag">&lt;/<span class="hljs-name">section</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">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'address'</span>&gt;</span>Billing Address<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span>
              <span class="hljs-attr">name</span>=<span class="hljs-string">'address'</span>
              <span class="hljs-attr">id</span>=<span class="hljs-string">'address'</span>
              <span class="hljs-attr">rows</span>=<span class="hljs-string">{3}</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full p-2 border border-gray-200 rounded-sm'</span>
              <span class="hljs-attr">value</span>=<span class="hljs-string">{customerAddress}</span>
              <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setCustomerAddress(e.target.value)}
              required
            /&gt;
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">'bg-blue-500 text-white p-2 rounded-md mb-6'</span>
              <span class="hljs-attr">disabled</span>=<span class="hljs-string">{loading}</span>
            &gt;</span>
              {loading ? "Adding..." : "Add Customer"}
            <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">CustomersTable</span> <span class="hljs-attr">customers</span>=<span class="hljs-string">{customers}</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">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>The code snippet above allows users to view, create, and delete customers from the application.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdd4_PjRZlb7a65BP3PJIItYL0b7GuwMqwiMstNFqPsOl7n5lNIehqAZFK33YPMSHBtbPeRg-LwRmMwv0ASz1PBfC9Bo8YWaNGJcO_heST76rrsB7R6c0PDeXeC5B9AH2TfWriGj4SNC7FGO1BcEm8cEwol?key=QrOqhkDtPIneanOaExEDaA" alt="Invoice-app-customer-page" width="1600" height="917" loading="lazy">
<em>Invoice-app-customer-page</em></p>
<h3 id="heading-dashboard-page"><strong>Dashboard Page</strong></h3>
<p>Create a dashboard folder containing a page.tsx within the Next.js app directory and copy the code snippet below into the file:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> InvoiceTable <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/components/InvoiceTable"</span>;
<span class="hljs-keyword">import</span> React, { useState, useEffect, useCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> { useRouter } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/navigation"</span>;
<span class="hljs-keyword">import</span> SideNav <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/components/SideNav"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Dashboard</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { isLoaded, isSignedIn, user } = useUser();
  <span class="hljs-keyword">const</span> [itemList, setItemList] = useState&lt;Item[]&gt;([]);
  <span class="hljs-keyword">const</span> [customer, setCustomer] = useState&lt;string&gt;(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [invoiceTitle, setInvoiceTitle] = useState&lt;string&gt;(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [itemCost, setItemCost] = useState&lt;number&gt;(<span class="hljs-number">1</span>);
  <span class="hljs-keyword">const</span> [itemQuantity, setItemQuantity] = useState&lt;number&gt;(<span class="hljs-number">1</span>);
  <span class="hljs-keyword">const</span> [itemName, setItemName] = useState&lt;string&gt;(<span class="hljs-string">""</span>);
  <span class="hljs-keyword">const</span> [customers, setCustomers] = useState([]);
  <span class="hljs-keyword">const</span> router = useRouter();

  <span class="hljs-keyword">const</span> handleAddItem = <span class="hljs-function">(<span class="hljs-params">e: React.FormEvent</span>) =&gt;</span> {
    e.preventDefault();
    <span class="hljs-keyword">if</span> (itemName.trim() &amp;&amp; itemCost &gt; <span class="hljs-number">0</span> &amp;&amp; itemQuantity &gt;= <span class="hljs-number">1</span>) {
      setItemList([
        ...itemList,
        {
          <span class="hljs-attr">id</span>: <span class="hljs-built_in">Math</span>.random().toString(<span class="hljs-number">36</span>).substring(<span class="hljs-number">2</span>, <span class="hljs-number">9</span>),
          <span class="hljs-attr">name</span>: itemName,
          <span class="hljs-attr">cost</span>: itemCost,
          <span class="hljs-attr">quantity</span>: itemQuantity,
          <span class="hljs-attr">price</span>: itemCost * itemQuantity,
        },
      ]);
    }

    setItemName(<span class="hljs-string">""</span>);
    setItemCost(<span class="hljs-number">0</span>);
    setItemQuantity(<span class="hljs-number">0</span>);
  };

  <span class="hljs-keyword">const</span> getTotalAmount = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">let</span> total = <span class="hljs-number">0</span>;
    itemList.forEach(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> {
      total += item.price;
    });
    <span class="hljs-keyword">return</span> total;
  };

  <span class="hljs-keyword">const</span> handleFormSubmit = <span class="hljs-function">(<span class="hljs-params">e: React.FormEvent&lt;HTMLFormElement&gt;</span>) =&gt;</span> {
    e.preventDefault();
    <span class="hljs-comment">//👉🏻 createInvoice();</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">'w-full'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'min-h-[90vh] flex items-start'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">SideNav</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'md:w-5/6 w-full h-full p-6'</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'font-bold text-2xl mb-3'</span>&gt;</span>Add new invoice<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>

          <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full flex flex-col'</span> <span class="hljs-attr">onSubmit</span>=<span class="hljs-string">{handleFormSubmit}</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'customer'</span>&gt;</span>Customer<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">select</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">'border-[1px] p-2 rounded-sm mb-3'</span>
              <span class="hljs-attr">required</span>
              <span class="hljs-attr">value</span>=<span class="hljs-string">{customer}</span>
              <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setCustomer(e.target.value)}
            &gt;
              {customers.map((customer: any) =&gt; (
                <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{customer.id}</span> <span class="hljs-attr">value</span>=<span class="hljs-string">{customer.name}</span>&gt;</span>
                  {customer.name}
                <span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
              ))}
            <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>

            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'title'</span>&gt;</span>Title<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">'border-[1px] rounded-sm mb-3 py-2 px-3'</span>
              <span class="hljs-attr">required</span>
              <span class="hljs-attr">value</span>=<span class="hljs-string">{invoiceTitle}</span>
              <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setInvoiceTitle(e.target.value)}
            /&gt;

            <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full flex justify-between flex-col'</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'my-4 font-bold'</span>&gt;</span>Items List<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>

              <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex space-x-3'</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex flex-col w-1/4'</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'itemName'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm'</span>&gt;</span>
                    Name
                  <span class="hljs-tag">&lt;/<span class="hljs-name">label</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">name</span>=<span class="hljs-string">'itemName'</span>
                    <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Name'</span>
                    <span class="hljs-attr">className</span>=<span class="hljs-string">'py-2 px-4 mb-6 bg-gray-100'</span>
                    <span class="hljs-attr">value</span>=<span class="hljs-string">{itemName}</span>
                    <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setItemName(e.target.value)}
                  /&gt;
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex flex-col w-1/4'</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'itemCost'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm'</span>&gt;</span>
                    Cost
                  <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                    <span class="hljs-attr">type</span>=<span class="hljs-string">'number'</span>
                    <span class="hljs-attr">name</span>=<span class="hljs-string">'itemCost'</span>
                    <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Cost'</span>
                    <span class="hljs-attr">className</span>=<span class="hljs-string">'py-2 px-4 mb-6 bg-gray-100'</span>
                    <span class="hljs-attr">value</span>=<span class="hljs-string">{itemCost}</span>
                    <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setItemCost(Number(e.target.value))}
                  /&gt;
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex flex-col justify-center w-1/4'</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">htmlFor</span>=<span class="hljs-string">'itemQuantity'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm'</span>&gt;</span>
                    Quantity
                  <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">input</span>
                    <span class="hljs-attr">type</span>=<span class="hljs-string">'number'</span>
                    <span class="hljs-attr">name</span>=<span class="hljs-string">'itemQuantity'</span>
                    <span class="hljs-attr">placeholder</span>=<span class="hljs-string">'Quantity'</span>
                    <span class="hljs-attr">className</span>=<span class="hljs-string">'py-2 px-4 mb-6 bg-gray-100'</span>
                    <span class="hljs-attr">value</span>=<span class="hljs-string">{itemQuantity}</span>
                    <span class="hljs-attr">onChange</span>=<span class="hljs-string">{(e)</span> =&gt;</span> setItemQuantity(Number(e.target.value))}
                  /&gt;
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex flex-col justify-center w-1/4'</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm'</span>&gt;</span>Price<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'py-2 px-4 mb-6 bg-gray-100'</span>&gt;</span>
                    {Number(itemCost * itemQuantity).toLocaleString("en-US")}
                  <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">button</span>
                <span class="hljs-attr">className</span>=<span class="hljs-string">'bg-blue-500 text-gray-100 w-[100px] p-2 rounded'</span>
                <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleAddItem}</span>
              &gt;</span>
                Add Item
              <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">InvoiceTable</span> <span class="hljs-attr">itemList</span>=<span class="hljs-string">{itemList}</span> /&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">'bg-blue-800 text-gray-100 w-full p-4 rounded my-6'</span>
              <span class="hljs-attr">type</span>=<span class="hljs-string">'submit'</span>
            &gt;</span>
              SAVE &amp; PREVIEW INVOICE
            <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>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>The code snippet above displays a form that accepts the invoice details, such as the customer’s name, invoice title, and items list needed to create an invoice.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcUHH9gL0R6IRq-WSuKxwiTyNLM0Hae4uqYjIPXBswcEDG_zNfk7-QBLGj1Ht-RC5zbPkp6JddjSgIEwvkNeID6756C7i_uA-_vq8kgTDU-tuA6FqORWxtaJ8Jc53XdOULfmGOmEHSsiGRbTuXuth957Hkt?key=QrOqhkDtPIneanOaExEDaA" alt="Invoice-app-dashboard" width="1600" height="866" loading="lazy">
<em>Invoice-app-dashboard</em></p>
<h3 id="heading-history-page"><strong>History Page</strong></h3>
<p>Create a <strong>history</strong> folder containing a <strong>page.tsx</strong> file within the Next.js app directory and copy the following code into the file:</p>
<pre><code class="lang-javascript"><span class="hljs-string">"use client"</span>;
<span class="hljs-keyword">import</span> { useState, useEffect, useCallback } <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
<span class="hljs-keyword">import</span> SideNav <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/components/SideNav"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">History</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { isLoaded, isSignedIn, user } = useUser();
  <span class="hljs-keyword">const</span> [invoices, setInvoices] = useState&lt;Invoice[]&gt;([]);

  <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">'w-full'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'min-h-[90vh] flex items-start'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">SideNav</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'md:w-5/6 w-full h-full p-6'</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-2xl font-bold'</span>&gt;</span>History<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-70 mb-4'</span>&gt;</span>View all your invoices and their status<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

          {invoices.map((invoice) =&gt; (
            <span class="hljs-tag">&lt;<span class="hljs-name">div</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">'bg-blue-50 w-full mb-3 rounded-md p-3 flex items-center justify-between'</span>
              <span class="hljs-attr">key</span>=<span class="hljs-string">{invoice.id}</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">className</span>=<span class="hljs-string">'text-sm text-gray-500 mb-2'</span>&gt;</span>
                  Invoice - #0{invoice.id} issued to{" "}
                  <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'font-bold'</span>&gt;</span>{invoice.customer_id}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-lg font-bold mb-[1px]'</span>&gt;</span>
                  {Number(invoice.total_amount).toLocaleString()}
                <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">Link</span>
                <span class="hljs-attr">href</span>=<span class="hljs-string">{{</span>
                  <span class="hljs-attr">pathname:</span> `/<span class="hljs-attr">invoices</span>/${<span class="hljs-attr">invoice.id</span>}`,
                  <span class="hljs-attr">query:</span> { <span class="hljs-attr">customer:</span> <span class="hljs-attr">invoice.customer_id</span> },
                }}
                <span class="hljs-attr">className</span>=<span class="hljs-string">'bg-blue-500 text-blue-50 rounded p-3'</span>
              &gt;</span>
                Preview
              <span class="hljs-tag">&lt;/<span class="hljs-name">Link</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">main</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
  );
}
</code></pre>
<p>The code snippet above displays the recently created invoices and enables users to preview them when needed.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfn94sQF287nrzVJb3FB3rO3zkfidF87Amtx4xliIM93iK_I30dAEZEZaDrt7YMX2e_Zi2o0lMJYHvqudFrlQA880nL8NbO0Rsii_n_sdMVV1Lp6DHbOT7eo-RLhAM7VUfxekxVyXjlpzqSn5LaX28_vZOz?key=QrOqhkDtPIneanOaExEDaA" alt="Invoice-app-history-page" width="1600" height="923" loading="lazy">
<em>Invoice-app-history-page</em></p>
<h2 id="heading-how-to-authenticate-users-using-clerk"><strong>How to Authenticate Users Using Clerk</strong></h2>
<p><a target="_blank" href="https://github.com/clerkinc">Clerk</a> is a complete user management platform that enables you to add various forms of authentication to your software applications. It provides easy-to-use, flexible UI components and APIs that can be integrated seamlessly into your application.</p>
<p>Install the <a target="_blank" href="https://clerk.com/docs/quickstarts/nextjs">Clerk Next.js SDK</a> by running the following code snippet in your terminal:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npm install @clerk/nextjs</span></p></td></tr></tbody></table>

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

<span class="hljs-comment">// the createRouteMatcher function accepts an array of routes to be protected</span>
<span class="hljs-keyword">const</span> protectedRoutes = createRouteMatcher([
    <span class="hljs-string">"/customers"</span>,
    <span class="hljs-string">"/settings"</span>,
    <span class="hljs-string">"/dashboard"</span>,
    <span class="hljs-string">"/history"</span>,
    <span class="hljs-string">"/invoices(.*)"</span>,
]);

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

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

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">RootLayout</span>(<span class="hljs-params">{
    children,
}: Readonly&lt;{
    children: React.ReactNode;
}&gt;</span>) </span>{
    <span class="hljs-keyword">return</span> (
 <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ClerkProvider</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">'en'</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">className</span>=<span class="hljs-string">{inter.className}</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex justify-between items-center h-[10vh] px-8 border-b-[1px]'</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'/'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-xl font-extrabold text-blue-700'</span>&gt;</span>
 Invoicer
 <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'flex items-center gap-5'</span>&gt;</span>
                            {/*-- if user is signed out --*/}
 <span class="hljs-tag">&lt;<span class="hljs-name">SignedOut</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">SignInButton</span> <span class="hljs-attr">mode</span>=<span class="hljs-string">'modal'</span> /&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">SignedOut</span>&gt;</span>
                            {/*-- if user is signed in --*/}
 <span class="hljs-tag">&lt;<span class="hljs-name">SignedIn</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">'/dashboard'</span> <span class="hljs-attr">className</span>=<span class="hljs-string">''</span>&gt;</span>
 Dashboard
 <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">UserButton</span> <span class="hljs-attr">showName</span> /&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">SignedIn</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>

                    {children}
 <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">ClerkProvider</span>&gt;</span></span>
 );
}
</code></pre>
<p>When a user is not signed in, the <a target="_blank" href="https://clerk.com/docs/components/unstyled/sign-in-button">Sign in button</a> component is rendered.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXe7-OxFwVNEjJ_vvM9zo7j-d1jVKcYj1EXoV-Kk5_WR3k3Ie3h1wXnr2VB_Df5rbc4OJ_uK3wtJ4g1iTfYNrsOqTDu4oMrljRNxhh0xQCVMkSyO_zrrUxmBaT-iBgAkiAKk4Tkoj17stTyY-Y3VP72BbjFL?key=QrOqhkDtPIneanOaExEDaA" alt="Clerk-Auth-Signup-Page" width="1600" height="903" loading="lazy">
<em>Clerk-Auth-Signup-Page</em></p>
<p>Then, after signing into the application, Clerk's <a target="_blank" href="https://clerk.com/docs/components/user/user-button">User Button component</a> and a link to the dashboard are displayed.</p>
<p>Next, create a <a target="_blank" href="https://clerk.com/">Clerk account</a> and add a new application project.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcu_CxSCF4Gy9AxT0QGVt8Ia1xcU3XrqLsOMxi9v1mqs7qMIHXQGPVHabyfIUkJ9YfyzkXcy-7Q85fSUz9_r1FPxY_9R8RtFuMxiR0CeNZjLqlgkNLXLG43L_EIdeyK1Dwl5tJd7PvBrG7LeHb-NJ8-I0o?key=QrOqhkDtPIneanOaExEDaA" alt="Clerk-Auth-Project-Page" width="1600" height="939" loading="lazy">
<em>Clerk-Auth-Project-Page</em></p>
<p>Select <strong>email</strong> as the authentication method and create the Clerk project.</p>
<p>Finally, add your Clerk publishable and secret keys into the .<strong>env.local</strong> file.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=&lt;your_publishable_key&gt;</span><span><br></span><span>CLERK_SECRET_KEY=&lt;your_secret_key&gt;</span></p></td></tr></tbody></table>

<p>Clerk provides various ways to <a target="_blank" href="https://clerk.com/docs/references/nextjs/read-session-data">read user's data</a> on the client and the server, which is essential for identifying users within the application.</p>
<h2 id="heading-how-to-add-neon-to-a-nextjs-app"><strong>How to Add Neon to a Next.js app</strong></h2>
<p><a target="_blank" href="https://github.com/tyaga001/awesome-neon">Neon</a> supports multiple frameworks and libraries and provides clear and detailed documentation on adding Neon to them. The Neon serverless driver lets you connect to and interact with Neon in a Next.js application.</p>
<p>Before we proceed, let’s <a target="_blank" href="https://neon.tech/docs/guides/nextjs">create a Neon account and project</a>.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdbDT3O2Kdn_GAbeMGyegKJB6dDkFnXRC9YyW_YTkGTyZuC3GYpb9ohemo3iatRjq7Cpx0jnwCnY5MXy0xkK6Nu7hf18rvZZOIsRXJi3zZUsTTAaOwDpN61WtnFVpIclISdBDZquVFtEFG8ZB9tg6bVg2wD?key=QrOqhkDtPIneanOaExEDaA" alt="Neon-postgres-all-project-dashboard" width="1600" height="934" loading="lazy">
<em>Neon-postgres-all-project-dashboard</em></p>
<p>Within your project dashboard, you'll find a database connection string. You'll use this to interact with your Neon database.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXf62euRKYINnsRnREwseLaCeBpGc9kKGTk1sIC4xO36QGpwCaYUhLva-71rrhJ_Z7sb9v1dN0Tz-3DtCCrKPy62duD2afc5MDVMpLi9wgvtw-rKg3o4huDZIbbxxSiwuftKwmtq6iVNAeQwkx1OohSKAA4b?key=QrOqhkDtPIneanOaExEDaA" alt="Neon-project-dashboard" width="1600" height="949" loading="lazy">
<em>Neon-project-dashboard</em></p>
<p>Next, install the Neon Serverless package into the Next.js project:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npm install </span><span>@neondatabase</span><span>/serverless</span></p></td></tr></tbody></table>

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

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

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

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

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

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

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

<p>Congratulations – you’ve successfully added Neon to your Next.js app.</p>
<p>However, interacting with the Neon database by writing SQL queries directly can require extra learning or introduce complexities for developers who are not familiar with SQL. It can also lead to errors or performance issues when performing complex queries.</p>
<p>This is why Neon supports database ORMs such as Drizzle ORM, which provide a higher-level interface for interacting with the database. <a target="_blank" href="https://orm.drizzle.team/docs/overview">Drizzle ORM</a> enables you to write complex query functions and interact with the database easily using TypeScript.</p>
<h2 id="heading-how-to-set-up-neon-serverless-driver-with-drizzle-orm-in-nextjs"><strong>How to Set Up Neon Serverless Driver with Drizzle ORM in Next.js</strong></h2>
<p>Drizzle ORM lets you query data and perform various operations on the database using simple TypeScript query commands. It is lightweight, typesafe, and easy to use.</p>
<p>First, you'll need to install the <a target="_blank" href="https://orm.drizzle.team/kit-docs/overview">Drizzle Kit</a> and the <a target="_blank" href="https://orm.drizzle.team/docs/overview">Drizzle ORM</a> package.</p>
<p>Drizzle Kit lets you manage the database schema and migrations.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npm i drizzle-orm</span><span><br></span><span>npm i -D drizzle-kit</span></p></td></tr></tbody></table>

<p>Inside the <strong>db</strong> folder, add an <strong>actions.ts</strong>, and <strong>schema.ts</strong> file:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>cd db</span><span><br></span><span>touch actions.ts schema.ts</span></p></td></tr></tbody></table>

<p>The actions.ts file will contain the required database queries and operations, while the schema.ts file will define the database schema for the invoicing application.</p>
<h3 id="heading-database-design-for-the-invoice-application"><strong>Database Design for the invoice application</strong></h3>
<p>Recall that users can add customers, update their bank information, and create invoices within the application. So you need to create database tables for the data in Neon.</p>
<p>The user's ID will be used as a foreign key to identify each row of data that belongs to a specific user.</p>
<p>Copy the code snippet below into the <strong>db/schema.ts</strong> file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> {  text, serial, pgTable, timestamp, numeric } <span class="hljs-keyword">from</span> <span class="hljs-string">"drizzle-orm/pg-core"</span>;

<span class="hljs-comment">//👇🏻 invoice table with its column types</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> invoicesTable = pgTable(<span class="hljs-string">"invoices"</span>, {
    <span class="hljs-attr">id</span>: serial(<span class="hljs-string">"id"</span>).primaryKey().notNull(),
    <span class="hljs-attr">owner_id</span>: text(<span class="hljs-string">"owner_id"</span>).notNull(),
    <span class="hljs-attr">customer_id</span>: text(<span class="hljs-string">"customer_id"</span>).notNull(),
    <span class="hljs-attr">title</span>: text(<span class="hljs-string">"title"</span>).notNull(),
    <span class="hljs-attr">items</span>: text(<span class="hljs-string">"items"</span>).notNull(),
    <span class="hljs-attr">created_at</span>: timestamp(<span class="hljs-string">"created_at"</span>).defaultNow(),
    <span class="hljs-attr">total_amount</span>: numeric(<span class="hljs-string">"total_amount"</span>).notNull(),
});

<span class="hljs-comment">//👇🏻 customers table with its column types</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> customersTable = pgTable(<span class="hljs-string">"customers"</span>, {
    <span class="hljs-attr">id</span>: serial(<span class="hljs-string">"id"</span>).primaryKey().notNull(),
    <span class="hljs-attr">created_at</span>: timestamp(<span class="hljs-string">"created_at"</span>).defaultNow(),
    <span class="hljs-attr">owner_id</span>: text(<span class="hljs-string">"owner_id"</span>).notNull(),
    <span class="hljs-attr">name</span>: text(<span class="hljs-string">"name"</span>).notNull(),
    <span class="hljs-attr">email</span>: text(<span class="hljs-string">"email"</span>).notNull(),
    <span class="hljs-attr">address</span>: text(<span class="hljs-string">"address"</span>).notNull(),
})

<span class="hljs-comment">//👇🏻 bank_info table with its column types</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> bankInfoTable = pgTable(<span class="hljs-string">"bank_info"</span>, {
    <span class="hljs-attr">id</span>: serial(<span class="hljs-string">"id"</span>).primaryKey().notNull(),
    <span class="hljs-attr">owner_id</span>: text(<span class="hljs-string">"owner_id"</span>).notNull().unique(),
    <span class="hljs-attr">bank_name</span>: text(<span class="hljs-string">"bank_name"</span>).notNull(),
    <span class="hljs-attr">account_number</span>: numeric(<span class="hljs-string">"account_number"</span>).notNull(),
    <span class="hljs-attr">account_name</span>: text(<span class="hljs-string">"account_name"</span>).notNull(),
    <span class="hljs-attr">created_at</span>: timestamp(<span class="hljs-string">"created_at"</span>).defaultNow(),
    <span class="hljs-attr">currency</span>: text(<span class="hljs-string">"currency"</span>).notNull(),
})
</code></pre>
<p>The actions.ts file will contain the various database operations required within the application. First, add the code snippet below to the file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { invoicesDB, customersDB, bankInfoDB } <span class="hljs-keyword">from</span> <span class="hljs-string">"."</span>;
<span class="hljs-keyword">import</span> { invoicesTable, customersTable, bankInfoTable } <span class="hljs-keyword">from</span> <span class="hljs-string">'./schema'</span>;
<span class="hljs-keyword">import</span> { desc, eq } <span class="hljs-keyword">from</span> <span class="hljs-string">"drizzle-orm"</span>;

<span class="hljs-comment">//👇🏻 add a new row to the invoices table</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> createInvoice = <span class="hljs-keyword">async</span> (invoice: any) =&gt; {
    <span class="hljs-keyword">await</span> invoicesDB.insert(invoicesTable).values({
    <span class="hljs-attr">owner_id</span>: invoice.user_id,
    <span class="hljs-attr">customer_id</span>: invoice.customer_id,
    <span class="hljs-attr">title</span>: invoice.title,
    <span class="hljs-attr">items</span>: invoice.items,
    <span class="hljs-attr">total_amount</span>: invoice.total_amount,
 });
};

<span class="hljs-comment">//👇🏻 get all user's invoices</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getUserInvoices = <span class="hljs-keyword">async</span> (user_id: string) =&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> invoicesDB.select().from(invoicesTable).where(eq(invoicesTable.owner_id, user_id)).orderBy(desc(invoicesTable.created_at));
};

<span class="hljs-comment">//👇🏻 get single invoice</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getSingleInvoice = <span class="hljs-keyword">async</span> (id: number) =&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> invoicesDB.select().from(invoicesTable).where(eq(invoicesTable.id, id));
};
</code></pre>
<p>The <strong><code>createInvoice</code></strong> function accepts invoice details as a parameter and adds a new row of data to its invoice table. The <strong><code>getUserInvoices</code></strong> function filters the table and returns an array of invoices created by the user. The <strong><code>getSingleInvoice</code></strong> function accepts an invoice ID, filters the table, and returns the invoice with a matching ID.</p>
<p>Add the following functions to the db/actions file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//👇🏻 get customers list</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getCustomers = <span class="hljs-keyword">async</span> (user_id: string) =&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> customersDB.select().from(customersTable).where(eq(customersTable.owner_id, user_id)).orderBy(desc(customersTable.created_at));
};

<span class="hljs-comment">//👇🏻 get single customer</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getSingleCustomer = <span class="hljs-keyword">async</span> (name: string) =&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> customersDB.select().from(customersTable).where(eq(customersTable.name, name));
};

<span class="hljs-comment">//👇🏻 add a new row to the customers table</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> addCustomer = <span class="hljs-keyword">async</span> (customer: Customer) =&gt; {
    <span class="hljs-keyword">await</span> customersDB.insert(customersTable).values({
        <span class="hljs-attr">owner_id</span>: customer.user_id,
        <span class="hljs-attr">name</span>: customer.name,
        <span class="hljs-attr">email</span>: customer.email,
        <span class="hljs-attr">address</span>: customer.address,
 });
};

<span class="hljs-comment">//👇🏻 delete a customer</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> deleteCustomer = <span class="hljs-keyword">async</span> (id: number) =&gt; {
  <span class="hljs-keyword">await</span> customersDB.delete(customersTable).where(eq(customersTable.id, id));
};
</code></pre>
<p>This code snippet enables users to retrieve all their customers from the database, get a single customer via its ID, add new customers, and delete customers from the <strong>customers</strong> table.</p>
<p>Finally, add this also to the <strong>db/actions.ts</strong> file:</p>
<pre><code class="lang-javascript"><span class="hljs-comment">//👇🏻 get user's bank info</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getUserBankInfo = <span class="hljs-keyword">async</span> (user_id: string) =&gt; {
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> bankInfoDB.select().from(bankInfoTable).where(eq(bankInfoTable.owner_id, user_id));
};

<span class="hljs-comment">//👇🏻 update bank info table</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> updateBankInfo = <span class="hljs-keyword">async</span> (info: any) =&gt; {
<span class="hljs-keyword">await</span> bankInfoDB.insert(bankInfoTable)
 .values({
        <span class="hljs-attr">owner_id</span>: info.user_id,
        <span class="hljs-attr">bank_name</span>: info.bank_name,
        <span class="hljs-attr">account_number</span>: info.account_number,
        <span class="hljs-attr">account_name</span>: info.account_name,
        <span class="hljs-attr">currency</span>: info.currency,
 })
 .onConflictDoUpdate({
            <span class="hljs-attr">target</span>: bankInfoTable.owner_id,
            <span class="hljs-attr">set</span>: {
                <span class="hljs-attr">bank_name</span>: info.bank_name,
                <span class="hljs-attr">account_number</span>: info.account_number,
                <span class="hljs-attr">account_name</span>: info.account_name,
                <span class="hljs-attr">currency</span>: info.currency,
 },
 });
};
</code></pre>
<p>The <strong><code>getUserBankInfo</code></strong> function fetches the user’s bank information from the database, while the <strong><code>updateBankInfo</code></strong> function updates it. If the user already has one, the function updates it with the new details – otherwise, it creates a new entry.</p>
<p>Next, update the <strong>db/index.ts</strong> file to connect to the Neon database and export the Drizzle instance for each table. This will be used to execute typesafe SQL queries against your Postgres database hosted on Neon.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { neon } <span class="hljs-keyword">from</span> <span class="hljs-string">'@neondatabase/serverless'</span>;
<span class="hljs-keyword">import</span> { drizzle } <span class="hljs-keyword">from</span> <span class="hljs-string">'drizzle-orm/neon-http'</span>;
<span class="hljs-keyword">import</span> { invoicesTable, customersTable, bankInfoTable } <span class="hljs-keyword">from</span> <span class="hljs-string">'./schema'</span>;

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


<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> invoicesDB = drizzle(sql, {
  <span class="hljs-attr">schema</span>: { invoicesTable }
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> customersDB = drizzle(sql, {
  <span class="hljs-attr">schema</span>: { customersTable }
});

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> bankInfoDB = drizzle(sql, {
  <span class="hljs-attr">schema</span>: { bankInfoTable }
});
</code></pre>
<p>Create a <strong>drizzle.config.ts</strong> file at the root of the Next.js folder and add the following configuration. Ensure you install the <a target="_blank" href="https://www.npmjs.com/package/dotenv">Dotenv package</a>.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> type { Config } <span class="hljs-keyword">from</span> <span class="hljs-string">"drizzle-kit"</span>;
<span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> dotenv <span class="hljs-keyword">from</span> <span class="hljs-string">"dotenv"</span>;

dotenv.config();

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

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

<p>You can now run <strong><code>npm run db-create</code></strong> to push the database tables to the Neon console.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdK9dJHITFXRrqOiK6pFL7hUZtvinCaymedYlOuWu9QUOOEEmKuweQ1z0MflHyhdsffeNJ7HGnFLlm9QQ10rH8q6gwGWB7nr-S6GDyCiHmkNAZCfJNhiwPuBY193H0W9nFLDUeLt8zaethyZ2bU9pMOKO5g?key=QrOqhkDtPIneanOaExEDaA" alt="Neon-tables-dashboard" width="1600" height="941" loading="lazy">
<em>Neon-tables-dashboard</em></p>
<h2 id="heading-creating-the-api-endpoints-for-the-application"><strong>Creating the API Endpoints for the Application</strong></h2>
<p>In the previous section, you created the necessary functions to interact with the database. In this section, you will learn how to create the API endpoints for each database operation.</p>
<p>First, create an <code>api</code> folder within the Next.js app directory. It will contain all the API routes for the application.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>cd app</span><span><br></span><span>mkdir api</span></p></td></tr></tbody></table>

<p>Add a <strong><code>bank-info</code></strong> folder containing a <strong>route.ts</strong> within the <code>api</code> folder. This means that the API route (<strong>/api/bank-info</strong>) will handle updating and fetching the user’s bank information.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>cd api</span><span><br></span><span>mkdir bank-info &amp;&amp; cd bank-info</span><span><br></span><span>touch route.ts</span></p></td></tr></tbody></table>

<p>Copy the code snippet below into the /bank-info/route.ts file. The POST request method updates the user’s bank information and returns a response and the GET request method retrieves the bank information from the database using the user’s ID.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { updateBankInfo, getUserBankInfo } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/db/actions"</span>;
<span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
    <span class="hljs-keyword">const</span> { accountName, userID, accountNumber, bankName, currency } = <span class="hljs-keyword">await</span> req.json();
    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> updateBankInfo({
            <span class="hljs-attr">user_id</span>: userID,
            <span class="hljs-attr">bank_name</span>: bankName,
            <span class="hljs-attr">account_number</span>: <span class="hljs-built_in">Number</span>(accountNumber),
            <span class="hljs-attr">account_name</span>: accountName,
            <span class="hljs-attr">currency</span>: currency,
 });
        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Bank Details Updated!"</span> }, { <span class="hljs-attr">status</span>: <span class="hljs-number">201</span> });
 } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred"</span>, err },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
 );
 }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
   <span class="hljs-keyword">const</span> userID  = req.nextUrl.searchParams.get(<span class="hljs-string">"userID"</span>);

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> bankInfo = <span class="hljs-keyword">await</span> getUserBankInfo(userID!);
        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Fetched bank details"</span>, bankInfo }, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
 } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred"</span>, err },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
 );
 }
}
</code></pre>
<p>Next, add an <strong>invoice</strong> folder containing a <strong>route.ts</strong> file to the <strong><code>api</code></strong> directory. Copy the code snippet below into the /api/invoice/route.ts file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { createInvoice, getUserInvoices } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/db/actions"</span>;
<span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
    <span class="hljs-keyword">const</span> { customer, title, items, total, ownerID } = <span class="hljs-keyword">await</span> req.json();

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">await</span> createInvoice({
            <span class="hljs-attr">user_id</span>: ownerID,
            <span class="hljs-attr">customer_id</span>: customer,
            title,
            <span class="hljs-attr">total_amount</span>: total,
            <span class="hljs-attr">items</span>: <span class="hljs-built_in">JSON</span>.stringify(items),
 })
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"New Invoice Created!"</span> },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">201</span> }
 );
 } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred"</span>, err },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
 );
 }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
    <span class="hljs-keyword">const</span> userID = req.nextUrl.searchParams.get(<span class="hljs-string">"userID"</span>);

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> invoices = <span class="hljs-keyword">await</span> getUserInvoices(userID!);
        <span class="hljs-keyword">return</span> NextResponse.json({<span class="hljs-attr">message</span>: <span class="hljs-string">"Invoices retrieved successfully!"</span>, invoices}, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
 } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred"</span>, err },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
 );
 }
}
</code></pre>
<p>The POST request method creates a new invoice and the GET request method returns all the user’s invoices from the database.</p>
<p>You can also create a sub-folder named <strong><code>single</code></strong> within the <strong>/api/invoices</strong> folder, and add a <strong>route.ts</strong> file within it.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">import</span> { getSingleInvoice } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/db/actions"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GET</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
   <span class="hljs-keyword">const</span> invoiceID = req.nextUrl.searchParams.get(<span class="hljs-string">"id"</span>);

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> invoice = <span class="hljs-keyword">await</span> getSingleInvoice(invoiceID);
        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Inovice retrieved successfully!"</span>, invoice }, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
 } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"An error occurred"</span>, err },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
 );
 }
}
</code></pre>
<p>The code snippet above accepts an invoice ID and retrieves all its data available within the database table. You can do the same with the <strong>customers</strong> table as well.</p>
<p>Congratulations! You’ve learned how to <em>create</em>, <em>store</em>, and <em>retrieve</em> data from the Neon Postgres database. In the upcoming sections, you’ll uncover how to print and send invoices to customers.</p>
<h2 id="heading-how-to-print-and-download-invoices-in-nextjs"><strong>How to Print and Download Invoices in Next.js</strong></h2>
<p>The <a target="_blank" href="https://www.npmjs.com/package/react-to-print">React-to-print</a> package is a simple JavaScript library that allows you to print the contents of a React component easily without tampering with the component's CSS styles. It converts React components exactly as they are into downloadable PDF files.</p>
<p>First, execute the following code snippet in your terminal to install the package:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npm install -save react-to-print</span></p></td></tr></tbody></table>

<p>Create a client page (<strong>/invoice/[id].tsx</strong>). </p>
<p>To do this, add an <strong>invoice</strong> folder containing a <strong>[id]</strong> sub-folder to the Next.js app directory. Inside the <strong>[id]</strong> folder, add a <strong>page.tsx</strong> file. This page displays all the information about an invoice and allows users to print, download, and send invoices to customers.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcnG8Yav_Xnqpmk5lO4PXkKjrWqMzEkOat42mTkGR-bvAEA5VTiZ1nasFEc05H_JR6pwlyars_oWMRuBNg4CCLCNpghvnZUQ8eBen-I0OvdPGYfItoUkcXC-Abz87MjBQdacIFUotw2WGYp7YyJFq6NeOrr?key=QrOqhkDtPIneanOaExEDaA" alt="Invoice-app-download-page-ui" width="1600" height="1022" loading="lazy">
<em>Invoice-app-download-page-ui</em></p>
<p>Create a invoice design similar to the image above by copying the code snippet below into the page.tsx file:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">const</span> ComponentToPrint = forwardRef&lt;HTMLDivElement, Props&gt;(<span class="hljs-function">(<span class="hljs-params">props, ref</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { id, customer, invoice, bankInfo } = props <span class="hljs-keyword">as</span> Props;

  <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">'w-full px-2 py-8'</span> <span class="hljs-attr">ref</span>=<span class="hljs-string">{ref}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'lg:w-2/3 w-full mx-auto shadow-md border-[1px] rounded min-h-[75vh] p-5'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full flex items-center space-x-4 justify-between'</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-4/5'</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-lg font-semibold mb-3'</span>&gt;</span>INVOICE #0{id}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'mb-6'</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-60'</span>&gt;</span>Issuer Name: {bankInfo?.account_name}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-60'</span>&gt;</span>Date: {formatDateString(invoice?.created_at!)}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-lg font-semibold mb-2'</span>&gt;</span>TO:<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'mb-6'</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-60'</span>&gt;</span>Name: {invoice?.customer_id}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-60'</span>&gt;</span>Address: {customer?.address}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'opacity-60'</span>&gt;</span>Email: {customer?.email}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">section</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> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-1/5 flex flex-col'</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'font-extrabold text-2xl'</span>&gt;</span>
              {`${bankInfo?.currency}${Number(invoice?.total_amount).toLocaleString()}`}
            <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-sm opacity-60'</span>&gt;</span>Total Amount<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">header</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">className</span>=<span class="hljs-string">'opacity-60'</span>&gt;</span>Subject:<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'text-lg font-semibold'</span>&gt;</span>{invoice?.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</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">InvoiceTable</span> <span class="hljs-attr">itemList</span>=<span class="hljs-string">{invoice?.items</span> ? <span class="hljs-attr">JSON.parse</span>(<span class="hljs-attr">invoice.items</span>) <span class="hljs-attr">:</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>
  );
});

ComponentToPrint.displayName = <span class="hljs-string">"ComponentToPrint"</span>;
</code></pre>
<p>The code snippet accepts invoice details, including the customer and user’s bank information and renders them within the component.</p>
<p>Finally, you need to wrap this component with another parent one and instruct <strong>React-to-print</strong> to print the sub-component. Add the following code snippet below the <strong><code>ComponentToPrint</code></strong> component.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { useReactToPrint } <span class="hljs-keyword">from</span> <span class="hljs-string">"react-to-print"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Invoices</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> { id } = useParams&lt;{ <span class="hljs-attr">id</span>: string }&gt;();
  <span class="hljs-comment">// Reference to the component to be printed</span>
  <span class="hljs-keyword">const</span> componentRef = useRef&lt;any&gt;();

  <span class="hljs-comment">// States for the data</span>
  <span class="hljs-keyword">const</span> [customer, setCustomer] = useState&lt;Customer&gt;();
  <span class="hljs-keyword">const</span> [bankInfo, setBankInfo] = useState&lt;BankInfo&gt;();
  <span class="hljs-keyword">const</span> [invoice, setInvoice] = useState&lt;Invoice&gt;();

  <span class="hljs-comment">// Function that sends invoice via email</span>
  <span class="hljs-keyword">const</span> handleSendInvoice = <span class="hljs-keyword">async</span> () =&gt; {};

  <span class="hljs-comment">// Function that prints the invoice</span>
  <span class="hljs-keyword">const</span> handlePrint = useReactToPrint({
    <span class="hljs-attr">documentTitle</span>: <span class="hljs-string">"Invoice"</span>,
    <span class="hljs-attr">content</span>: <span class="hljs-function">() =&gt;</span> componentRef.current,
  });

  <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full min-h-screen'</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">className</span>=<span class="hljs-string">'w-full flex p-4 items-center justify-center space-x-5 mb-3'</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
          <span class="hljs-attr">className</span>=<span class="hljs-string">'p-3 text-blue-50 bg-blue-500 rounded-md'</span>
          <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handlePrint}</span>
        &gt;</span>
          Download
        <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">className</span>=<span class="hljs-string">'p-3 text-blue-50 bg-green-500 rounded-md'</span>
          <span class="hljs-attr">onClick</span>=<span class="hljs-string">{()</span> =&gt;</span> {
            handleSendInvoice();
          }}
        &gt;
          Send Invoice
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">ComponentToPrint</span>
        <span class="hljs-attr">ref</span>=<span class="hljs-string">{componentRef}</span>
        <span class="hljs-attr">id</span>=<span class="hljs-string">{id}</span>
        <span class="hljs-attr">customer</span>=<span class="hljs-string">{customer}</span>
        <span class="hljs-attr">bankInfo</span>=<span class="hljs-string">{bankInfo}</span>
        <span class="hljs-attr">invoice</span>=<span class="hljs-string">{invoice}</span>
      /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span></span>
  );
}
</code></pre>
<p>The component renders the <strong><code>ComponentToPrint</code></strong> component, creates a reference to it, and prints it using the <a target="_blank" href="https://github.com/MatthewHerbst/react-to-print?tab=readme-ov-file#usage"><strong>useReactToPrint</strong></a> hook.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeMjZeFBZ_-Y-mP7tH9rmlBYUwSsGIJfOiCQ7VvYOtLhZBJhgZn60bWpFBNlqOWFIGtwMDizCTooXoWtSX6soKbiGr2xKU3PGMC-5YG9wA-9er21DORGzX4IsdtaxoipsQqQVKlGCu7Ix2igPgLEBaWB_I?key=QrOqhkDtPIneanOaExEDaA" alt="Invoice-app-print-ui" width="1600" height="1010" loading="lazy">
<em>Invoice-app-print-ui</em></p>
<h2 id="heading-how-to-send-digital-invoices-with-resend-and-react-email"><strong>How to Send Digital Invoices with Resend and React Email</strong></h2>
<p><a target="_blank" href="https://resend.com/">Resend</a> is an API service that enables us to send and manage emails programmatically, making it easy to integrate email functionality into software applications. </p>
<p><a target="_blank" href="https://react.email/">React Email</a> is a library that allows us to create reusable, beautifully designed email templates using React components. Both packages are created by the person, allowing for smooth integration between the two services.</p>
<p>Install both packages by running the code snippet below:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npm install resend </span><span><br></span><span>npm install react-email </span><span>@react</span><span>-email/components -E</span></p></td></tr></tbody></table>

<p>Configure React Email by including the following script in your <strong>package.json</strong> file.</p>
<p>The <strong><code>--dir</code></strong> flag gives React Email access to the email templates located within the project. In this case, the email templates are located in the <strong>src/app/emails</strong> folder.</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>{</span><span><br></span><span>&nbsp; &nbsp; </span><span>"scripts"</span><span>: {</span><span><br></span><span>&nbsp; &nbsp; &nbsp; &nbsp; </span><span>"email"</span><span>: </span><span>"email dev --dir src/app/emails"</span><span><br></span><span>&nbsp; &nbsp; }</span><span><br></span><span>}</span></p></td></tr></tbody></table>

<p>Next, create the emails folder containing the email template to be sent to the customers’ email:</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { Heading, Hr, Text } <span class="hljs-keyword">from</span> <span class="hljs-string">"@react-email/components"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">EmailTemplate</span>(<span class="hljs-params">{
    invoiceID,
    items,
    amount,
    issuerName,
    accountNumber,
    currency,
}: Props</span>) </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">Heading</span> <span class="hljs-attr">as</span>=<span class="hljs-string">'h2'</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">color:</span> "#<span class="hljs-attr">0ea5e9</span>" }}&gt;</span>
 Purhcase Invoice from {issuerName}
 <span class="hljs-tag">&lt;/<span class="hljs-name">Heading</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Text</span> <span class="hljs-attr">style</span>=<span class="hljs-string">{{</span> <span class="hljs-attr">marginBottom:</span> <span class="hljs-attr">5</span> }}&gt;</span>Invoice No: INV0{invoiceID}<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Heading</span> <span class="hljs-attr">as</span>=<span class="hljs-string">'h3'</span>&gt;</span> Payment Details:<span class="hljs-tag">&lt;/<span class="hljs-name">Heading</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Text</span>&gt;</span>Account Details: {issuerName}<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Text</span>&gt;</span>Account Number: {accountNumber}<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Text</span>&gt;</span>Total Amount: {`${currency}${amount}`}<span class="hljs-tag">&lt;/<span class="hljs-name">Text</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Hr</span> /&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Heading</span> <span class="hljs-attr">as</span>=<span class="hljs-string">'h3'</span>&gt;</span> Items: <span class="hljs-tag">&lt;/<span class="hljs-name">Heading</span>&gt;</span>
            {items &amp;&amp;
                items.map((item, index) =&gt; (
 <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{index}</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">Text</span>&gt;</span>
                            {item.cost} x {item.quantity} = {item.price}
 <span class="hljs-tag">&lt;/<span class="hljs-name">Text</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>
 );
}
</code></pre>
<p>The email template accepts all the invoice details as props and sends a dynamic email template to the user. You can also preview the invoice layout by running <strong><code>npm run email</code></strong> within your terminal.</p>
<p>Next, create a <a target="_blank" href="https://resend.com/docs/introduction">Resend account</a>, and select <strong>API Keys</strong> from the sidebar menu on your dashboard to create one.</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXdTkbkk-f3JIvcGLXoFdeQGpFNF6gDgqZWVL5NnJjcbu17I4dRp3rF8GYNUHXkvF2Gs59OQgjuknTVXWzOjknrJVeZ7xv90LhLZLPeqGgYI-il5PyKEcL3g-E3_VAem-sX13pkRlz-AhqPdgXgVQo884Uce?key=QrOqhkDtPIneanOaExEDaA" alt="resend-api-keys-dashboard" width="1600" height="938" loading="lazy">
<em>resend-api-keys-dashboard</em></p>
<p>Copy the API key into the .env.local file.</p>
<p>Finally, create an API endpoint that accepts the invoice details from the frontend and sends an invoice containing the data to a customer.</p>
<pre><code class="lang-javascript"><span class="hljs-keyword">import</span> { NextRequest, NextResponse } <span class="hljs-keyword">from</span> <span class="hljs-string">"next/server"</span>;
<span class="hljs-keyword">import</span> EmailTemplate <span class="hljs-keyword">from</span> <span class="hljs-string">"@/app/emails/email"</span>;
<span class="hljs-keyword">import</span> { Resend } <span class="hljs-keyword">from</span> <span class="hljs-string">"resend"</span>;
<span class="hljs-keyword">const</span> resend = <span class="hljs-keyword">new</span> Resend(process.env.RESEND_API_KEY!);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">POST</span>(<span class="hljs-params">req: NextRequest</span>) </span>{
    <span class="hljs-keyword">const</span> {
        invoiceID,
        items,
        title,
        amount,
        customerEmail,
        issuerName,
        accountNumber,
        currency,
 } = <span class="hljs-keyword">await</span> req.json();

    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> { data, error } = <span class="hljs-keyword">await</span> resend.emails.send({
            <span class="hljs-attr">from</span>: <span class="hljs-string">"Acme &lt;onboarding@resend.dev&gt;"</span>,
            <span class="hljs-attr">to</span>: [customerEmail],
            <span class="hljs-attr">subject</span>: title,
            <span class="hljs-attr">react</span>: EmailTemplate({
                invoiceID,
                <span class="hljs-attr">items</span>: <span class="hljs-built_in">JSON</span>.parse(items),
                <span class="hljs-attr">amount</span>: <span class="hljs-built_in">Number</span>(amount),
                issuerName,
                accountNumber,
                currency,
 }) <span class="hljs-keyword">as</span> React.ReactElement,
 });

        <span class="hljs-keyword">if</span> (error) {
            <span class="hljs-keyword">return</span> Response.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"Email not sent!"</span>, error },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">500</span> }
 );
 }

        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Email delivered!"</span> }, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
 } <span class="hljs-keyword">catch</span> (error) {
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"Email not sent!"</span>, error },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">500</span> }
 );
 }
}
</code></pre>
<p>The code snippet above accepts invoice details from the frontend, passes the required data into the email template, and sends an email to the user.</p>
<h2 id="heading-next-steps"><strong>Next Steps</strong></h2>
<p>Congratulations. By now, you should have a good understanding of how to build full-stack applications with Clerk, Resend, Neon Postgres and Next.js.</p>
<p>If you'd like to learn more about how you can leverage Neon Postgres to build advanced and scalable apps, you can check out the following resources:</p>
<ul>
<li><a target="_blank" href="https://neon.tech/docs/introduction">Neon documentation</a></li>
<li><a target="_blank" href="https://github.com/tyaga001/awesome-neon">Awesome Neon</a></li>
<li><a target="_blank" href="https://github.com/neondatabase/examples">Neon example projects</a></li>
<li><a target="_blank" href="https://neon.tech/docs/guides/vercel">How to integrate Neon with Vercel</a></li>
<li><a target="_blank" href="https://neon.tech/docs/import/import-from-postgres">How to import your data from a Postgres database to Neon</a></li>
</ul>
<h2 id="heading-thank-you-for-reading">Thank you for reading</h2>
<p>If you found this article useful, you can:</p>
<ul>
<li><a target="_blank" href="https://bytesizedbets.com/">Subscribe to my newsletter.</a></li>
<li><a target="_blank" href="https://x.com/TheAnkurTyagi">Follow me on Twitter</a> where I post about my business and writing journey, side projects, and current learnings.</li>
<li>Checkout <a target="_blank" href="https://theankurtyagi.com/">my blog</a> for more tutorials like this on developer tools.   </li>
</ul>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ Full Stack Development with Next.js, Clerk, and Neon Postgres ]]>
                </title>
                <description>
                    <![CDATA[ Full stack development is constantly evolving, with new developer tools and products being introduced that allow us to build secure and reliable applications more efficiently. In this tutorial, I’ll walk you through how to build highly performant web... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/nextjs-clerk-neon-fullstack-development/</link>
                <guid isPermaLink="false">66c375561784344f009b632f</guid>
                
                    <category>
                        <![CDATA[ database ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Ankur Tyagi ]]>
                </dc:creator>
                <pubDate>Wed, 10 Jul 2024 15:31:12 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/07/Orange---Yellow-Gradient-Make-Design-Blog-Banner--77-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Full stack development is constantly evolving, with new developer tools and products being introduced that allow us to build secure and reliable applications more efficiently.</p>
<p>In this tutorial, I’ll walk you through how to build highly performant web applications with <a target="_blank" href="https://neon.tech">Neon – a serverless PostgreSQL</a> database designed for the cloud. You'll also learn how to perform CRUD (Create, Read, Update, and Delete) operations with Neon.</p>
<p>By the end of this tutorial, you will have the basic knowledge required to start building advanced and scalable web applications with Neon.</p>
<h2 id="heading-table-of-contents"><strong>Table of Contents:</strong></h2>
<ul>
<li><a class="post-section-overview" href="#heading-what-is-neon">What is Neon?</a></li>
<li><a class="post-section-overview" href="#heading-why-neon">Why Neon?</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-neon-to-a-nextjs-app">How to add Neon to a Next.js app</a></li>
<li><a class="post-section-overview" href="#heading-how-to-set-up-neon-serverless-driver-with-drizzle-orm-in-nextjs">How to set up Neon Serverless Driver with Drizzle ORM in Next.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-build-the-application-interface-with-nextjs">How to Build the Application Interface with Next.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-authenticate-users-with-clerk">How to Authenticate Users with Clerk</a></li>
<li><a class="post-section-overview" href="#heading-crud-operations-with-the-neon-database">CRUD Operations with the Neon Database</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
<li><a class="post-section-overview" href="#heading-next-steps">Next Steps</a></li>
</ul>
<h2 id="heading-what-is-neon">What is Neon?</h2>
<p><a target="_blank" href="https://github.com/neondatabase/neon">Neon</a> is an open-source, scalable, and efficient Postgres DB that separates compute from storage. This means that database computation processes (queries, transactions, and so on) are handled by one set of resources (compute), while the data itself is stored on a separate set of resources (storage). </p>
<p>This architecture allows for greater scalability and performance, making Neon a solid choice for modern web applications.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXcT4hh-liS2uYYcatl8jC6h9gFqArEw113_WaPzoTFxeps_G97JIVhJKVSQq5DC52NJ0GOoQm4sYL5QhyLhC_e_xocDjSp7iks6j8kv6WSnhRzLVy8TxftshzFnwK238QuVsGdnnBpL_nLDmXju3klwlB6T?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="Neon - a serverless Postgres database" width="1600" height="416" loading="lazy">
<em><a target="_blank" href="https://neon.tech">Neon - a serverless Postgre</a>s database</em></p>
<h3 id="heading-3-things-to-remember-about-neon">3 Things to Remember About Neon:</h3>
<ul>
<li>🐘 <strong>Postgres</strong>: Neon is built on the foundation of Postgres. It supports the same extensions, drivers, and SQL syntax as Postgres, ensuring familiarity and ease of use.</li>
<li>☁️ <strong>Serverless</strong>: Neon operates on a serverless model. Your database is represented as a simple URL, and Neon automatically scales up and down based on workload demands. Say goodbye to over-provisioning.</li>
<li>🌱 <strong>Branching</strong>: Just like version control for code, Neon allows you to create instant, isolated copies of your data. This feature is invaluable for development, testing, and maintaining separate environments.</li>
</ul>
<h2 id="heading-why-neon">Why Neon?</h2>
<p>Neon brings the serverless experience to Postgres. Developers can build faster and scale their products effortlessly, without the need to dedicate big teams or big budgets to the database.</p>
<p>Neon supports <a target="_blank" href="https://neon.tech/docs/introduction#framework-and-language-quickstarts">multiple languages and frameworks</a> – but what are the unique features that make Neon stand out?</p>
<h3 id="heading-instant-branching-and-auto-scaling">Instant branching and auto-scaling</h3>
<p><a target="_blank" href="https://neon.tech/blog/why-you-want-a-database-that-scales-to-zero">Neon</a> allows you to <a target="_blank" href="https://neon.tech/branching">create database branches instantly</a> for testing, development, and staging environments. This lets you experiment without affecting the production database. </p>
<p>It also provides an <a target="_blank" href="https://neon.tech/docs/introduction/autoscaling">auto-scaling capability</a> that automatically adjusts resources based on the application's workload, ensuring optimal performance and cost-efficiency.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXc1HSzmptXUYsu49JPbWSizwp64G-JVME-7kmmwSKNYLcc1wUmwWXvBa6kuVxncpyazqSPgj_N4ABZddjNG2rDTHE8MFIGm3yKy1DPpiV6C7GZ1tOcTzOFkvtDwpleeJZC--V4efudtYnPe-XKs8K2P740R?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="Neon DB Main Branch Dashboard" width="1600" height="929" loading="lazy">
<em>Neon DB Main Dashboard</em></p>
<h3 id="heading-support-for-ai-applications">Support for AI applications</h3>
<p>Neon <a target="_blank" href="https://neon.tech/ai">supports AI and machine learning applications</a> by providing a high-performance and scalable infrastructure. It enables you to perform semantic and similarity searches in Postgres and handles complex queries and large datasets efficiently, making it ideal for AI or LLM applications.</p>
<h3 id="heading-open-source">Open-source</h3>
<p>Neon is backed by a <a target="_blank" href="https://github.com/neondatabase/neon">vibrant community</a> of Postgres hackers, systems engineers, and cloud engineers who are all huge fans of Postgres. </p>
<p>As an open-source platform, Neon offers transparency and flexibility. You can also reach out to the team and contributors to ask questions, contribute, and help improve the software.</p>
<h3 id="heading-serverless-architecture">Serverless Architecture</h3>
<p><a target="_blank" href="https://neon.tech/blog/architecture-decisions-in-neon">Neon</a> eliminates the need for manual server management, allowing you to focus on building applications rather than maintaining infrastructure. Its serverless nature provides on-demand scalability, ensuring that your application can handle varying loads without manual intervention.</p>
<h3 id="heading-built-upon-postgres">Built upon Postgres</h3>
<p>Postgres is one of the most reliable open-source relational <a target="_blank" href="https://neon.tech/blog/get-page-at-lsn">database</a> systems. Neon inherits all the advanced features, stability, and performance optimizations of Postgres, including support for ACID transactions, advanced SQL, and NoSQL/JSON, to create a cheaper and more efficient database for cloud environments.</p>
<h2 id="heading-how-to-add-neon-to-a-nextjs-app">How to Add Neon to a Next.js App</h2>
<p>Neon supports multiple frameworks and libraries and provides clear and detailed documentation on adding Neon to them. The Neon serverless driver enables us to connect and interact with Neon in a Next.js application.</p>
<p>Before we proceed, let’s <a target="_blank" href="https://neon.tech/docs/guides/nextjs">create a Neon account and project</a>.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXeLMMmCdF3yK_mflMt4mqz3woiTLibcrGCMK5-AE1f2KftVKYduH39BybuVdu68G2am8uwDWHJUyisFittXeqCmcgxNyhcZXIiXHjxIIu-eymmM_-VVdAMW0LWTVA7NrXI-QXNEYso3Sj1FrLX0tvSP6yNK?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="Neon DB Projects Overview" width="1600" height="938" loading="lazy">
<em>Neon DB Projects Overview: View and manage all your projects in one place.</em></p>
<p>Within your project dashboard, you'll find a database connection string. You'll use this to interact with your Neon database.</p>
<p><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXfpIPj10xMleoJEKIMFghRGp2ofgC0zJEA0C2ExMr2ijz673RM_45Kuh1RXVKkEn_uW-hU6-YIPd35C73gYMZtN_7lvChQ4MSK47CIWovh8MyUzRQluguEhAEXdvRZ8wxpOLINIyVfp50u1gIOf3foBAzg3?key=4GdX_KHTwBEvJEyZsT7b3Q" alt="Neon DB Project Dashboard" width="1600" height="897" loading="lazy">
<em>Neon DB Project Dashboard: Manage database settings with ease from the project dashboard.</em></p>
<p>Create a TypeScript Next.js project by running the following code snippet in your terminal:</p>
<table><colgroup></colgroup><tbody><tr><td><p><span>npx create-next-app neon-blog-with-clerk</span></p></td></tr></tbody></table>

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

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

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

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

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

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

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

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

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

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


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

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

dotenv.config();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


    <span class="hljs-keyword">try</span> {
        <span class="hljs-keyword">const</span> data = <span class="hljs-keyword">await</span> getPosts()
        <span class="hljs-keyword">return</span> NextResponse.json({ <span class="hljs-attr">message</span>: <span class="hljs-string">"Post fetched"</span>, data }, { <span class="hljs-attr">status</span>: <span class="hljs-number">200</span> });
 } <span class="hljs-keyword">catch</span> (err) {
        <span class="hljs-keyword">return</span> NextResponse.json(
 { <span class="hljs-attr">message</span>: <span class="hljs-string">"Post not available"</span>, err },
 { <span class="hljs-attr">status</span>: <span class="hljs-number">400</span> }
 );
 }
}
</code></pre>
<p>Congratulations! You’ve completed the project for this tutorial.</p>
<p>You can find the code for the app we built <a target="_blank" href="https://github.com/tyaga001/serverless-postgres-nextjs-handbook">here</a>.</p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>In this tutorial, you’ve learned what a Neon database is, how to create one, and how to perform CRUD operations with Neon and Drizzle ORM in a Next.js application.</p>
<p>Neon's serverless architecture, combined with its scalability and performance optimizations, makes it an excellent choice for modern web applications. Neon also provides a smooth developer experience and a community of passionate individuals ready to help you achieve your application goals. Thank you for reading.</p>
<h2 id="heading-next-steps">Next Steps</h2>
<p>By now, you should have a good understanding of how to build full-stack applications with Neon and Next.js.</p>
<p>If you'd like to learn more about how you can leverage Neon to build advanced and scalable applications, you can check out the following resources:</p>
<ul>
<li><a target="_blank" href="https://neon.tech/docs/introduction">Neon documentation</a></li>
<li><a target="_blank" href="https://github.com/neondatabase/examples">Neon example projects</a></li>
<li><a target="_blank" href="https://neon.tech/docs/guides/vercel">How to integrate Neon with Vercel</a></li>
<li><a target="_blank" href="https://neon.tech/docs/import/import-from-postgres">How to import your data from a Postgres database to Neon</a></li>
</ul>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/j4Vak4J10KU" style="aspect-ratio: 16 / 9; width: 100%; height: auto;" title="YouTube video player" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen="" loading="lazy"></iframe></div>
<h2 id="heading-thanks-for-reading"><strong>Thanks for Reading!</strong></h2>
<p>That's it for this tutorial. I hope you learned something new today.</p>
<p>If you did, please share so that it reaches others as well.</p>
<p>You can connect with me on <a target="_blank" href="https://twitter.com/TheAnkurTyagi">Twitter</a> or subscribe to my <a target="_blank" href="https://bytesizedbets.com/">newsletter</a>. </p>
<h3 id="heading-want-to-read-more-interesting-blog-posts"><strong>Want to read more interesting blog posts?</strong></h3>
<p>You can read more tutorials like this one on my <strong><a target="_blank" href="https://theankurtyagi.com/">blog</a>.</strong></p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ How to Build a Full-Stack Kanban Task Management App With TypeScript, Next.js, Redux-toolkit, and Firebase ]]>
                </title>
                <description>
                    <![CDATA[ By Olasunkanmi Balogun In this in-depth tutorial, you'll learn how to build a full-stack Kanban task management app. Along the way, we'll explore the synergies between technologies like Next.js (featuring a dive into the app router), Next-auth for us... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/build-full-stack-app-with-typescript-nextjs-redux-toolkit-firebase/</link>
                <guid isPermaLink="false">66d4608a246e57ac83a2c7bd</guid>
                
                    <category>
                        <![CDATA[ Firebase ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ handbook ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Next.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ TypeScript ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ freeCodeCamp ]]>
                </dc:creator>
                <pubDate>Tue, 26 Mar 2024 21:44:04 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/03/Option-1.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>By Olasunkanmi Balogun</p>
<p>In this in-depth tutorial, you'll learn how to build a full-stack Kanban task management app. Along the way, we'll explore the synergies between technologies like <a target="_blank" href="https://nextjs.org"><code>Next.js</code></a> (featuring a dive into the app router), <a target="_blank" href="https://next-auth.js.org"><code>Next-auth</code></a> for user authentication, and <a target="_blank" href="https://firebase.google.com/">Firebase</a>, a backend as a service platform to save user data in a database. </p>
<p>We'll also cover how you can integrate Firebase Firestore with <a target="_blank" href="https://redux-toolkit.js.org/"><code>Redux Toolkit</code></a> which enables you to cache data you have retrieved from the database to improve performance. You will also learn how to manage state with Redux Toolkit.</p>
<p>To wrap it up, we will employ <a target="_blank" href="https://www.npmjs.com/package/react-beautiful-dnd"><code>React-beautiful-dnd</code></a>, a library that effortlessly integrates drag-and-drop interactions into our Kanban boards to enhance the user experience.</p>
<p>Here's what we'll cover:</p>
<ol>
<li>How to implement authentication with the <code>next-auth.js</code> library</li>
<li>How to set up and integrate the <code>Redux</code> store with Firestore in Next.js.</li>
<li>How to build and populate the Kanban app markup with data</li>
<li>How to implement Create, Read, Update, and Delete (CRUD) operations on boards and tasks.</li>
<li>How to implement drag and drop with <code>react-beautiful-dnd</code> library.</li>
</ol>
<h2 id="heading-prerequisites">Prerequisites</h2>
<ul>
<li>You should have prior experience working with the <code>Reactjs/Next.js</code> framework.</li>
<li>You should have an understanding of type annotations in TypeScript, and ultimately, working with <code>TypeScript</code> in React.</li>
<li>An understanding of DSA in <code>JavaScript</code> is a plus.</li>
<li>Experience with <code>Redux-toolkit</code> library will also be a plus. </li>
</ul>
<p>A few notes: </p>
<ul>
<li>This article will focus primarily on functionality, but we'll use <code>Tailwind CSS</code> for styling.</li>
<li>I'll also include comments with each code snippet provided throughout this article to explain the code better. Keep an eye out for them. </li>
</ul>
<h2 id="heading-table-of-contents">Table Of Contents</h2>
<ol>
<li><a class="post-section-overview" href="#heading-how-to-implement-authentication-with-next-authjs">How To Implement Authentication With next-auth.js</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-the-redux-store">How to Configure the Redux Store</a></li>
<li><a class="post-section-overview" href="#heading-how-to-create-your-kanban-app-markup">How to Create Your Kanban App Markup</a></li>
<li><a class="post-section-overview" href="#heading-how-to-configure-firebase-firestore">How to Configure Firebase Firestore</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-initial-data-to-the-firestore-database">How to Add Initial Data to the Firestore Database</a></li>
<li><a class="post-section-overview" href="#heading-how-to-use-rtk-query-to-fetch-data-from-cloud-firestore">How to Use RTK Query to Fetch Data from Cloud Firestore</a></li>
<li><a class="post-section-overview" href="#heading-how-to-fetch-and-populate-data">How to Fetch and Populate Data</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-populate-the-navbar">How to populate the navbar</a></li>
<li><a class="post-section-overview" href="#heading-how-to-populate-the-sidebar">How to populate the sidebar</a></li>
<li><a class="post-section-overview" href="#heading-how-to-populate-the-boardtasks-component">How to populate the BoardTasks component</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-implement-crud-operations">How to Implement CRUD Operations</a><ul>
<li><a class="post-section-overview" href="#heading-how-to-add-and-edit-a-board">How to add and edit a board</a></li>
<li><a class="post-section-overview" href="#heading-how-to-add-and-edit-tasks">How to add and edit tasks</a></li>
<li><a class="post-section-overview" href="#heading-how-to-delete-boards-and-tasks">How to delete boards and tasks</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-how-to-implement-drag-and-drop-functionality">How to Implement Drag and Drop Functionality</a></li>
<li><a class="post-section-overview" href="#heading-conclusion">Conclusion</a></li>
</ol>
<p>When you are ready, let's dive in.</p>
<h2 id="heading-how-to-implement-authentication-with-next-authjs">How To Implement Authentication With <code>next-auth.js</code></h2>
<p>Begin by running the following command in your terminal to create a new <code>Next.js</code> project:</p>
<pre><code class="lang-npm">npx create-next-app@latest kanban-app-tutorial
</code></pre>
<p>Throughout the installation process, you will encounter prompts. Make sure you enable <code>TypeScript</code> and <code>Tailwind CSS</code>, as both will be integral to our project development.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/1-3.png" alt="Nextjs project installation prompts" width="600" height="400" loading="lazy"></p>
<p>Go ahead and clean out the redundant code that comes with the project. Delete the content in the <code>page.tsx</code> file and paste the code below as a placeholder:</p>
<pre><code class="lang-tsx">export default function Home() {
  return (
    &lt;main&gt;
      &lt;p&gt;Hi&lt;/p&gt;
    &lt;/main&gt;
  )
}
</code></pre>
<p>Also, edit the content in the <code>global.css</code> file and leave only the <code>Tailwind CSS</code> imports. </p>
<p>Once these modifications are complete, install the <code>next-auth.js</code> library with the following command:</p>
<pre><code>npm install next-auth
</code></pre><p>After successful installation, create an <code>api</code> folder in your root <code>app</code> folder, and inside it create an <code>auth</code> folder. Then, create a <code>[...nextauth]</code> folder inside the <code>auth</code> folder.</p>
<p>Finally, create two files named <code>route.ts</code> and <code>options.ts</code> inside the <code>[...nextauth]</code> folder. </p>
<p>Your file structure should look like the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/2-2.png" alt="Nextjs app file structure" width="600" height="400" loading="lazy"></p>
<p>Among the various <code>next-auth.js</code> providers, we will exclusively utilize the Google Provider to execute the authentication process.</p>
<p>In the <code>option.ts</code> file, paste the following code:</p>
<pre><code class="lang-tsx">import type { NextAuthOptions } from "next-auth";
import GoogleProvider from "next-auth/providers/google";

export const options: NextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID as string,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
    }),
  ],
  secret: process.env.NEXTAUTH_URL,
};
</code></pre>
<p>Here, we imported the <code>NextAuthOptions</code> type provided by <code>next-auth</code> for the sake of type safety concerning the <code>options</code> variable.</p>
<p>In the above code, the <code>options</code> object is where whichever provider we want to utilize will be housed (the Google Provider in this case). </p>
<p>You can get your <code>clientId</code> and <code>clientSecret</code> values from the Google Cloud Platform. If you need a step-by-step guide on how to get them, refer to this guide. </p>
<p>Once you have gotten them, create a <code>.env</code> file in the root folder of your application and paste the values in their respective variables.</p>
<p>Lastly, create a secret key for the <code>NEXTAUTH_SECRET</code> variable using the following terminal command:</p>
<pre><code>openssl rand -base64 <span class="hljs-number">32</span>
</code></pre><p>Ultimately, your <code>.env</code> file should contain these variables and values:</p>
<pre><code>GOOGLE_CLIENT_ID = &lt;client ID value&gt;
GOOGLE_CLIENT_SECRET = &lt;client secret value&gt;
NEXT_AUTH_SECRET = &lt;next auth secret&gt;
</code></pre><p>Important: You’ll also need these environment variables in production. So, don’t forget to update your production environment variable in your project settings on Vercel.</p>
<p>Proceed to the <code>route.ts</code> file and paste the following code in it:</p>
<pre><code><span class="hljs-keyword">import</span> NextAuth <span class="hljs-keyword">from</span> <span class="hljs-string">"next-auth/next"</span>;
<span class="hljs-keyword">import</span> { options } <span class="hljs-keyword">from</span> <span class="hljs-string">"./options"</span>;

<span class="hljs-keyword">const</span> handler = NextAuth(options);

<span class="hljs-keyword">export</span> { handler <span class="hljs-keyword">as</span> GET, handler <span class="hljs-keyword">as</span> POST };
</code></pre><p>Here, we imported the <code>options</code> variable from the <code>option.ts</code> file and passed it as a parameter to the <code>NextAuth</code> function, assigning the result to the <code>handler</code> variable.</p>
<p>The final statement ensures that any GET or POST request sent to the <code>api/auth/[...nextauth]</code> route will be managed by <code>next-auth.js</code>.</p>
<p>However, authentication won't be initiated yet because we haven't informed <code>next-auth.js</code> about which pages should be protected. </p>
<p>To implement protected routes, generate a <code>middleware.ts</code> file in the root <code>src</code> folder and insert the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> { <span class="hljs-keyword">default</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'next-auth/middleware'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> config = { matcher: [<span class="hljs-string">'/'</span>] }
</code></pre>
<p>The <code>matcher</code> property in the <code>config</code> object is an array containing the routes you want the <code>middleware</code> to protect. In this case, <code>'/'</code> designates the home page, indicating that the <code>middleware</code> protects the home page.</p>
<p>When you run your project server (with <code>npm run dev</code>), you should see an authentication page as seen below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/3-3.png" alt="Nextauth.js auth page" width="600" height="400" loading="lazy"></p>
<p>Now, let's configure the <code>Redux</code> store in our application.</p>
<h2 id="heading-how-to-configure-the-redux-store">How to Configure the Redux Store</h2>
<p>To set up the Redux store in your application, follow these steps:</p>
<ol>
<li>Begin by installing the necessary packages. Run the following command in your terminal:</li>
</ol>
<pre><code class="lang-npm">npm install @reduxjs/toolkit react-redux
</code></pre>
<p>This installs the <code>Redux Toolkit</code> and <code>react-redux</code> for React bindings.</p>
<ol start="2">
<li>In the root <code>src</code> directory, create a folder named <code>redux</code>. Within this folder, create a <code>store.ts</code> file. Paste the following code into the <code>store.ts</code> file:</li>
</ol>
<pre><code class="lang-tsx">   // store.ts

   import { configureStore } from "@reduxjs/toolkit";
   import { setupListeners } from "@reduxjs/toolkit/dist/query";

   // Create the Redux store
   export const store = configureStore({
     reducer: {}, // Add your reducers here
   });

   // Setup listeners for refetch behaviors
   setupListeners(store.dispatch);

   // Define RootState and AppDispatch types
   export type RootState = ReturnType&lt;typeof store.getState&gt;;
   export type AppDispatch = typeof store.dispatch;
</code></pre>
<p>In this code snippet, <code>configureStore</code> is used to create the Redux store, and <code>setupListeners</code> is called to handle <code>refetchOnFocus</code> and <code>refetchOnReconnect</code> behaviours.</p>
<ol start="3">
<li>Now, create another file in the same <code>redux</code> folder named <code>hooks.ts</code> and add the following code:</li>
</ol>
<pre><code class="lang-tsx">// hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";
// Typed versions of useDispatch and useSelector hooks

export const useAppDispatch = () =&gt; useDispatch&lt;AppDispatch&gt;();
export const useAppSelector: TypedUseSelectorHook&lt;RootState&gt; = useSelector;
</code></pre>
<p>This code creates typed versions of the <code>useDispatch</code> and <code>useSelector</code> hooks to ensure type safety when interacting with the Redux store.</p>
<ol start="4">
<li>Still in the <code>redux</code> folder, create a file named <code>provider.tsx</code> with the following code snippet:</li>
</ol>
<pre><code class="lang-tsx">// provider.tsx
'use client'
import { store } from "./store";
import { Provider } from "react-redux";

// Custom provider component
export function Providers({ children }: { children: React.ReactNode }) {
   return &lt;Provider store={store}&gt;{children}&lt;/Provider&gt;;
 }
</code></pre>
<p>This file defines a custom provider component to wrap around your application components.</p>
<ol start="5">
<li>In your application layout file (<code>src/app/layout.tsx</code>), import the <code>Providers</code> component and wrap it around your main layout as seen below:</li>
</ol>
<pre><code class="lang-tsx">// layout.tsx

import type { Metadata } from 'next'
import { Plus_Jakarta_Sans } from "next/font/google";
import './globals.css'
import { Providers } from "@/components/redux/provider";

//font we'll use throughout the project
const pjs = Plus_Jakarta_Sans({ subsets: ["latin"], display: "swap" });
// Metadata definition
export const metadata: Metadata = {
   title: 'Create Next App',
   description: 'Generated by create next app',
  }

// RootLayout component
export default function RootLayout({
   children,
   }: {
   children: React.ReactNode
 }) {
   return (
      &lt;html lang="en" className={pjs.className}&gt;
        &lt;body&gt;
          &lt;Providers&gt;
            {children}
          &lt;/Providers&gt;
        &lt;/body&gt;
      &lt;/html&gt;
  );
}
</code></pre>
<p>By wrapping your components with the <code>Providers</code> component, you ensure that every component in your application has access to the Redux store.</p>
<p>Up to this point, your folder structure should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/4-1.png" alt="nextjs folder app structure" width="600" height="400" loading="lazy"></p>
<p>With these steps, you have successfully integrated the Redux store into your application, and you are ready to create <a target="_blank" href="https://redux-toolkit.js.org/tutorials/quick-start#create-a-redux-state-slice">slices</a> for your application.</p>
<p>Before diving into the implementation of slices, let's create the markup for our application.</p>
<h2 id="heading-how-to-create-your-kanban-app-markup">How to Create Your Kanban App Markup</h2>
<p>This section guides you through the process of building the markup for your Kanban app. By the end of this section, your markup should resemble the image below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/8-1.png" alt="Kanban app markup" width="600" height="400" loading="lazy"></p>
<p>Let's start by creating the navbar component.</p>
<ol>
<li>Begin by establishing a <code>components</code> folder within the <code>app</code> directory. Inside it, create a <code>Navbar.tsx</code> file and insert the following code:</li>
</ol>
<pre><code class="lang-tsx">// src/app/components/Navbar.tsx

export default function Navbar() {

return (
  &lt;nav className="bg-white border flex h-24"&gt;
    &lt;div className="flex-none w-[18.75rem] border-r-2 flex items-center pl-[2.12rem]"&gt;
      &lt;p className="font-bold text-3xl"&gt; Kanban App &lt;/p&gt;
    &lt;/div&gt;

   &lt;div className="flex justify-between w-full items-center pr-[2.12rem]"&gt;
       &lt;p className="text-black text-2xl font-bold pl-6"&gt;
         Board Name
       &lt;/p&gt;

      &lt;div className="flex items-center space-x-3"&gt;
        &lt;button className="bg-blue-500 text-black px-4 py-2 flex rounded-3xl items-center space-x-2"&gt;
           &lt;p&gt;+ Add New Task&lt;/p&gt;
        &lt;/button&gt;
          &lt;div className="flex items-center"&gt;
            &lt;button className="text-3xl mb-4"&gt;...&lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
  )}
</code></pre>
<ol start="2">
<li>Next, render the <code>Navbar</code> component in the <code>src/app/layout.tsx</code> file:</li>
</ol>
<pre><code class="lang-tsx">   import type { Metadata } from 'next'
   import { Providers } from "@/components/redux/provider";
   import Navbar from './components/Navbar';
   import { Plus_Jakarta_Sans } from "next/font/google";
   import './globals.css'

   const pjs = Plus_Jakarta_Sans({ subsets: ["latin"], display: "swap" });

   export const metadata: Metadata = {
    title: 'Create Next App',
    description: 'Generated by create next app',
   }

   export default function RootLayout({
    children,
   }: {
    children: React.ReactNode
   }) {
   return (
    &lt;html lang="en" className={pjs.className}&gt;
      &lt;body&gt;
        &lt;Providers&gt;
          &lt;Navbar /&gt;  {/* Render the component here */}
          {children}
        &lt;/Providers&gt;
      &lt;/body&gt;
    &lt;/html&gt;
    )}
</code></pre>
<p>Now, the <code>Navbar</code> component is available globally across all pages in the application since it's rendered in the root layout component.</p>
<p>After implementing these changes, upon signing in to your application on <code>localhost:3000</code>, you should observe the UI as depicted in the image below. </p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/5-4.png" alt="Navbar markup" width="600" height="400" loading="lazy"></p>
<p>The placeholder "Current board name" in the navbar will eventually be replaced with the name of an active board once we populate the app with data.</p>
<p>The "Add New Task" button is designed to open the "Add new tasks" modal, and the ellipsis next to it will trigger a dropdown for editing and deleting a board. The implementation of this dropdown is the focus of the next step.</p>
<ol start="3">
<li>Create a <code>Dropdown.tsx</code> file in the same <code>components</code> folder, and paste the following code into it:</li>
</ol>
<pre><code class="lang-tsx">   //src/app/components/Dropdown.tsx

   interface IDropdown {
    show: boolean
   }

   export default function Dropdown({ show }: IDropdown) {

    return (
      &lt;div
        className={`${
          show ? "block" : "hidden"
        } w-48 absolute top-full bg-white
         border shadow-lg right-0 py-2 rounded-2xl`}
      &gt;
        &lt;div className="hover:bg-gray-300"&gt;
          &lt;button className="text-sm px-4 py-2"&gt;Edit Board&lt;/button&gt;
        &lt;/div&gt;
        &lt;div className="hover:bg-gray-300"&gt;
          &lt;button className="text-sm px-4 py-2"&gt;
            Delete Board
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    )}
</code></pre>
<p>This component takes a <code>show</code> parameter of type <code>boolean</code> as a prop. The dropdown content is displayed when <code>show</code> is <code>true</code> and hidden when it's <code>false</code>.</p>
<p>Now, proceed to the <code>Navbar.tsx</code> file and update the code to render the <code>Dropdown</code> component. Pay attention to the comments in the code snippet below to get a grasp of the updates here:</p>
<pre><code class="lang-tsx">   //src/app/components/Navbar.tsx

   'use client' // we made this a client component since we have to make use of useState

   import Dropdown from "./Dropdown";
   import { useState } from 'react'

   export default function Navbar() {

   const [show, setShow] = useState&lt;boolean&gt;(false); // this will manage the state of the show variable

   return (
    &lt;nav className="bg-white border flex h-24"&gt;
      &lt;div className="flex-none w-[18.75rem] border-r-2 flex items-center pl-[2.12rem]"&gt;
        &lt;p className="font-bold text-3xl"&gt; Kanban App &lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex justify-between w-full items-center pr-[2.12rem]"&gt;
        &lt;p className="text-black text-2xl font-bold pl-6"&gt;Current board name&lt;/p&gt;

        &lt;div className="flex items-center space-x-3"&gt;
          &lt;button className="bg-blue-500 text-black px-4 py-2 flex rounded-3xl items-center space-x-2"&gt;
            &lt;p&gt;+ Add New Task&lt;/p&gt;
          &lt;/button&gt;
          &lt;div className="relative flex items-center"&gt;
            &lt;button 
            onClick={() =&gt; setShow(!show)} // trigger function that shows dropdown here
            className="text-3xl mb-4"&gt;...&lt;/button&gt;
            &lt;Dropdown show={show}/&gt;  {/* render dropdown here and pass show as prop */}
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
    )}
</code></pre>
<p>After you make these adjustments in your <code>Navbar</code> component, you can now toggle the dropdown by clicking on the ellipsis:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/6-1.gif" alt="Dropdown toggle" width="600" height="400" loading="lazy"></p>
<p>In the next step, we'll implement components that make up the body of our application, specifically the sidebar components and board that displays the tasks.</p>
<ol start="4">
<li>To implement the sidebar, create a <code>Sidebar.tsx</code> file within the same <code>components</code> directory. Paste the following code into it:</li>
</ol>
<pre><code class="lang-tsx">   // src/app/components/Sidebar.tsx

   export default function Sidebar() {
   return (
    &lt;aside className="w-[18.75rem] flex-none dark:bg-dark-grey h-full py-6 pr-6"&gt;
      &lt;p className="text-medium-grey pl-[2.12rem] text-[.95rem] font-semibold uppercase pb-3"&gt;
        {`All Boards (0)`}
      &lt;/p&gt;
      &lt;div className="cursor-pointer flex items-center rounded-tr-full rounded-br-full bg-blue-500 space-x-2 pl-[2.12rem] py-3 pb-3"&gt;
        &lt;p className="text-white text-lg capitalize"&gt;Current board name&lt;/p&gt;
      &lt;/div&gt;
      &lt;button className="flex items-center space-x-2 pl-[2.12rem] py-3"&gt;
        &lt;p className="text-base font-bold capitalize text-main-purple"&gt;
          + Create New Board
        &lt;/p&gt;
      &lt;/button&gt;
    &lt;/aside&gt;
   );
   }
</code></pre>
<ol start="5">
<li>Following this, create another file named <code>BoardTasks.tsx</code> and paste the code below to it. This component will contain the contents of an active board task. Since the app is not yet populated with data, we'll use a placeholder that will be substituted by actual tasks later.</li>
</ol>
<pre><code class="lang-tsx">   // src/app/components/BoardTasks.tsx

   export default function BoardTasks() {
   return (
    &lt;div className="overflow-x-auto overflow-y-auto w-full bg-stone-200"&gt;
      &lt;div className="w-full h-full flex justify-center items-center"&gt;
        &lt;div className="flex flex-col items-center"&gt;
          &lt;p className="text-black text-sm"&gt;
            This board is empty. Create a new column to get started.
          &lt;/p&gt;
          &lt;button className="bg-blue-500 text-black px-4 py-2 flex mt-6 rounded-3xl items-center space-x-2"&gt;
            &lt;p&gt;+ Add New Column&lt;/p&gt;
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    );
   }
</code></pre>
<ol start="6">
<li>Then, paste the following code in your <code>src/app/page.tsx</code> file to render both the <code>Sidebar</code> and <code>BoardTasks</code> components:</li>
</ol>
<pre><code class="lang-tsx">   import Sidebar from "./components/Sidebar";
   import BoardTasks from "./components/BoardTasks";

   export default function Home() {
   return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
    &lt;/main&gt;
   );
   }
</code></pre>
<p>Up to this point, your file structure should resemble the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/7-2.png" alt="Nextjs app file structure" width="600" height="400" loading="lazy"></p>
<ol start="7">
<li>Finally, in the root <code>layout.tsx</code> file, update the style of the <code>body</code> tag as shown below:</li>
</ol>
<pre><code class="lang-tsx"> // src/app/layout.tsx
   // rest of the code here
   export default function RootLayout({
   children,
   }: {
   children: React.ReactNode;
   }) {
   return (
    &lt;html lang="en" className={pjs.className}&gt;
      &lt;body className='pb-24 h-screen overflow-hidden'&gt; {/* update style here*/}
        {/* rest of the code here */}
      &lt;/body&gt;
    &lt;/html&gt;
   );
   }
</code></pre>
<p>This adjustment ensures that the content in the <code>BoardTasks</code> component is scrollable on both the x and y axis if it exceeds the length and breadth of the screen.</p>
<p>With this, the markup for our app is complete. Your UI should resemble this if you have been following along:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/8-2.png" alt="Complete Kanban app markup" width="600" height="400" loading="lazy"></p>
<p>The sidebar will display the number of boards and the names of available boards in the app. Clicking different boards in the sidebar will switch to the selected board, and clicking "Create New Board" in the sidebar opens the "Add New Board" modal.</p>
<p>Right next to the sidebar, the tasks in each board will be displayed in columns. The current screen will be displayed if the board has no tasks yet. The "+Add New Column" button will open a modal used to add a column to a board.</p>
<p>All these features will be activated as we populate the application with data.</p>
<p>Moving forward, the next section will guide you in integrating Firebase Firestore into your application.</p>
<h2 id="heading-how-to-configure-firebase-firestore">How to Configure Firebase Firestore</h2>
<p>To integrate Firestore into your application, you'll need to create a Firebase project using the <a target="_blank" href="https://console.firebase.google.com/u/0/?_gl=1*1r24b4a*_ga*MTkyMjc0OTE3NC4xNjc4MDIwMDMw*_ga_CW55HF8NVT*MTcwMDMxMTAwNC4xNjUuMS4xNzAwMzExNDU0LjQ3LjAuMA..">Firebase console</a>. Feel free to name the project according to your preference, but for the sake of this tutorial, let's name it "Kanban-app-tutorial."</p>
<p>Once the project is created, you'll be prompted to register your app. After registration, install Firebase in your application. Install the Firebase package with the following command in your terminal:</p>
<pre><code class="lang-npm">npm install firebase
</code></pre>
<p>Now, you need to initialize Cloud Firestore in your application. Create a folder named <code>utils</code> and within it, create a <code>firebaseConfig.ts</code> file. Paste your Firebase configuration into it as shown below:</p>
<pre><code class="lang-tsx">import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

// Your web app's Firebase configuration
const firebaseConfig = {
 // Paste your Firebase config here
};

// Initialize Firebase
const app = initializeApp(firebaseConfig);
// Initialize Firestore and export it
export const db = getFirestore(app);
</code></pre>
<p>Finally, navigate to your newly created project on the cloud platform and create a Cloud Firestore database. Following this, proceed to the "Rules" tab and modify the read and write rules from false to true as illustrated in the image:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/9-1.png" alt="Firestore rules tab" width="600" height="400" loading="lazy"></p>
<p>This will enable anyone to add data to the database without restrictions. Note that this is not recommended for production – we are implementing it like this for the purpose of this article.  </p>
<p>With this setup complete, we can now begin adding data to the Cloud Firestore.</p>
<h2 id="heading-how-to-add-initial-data-to-the-firestore-database">How to Add Initial Data to the Firestore Database</h2>
<p>Our goal is to ensure that users aren't greeted with an empty board when they complete the authentication process. Instead, we want to present them with dummy task data that they can interact with, allowing them to explore the application's features.</p>
<p>Also, we aim to make this data user-specific, forming the foundation for each user to build upon by creating new boards and tasks. </p>
<p>To accomplish this, when a new user signs in, we'll generate a new document in the database for that user.</p>
<p>Here's a breakdown of our approach:</p>
<ol>
<li><p><strong>Check if the user is new</strong>:
We need to determine whether the user is signing in for the first time. This way, we can automatically create a new document for the user in the database.</p>
</li>
<li><p><strong>Create a new user document</strong>:
If the user is new, we proceed to create a new data entry in the database specifically for that user.</p>
</li>
</ol>
<p>To begin, create a <code>data.js</code> file inside the <code>utils</code> folder we created earlier (this will contain our dummy data for a board). Paste the provided data code into it.</p>
<pre><code class="lang-tsx">//used to generate new id
export const id = () =&gt; Math.random().toString(36).substring(2, 10);

export const data = {
  "boards": [
    {
      id: id(),
      name: "Roadmap",
      columns: [
        {
          id: id(),
          name: "Now",
          tasks: [
            {
              id: id(),
              title: "Launch version one",
              status: "Now"
            },
            {
              id: id(),
              title: "Review early feedback and plan next steps for roadmap",
              status: "Now"
            }
          ]
        },
        {
          id: id(),
          name: "Next",
          tasks: []
        },
        {
          id: id(),
          name: "Later",
          tasks: []
        }
      ]
    }
  ]
}
</code></pre>
<p>Now, navigate to the <code>src/app/page.tsx</code> file and modify it as demonstrated below:</p>
<pre><code class="lang-tsx">"use client";
import Sidebar from "./components/Sidebar";
import BoardTasks from "./components/BoardTasks";
// Firestore methods: collection and getDocs for document reference, addDoc for adding a document
import { collection, getDocs, addDoc } from "firebase/firestore";
// Connect our app to Firestore
import { db } from "./utils/firebaseConfig";
import { useEffect, useState } from "react";
// Import getSession from next-auth library to retrieve signed-in user details
import { getSession } from "next-auth/react";
// Import data from data.json, used to initialize the Firestore database for new users
import { data } from "./utils/data.json";

export default function Home() {
  // Manage user details in this state. Key index in TypeScript ensures type safety.
  const [userDetails, setUserDetails] = useState&lt;{ [key: string]: any }&gt;();

  // Get user session using getSession. Contains user's name and email, then passed to user details state.
  const getUserSession = async () =&gt; {
    const session = await getSession();
    if (session) {
      setUserDetails(session.user);
    }
  };

  const handleAddDoc = async () =&gt; {
    if (userDetails) {
      // Execute code inside curly braces only when `userDetails` is true.

      // Reference to the document with the user's email to check its existence in the database.
      const docRef = collection(db, "users", userDetails.email, "tasks");
      const getDos = await getDocs(docRef);

      // If the document exists, terminate the program.
      if (getDos.docs.length &gt; 0) {
   ;     return;
      } else {
        // If not, submit a new document containing the data from data.json for the user in the database.
        try {
          await addDoc(
            collection(db, "users", userDetails.email, "tasks"),
            data
          );
        } catch (e) {
          console.error("Error adding document: ", e);
        }
      }
    }
  };

  useEffect(() =&gt; {
    getUserSession(); // Call getUserSession function after the page renders.
  }, []);

  useEffect(() =&gt; {
    handleAddDoc(); // Call handleAddDoc function after the user details update.
  }, [userDetails]);

  return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
    &lt;/main&gt;
  );
}
</code></pre>
<p>This code ensures that when a user logs in, their details are fetched and checked. If it's a new user, a new document with initial dummy data is added to the Firestore database under the user's email. Make sure you've read through the comments I added if you need any further explanation.</p>
<p>Upon visiting your project console, you'll notice the presence of a document created for the signed-in user (which is you):</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/10-1.png" alt="Firestore document presence" width="600" height="400" loading="lazy"></p>
<p>The initial setup is now complete, enabling us to fetch data and initiate the population of our application. But before directly interacting with the data, we'll employ RTK query, which is included in the Redux toolkit package, as an intermediary.</p>
<p>This approach not only eliminates the need to write data fetching and caching logic in various components repeatedly, but also eliminates background revalidation, so we don't need explicit manual refreshes. </p>
<p>The next section will explore this process. </p>
<h2 id="heading-how-to-use-rtk-query-to-fetch-data-from-cloud-firestore">How to Use <code>RTK Query</code> to Fetch Data from Cloud Firestore</h2>
<p>Here, we'll begin the process of creating slices for the reducer, starting with the development of the slice dedicated to data fetching.</p>
<p>Within the <code>src/redux</code> directory, create a new folder named <code>services</code>.</p>
<p>Inside the newly created <code>services</code> folder, establish a file named <code>apiSlice.ts</code>. Copy and paste the provided code into this file:</p>
<pre><code class="lang-tsx">   import { createApi, fakeBaseQuery } from "@reduxjs/toolkit/query/react";
   import { getSession } from "next-auth/react";
   import { collection, getDocs } from "firebase/firestore";
   import { db } from "@/components/app/utils/firebaseConfig";

   // Create the Firestore API using createApi
   export const fireStoreApi = createApi({
   reducerPath: "firestoreApi", // Specifies the path for the reducer
   baseQuery: fakeBaseQuery(), // Utilizes fakeBaseQuery because Firebase has no traditional REST API endpoint
   tagTypes: ["Tasks"], // Defines tag types for caching purposes
   endpoints: (builder) =&gt; ({
    fetchDataFromDb: builder.query&lt;{ [key: string]: any }[], void&gt;({
      // Utilizes builder.query for making requests; builder.mutation can be used for CRUD operations
      async queryFn() {
        // Employs queryFn since we are not fetching data from a conventional API;
        // This allows us to include arbitrary code, as long as we return our data in the { data: results } format

        try {
          const session = await getSession();
          const { user } = session!;
            const ref = collection(db, `users/${user?.email}/tasks`);
            const querySnapshot = await getDocs(ref);
            return { data: querySnapshot.docs.map((doc) =&gt; doc.data()) };
            // Data must be returned in this format when using queryFn

        } catch (e) {
          return { error: e };
        }
      },
      providesTags: ["Tasks"], // Specifies tags for caching
    }),
   }),
   });

   // Export hooks for using the created endpoint
   export const { useFetchDataFromDbQuery } = fireStoreApi;
</code></pre>
<p>This code establishes a Firestore API using <code>createApi</code>, defining an endpoint for fetching data. The use of <code>fakeBaseQuery</code> is intentional, considering Firebase doesn't have a conventional base URL. </p>
<p>The code also integrates caching and invalidation through tags. In this slice, we've specified <code>tagTypes</code> as <code>'Tasks'</code>. In subsequent sections, we'll explore how invalidation and refetching can be done through tags.</p>
<p>In the slice, <code>endpoints</code> can be perceived as API endpoints. Functions defined within this <code>endpoints</code> function will be exported in the form of <code>use...Query</code> if it's a <code>builder.query</code> function (as in this case, <code>useFetchDataFromDbQuery</code>), and <code>use...Mutation</code> if it's a <code>builder.mutation</code> function (more on this later).</p>
<p>Now, we'll lay the foundation for incorporating the slices we generate into the Redux store. Since we will create multiple <code>slices</code> in the future, it's prudent to compile them into a dedicated file using <code>combineReducers</code>.</p>
<p>Next, create a <code>rootReducer.ts</code> file within the <code>src/redux</code> folder. Embed the following code snippet into this file to integrate the previously created <code>apiSlice</code>:</p>
<pre><code class="lang-tsx">  import { combineReducers } from "@reduxjs/toolkit";
   import { fireStoreApi } from "./services/apiSlice";

   export const rootReducer = combineReducers({
    [fireStoreApi.reducerPath]: fireStoreApi.reducer,
   });
</code></pre>
<p>In this snippet, we imported the earlier-created <code>apiSlice</code> and include it in the <code>combineReducers</code> function, specifying the key-value pair as <code>[fireStoreApi.reducerPath]: fireStoreApi.reducer</code>. </p>
<p>This configuration ensures that the state managed by the <code>apiSlice</code> is effectively integrated into the Redux store. </p>
<p>Finally, we'll add the <code>rootReducer</code> to the Redux store here. Navigate to the <code>src/redux/store.ts</code> and modify it like below:</p>
<pre><code class="lang-tsx">import { configureStore } from "@reduxjs/toolkit";
   import { setupListeners } from "@reduxjs/toolkit/dist/query";
   import { rootReducer } from "./rootReducer";
   import { fireStoreApi } from "./services/apiSlice";

   export const store = configureStore({
    reducer: rootReducer,
    middleware: (getDefaultMiddleware) =&gt; getDefaultMiddleware().concat(fireStoreApi.middleware),
   });
   setupListeners(store.dispatch)
   export type RootState = ReturnType&lt;typeof store.getState&gt;;
   export type AppDispatch = typeof store.dispatch;
</code></pre>
<p>Here, we integrate our <code>rootReducer</code> into the store and pass the <code>fireStoreApi.middleware</code> to the <code>middleware</code> prop of the <code>configureStore</code> function. This ensures that the Redux store uses the <code>middleware</code> for making requests to Firestore.</p>
<p>Now, we can safely start the process of fetching and populating our application with data, which will be the focus of the upcoming section.</p>
<h2 id="heading-how-to-fetch-and-populate-data">How to Fetch and Populate Data</h2>
<p>Our approach begins with populating data in the <code>Navbar</code> component, followed by the <code>Sidebar</code>, and finally, the <code>BoardTasks</code>.</p>
<h3 id="heading-how-to-populate-the-navbar">How to populate the navbar</h3>
<p>For the Navbar, we want to display the name of the current board. But since we'll need this information in other parts of the app, we'll also store it centrally in the Redux store.</p>
<p>To achieve this, we'll create a new slice called <code>appSlice</code>, which will manage the state related to the current board name. This slice will also be responsible for handling logic and state unrelated to API calls.</p>
<p>First, create a <code>features</code> folder within the <code>src/redux</code> directory.</p>
<p>Inside the features folder, create a file named <code>appSlice.ts</code> and paste the following code:</p>
<pre><code class="lang-tsx">   import { createSlice, PayloadAction } from "@reduxjs/toolkit";
   import { RootState } from "../store";

   // Define the initial state for the slice
   const initialState = {
    currentBoardName: "",
   };

   export const features = createSlice({
   // Name of the slice
   name: "features",
   initialState,
   // Functions that update the initialState are written inside the reducers object
   reducers: {
    // This function updates the board name when called
    setPageTitle: (state, action: PayloadAction&lt;string&gt;) =&gt; {
      state.currentBoardName = action.payload;
    },
   },
   });

   // Export the functions defined inside the reducers here
   export const { setPageTitle } = features.actions;

   // Selector function to retrieve the current board name from the state
   export const getPageTitle = (state: RootState) =&gt; state.features.currentBoardName;

   // Export the reducer for use in the Redux store
   export default features.reducer;
</code></pre>
<p>This code defines the <code>appSlice</code> slice, which includes the initial state, <code>reducers</code>, and <code>actions</code> for managing the current board name.</p>
<p>To make the <code>appSlice</code> available globally, we must integrate it into the Redux store. Open the <code>src/redux/rootReducer.ts</code> file and modify it as follows:</p>
<pre><code class="lang-tsx">   // src/redux/rootReducer.ts
   import { combineReducers } from "@reduxjs/toolkit";
   import { fireStoreApi } from "./services/apiSlice";
   import  featuresReducer  from "./features/appSlice";

   export const rootReducer = combineReducers({
   //add the features slice here
   features: featuresReducer,
   [fireStoreApi.reducerPath]: fireStoreApi.reducer,
   });
</code></pre>
<p>This updated <code>rootReducer</code> now includes the <code>featuresReducer</code>, making the <code>appSlice</code> available throughout the application.</p>
<p>Next, we need to update the <code>Navbar</code> component to fetch the current board name from the Redux store and display it. Open the <code>app/components/Navbar.tsx</code> file and make the following changes:</p>
<pre><code class="lang-tsx">  'use client' 

   import Dropdown from "./Dropdown";
   import { useState, useEffect } from 'react'
   // Import Redux functions and selectors for managing board names
   import { setCurrentBoardName, getCurrentBoardName } from '../../redux/features/appSlice'
   import { useAppDispatch, useAppSelector } from '@/components/redux/hooks'
   // Import the data-fetching hook from the API slice
   import { useFetchDataFromDbQuery } from "@/components/redux/services/apiSlice";

   export default function Navbar() {
    const [show, setShow] = useState&lt;boolean&gt;(false);
   // Destructuring assignment to extract data from the useFetchDataFromDbQuery hook
   const { data } = useFetchDataFromDbQuery();
   // Access the Redux dispatch function for calling actions
   const dispatch = useAppDispatch();

   // Effect hook to run when the data updates
   useEffect(() =&gt; {
    if (data) {
      // When a user signs in, set the currentBoardName to the first board's name
      const activeBoard = data[0].boards[0];
      dispatch(setCurrentBoardName(activeBoard.name));
    }
   }, [data]);

   // Select the current board name from the Redux store
   const currentBoardName = useAppSelector(getCurrentBoardName);

   return (
    &lt;nav className="bg-white border flex h-24"&gt;
      &lt;div className="flex-none w-[18.75rem] border-r-2 flex items-center pl-[2.12rem]"&gt;
        &lt;p className="font-bold text-3xl"&gt; Kanban App &lt;/p&gt;
      &lt;/div&gt;

      &lt;div className="flex justify-between w-full items-center pr-[2.12rem]"&gt;
        {/* populate the current board name in the navbar */}
        &lt;p className="text-black text-2xl font-bold pl-6"&gt;{currentBoardName}&lt;/p&gt;

        &lt;div className="flex items-center space-x-3"&gt;
          &lt;button className="bg-blue-500 text-black px-4 py-2 flex rounded-3xl items-center space-x-2"&gt;
            &lt;p&gt;+ Add New Task&lt;/p&gt;
          &lt;/button&gt;
          &lt;div className="relative flex items-center"&gt;
            &lt;button onClick={() =&gt; setShow(!show)} className="text-3xl mb-4"&gt;
              ...
            &lt;/button&gt;
            &lt;Dropdown show={show} /&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/nav&gt;
   );
   }
</code></pre>
<p>After these updates, your navbar should now display the name of the current board, which is "Roadmap":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/11-1.png" alt="Display board name on navar" width="600" height="400" loading="lazy"></p>
<h3 id="heading-how-to-populate-the-sidebar">How to populate the sidebar</h3>
<p>Once populated with data, the sidebar will display the number of boards and the names of the available boards in the application. Clicking on different boards in the sidebar will switch the view to the selected board. </p>
<p>While we currently only have one board available in the data, we'll lay the groundwork for these features to support multiple boards in the future. </p>
<p>Navigate to the <code>Sidebar</code> component and make the following edits as seen below:</p>
<pre><code class="lang-tsx">import { useState } from "react";
import { useAppDispatch } from "@/components/redux/hooks";
import { useFetchDataFromDbQuery } from "@/components/redux/services/apiSlice";
import { setCurrentBoardName } from "@/components/redux/features/appSlice";

export default function Sidebar() {
  // State to keep track of the index of the active board during navigation
  const [active, setActive] = useState&lt;number&gt;(0);

  const { data } = useFetchDataFromDbQuery();
  const dispatch = useAppDispatch();

  // Function to handle navigation through boards
  const handleNav = (index: number, name: string) =&gt; {
    setActive(index);
    dispatch(setCurrentBoardName(name));
  };

  return (
    &lt;aside className="w-[18.75rem] flex-none dark:bg-dark-grey h-full py-6 pr-6"&gt;
      {data &amp;&amp; (
        &lt;&gt;
          {/* Display the number of boards available in the data */}
          &lt;p className="text-medium-grey pl-[2.12rem] text-[.95rem] font-semibold uppercase pb-3"&gt;
            {`All Boards (${data[0]?.boards.length})`}
          &lt;/p&gt;
          {/* Display the names of each board */}
          {data[0]?.boards.map(
            (board: { [key: string]: any }, index: number) =&gt; {
              const { name, id } = board;
              const isActive = index === active; // Check if the board is active
              return (
                &lt;div
                  key={id}
                  onClick={() =&gt; handleNav(index, name)} // Handle navigation through boards on click
                  className={`${
                    isActive ? 'rounded-tr-full rounded-br-full bg-blue-500 text-white' : 'text-black'
                  } cursor-pointer flex items-center 
                  space-x-2 pl-[2.12rem] py-3 pb-3`}
                &gt;
                  &lt;p className="text-lg capitalize"&gt;{name}&lt;/p&gt;
                &lt;/div&gt;
              );
            }
          )}
        &lt;/&gt;
      )}
      &lt;button className="flex items-center space-x-2 pl-[2.12rem] py-3"&gt;
        &lt;p className="text-base font-bold capitalize text-main-purple"&gt;
          + Create New Board
        &lt;/p&gt;
      &lt;/button&gt;
    &lt;/aside&gt;
  );
}
</code></pre>
<p>With the above code, we have prepared the sidebar for handling multiple boards in the future. When multiple boards are available in the data, the sidebar will dynamically display them, allowing users to switch between them seamlessly.</p>
<p>Up to this point, your sidebar UI should reflect these updates:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/12-1.png" alt="Populated sidebar" width="600" height="400" loading="lazy"></p>
<p>Moving forward, in the next section we'll populate the <code>BoardTasks</code> component.</p>
<h3 id="heading-how-to-populate-the-boardtasks-component">How to populate the <code>BoardTasks</code> component</h3>
<p>In this section, the goal is to present a maximum of seven task columns on the screen. If there are fewer than seven columns, we'll display an option to add more. Also, we'll want to have an indication of an empty column for columns without tasks.</p>
<p>Each task card should feature edit and delete icons. These will serve as placeholders for forthcoming modal functionalities.</p>
<p>To implement these changes, go to the <code>BoardTasks</code> component and make the following updates:</p>
<pre><code class="lang-tsx">import { useEffect, useState } from "react";
import { useFetchDataFromDbQuery } from "@/components/redux/services/apiSlice";
import { useAppSelector } from "@/components/redux/hooks";
import { getCurrentBoardName } from "@/components/redux/features/appSlice";
import { MdEdit, MdDelete } from "react-icons/md";

// Define types for the tasks data
interface ITask {
  title: string;
  description: string;
  status: string;
}

// Define types for the data in each column
interface Column {
  name: string;
  tasks?: ITask[];
}

export default function BoardTasks() {
  // Get loading state and data from the useFetchDataFromDbQuery endpoint
  const { isLoading, data } = useFetchDataFromDbQuery();
  // Manage column data in columns state
  const [columns, setColumns] = useState&lt;Column[]&gt;([]);
  // Get active board name from the redux store
  const activeBoard = useAppSelector(getCurrentBoardName);

  // Once data fetches successfully, this function in the useEffect runs
  useEffect(() =&gt; {
    if (data !== undefined) {
      const [boards] = data;
      if (boards) {
        // Get the data of the active board
        const activeBoardData = boards.boards.find(
          (board: { name: string }) =&gt; board.name === activeBoard
        );
        if (activeBoardData) {
          const { columns } = activeBoardData;
          setColumns(columns);
        }
      }
    }
  }, [data, activeBoard]);

  return (
    &lt;div className="overflow-x-auto overflow-y-auto w-full p-6 bg-stone-200"&gt;
      {/* If data has not been fetched successfully, display a loading state, else display the column of tasks */}
      {isLoading ? (
        &lt;p className="text-3xl w-full text-center font-bold"&gt;Loading tasks...&lt;/p&gt;
      ) : (
        &lt;&gt;
          {/* If columns of tasks isn't empty: display the tasks, else display the prompt to add a new column */}
          {columns.length &gt; 0 ? (
            &lt;div className="flex space-x-6"&gt;
              {columns.map((column) =&gt; {
                const { id, name, tasks } = column;
                return (
                  &lt;div key={id} className="w-[17.5rem] shrink-0"&gt;
                    &lt;p className="text-black"&gt;{`${name} (${
                      tasks ? tasks?.length : 0
                    })`}&lt;/p&gt;

                    {tasks &amp;&amp;
                      // Display the tasks if there are tasks in the column, if not, display an empty column
                      (tasks.length &gt; 0 ? (
                        tasks.map((task) =&gt; {
                          const { id, title, status } = task;

                          return (
                            &lt;div
                              key={id}
                              className="bg-white p-6 rounded-md mt-6 flex items-center justify-between border"
                            &gt;
                              &lt;p&gt;{title}&lt;/p&gt;
                              &lt;div className="flex items-center space-x-1"&gt;
                                &lt;MdEdit className="text-lg cursor-pointer" /&gt;
                                &lt;MdDelete className="text-lg cursor-pointer text-red-500" /&gt;
                              &lt;/div&gt;
                            &lt;/div&gt;
                          );
                        })
                      ) : (
                        &lt;div className="mt-6 h-full rounded-md border-dashed border-4 border-white" /&gt;
                      ))}
                  &lt;/div&gt;
                );
              })}
              {/* If the number of columns of tasks is less than 7, display an option to add more columns */}
              {columns.length &lt; 7 ? (
                &lt;div className="rounded-md bg-white w-[17.5rem] mt-12 shrink-0 flex justify-center items-center"&gt;
                  &lt;p className="cursor-pointer font-bold text-black text-2xl"&gt;
                    + New Column
                  &lt;/p&gt;
                &lt;/div&gt;
              ) : (
                ""
              )}
            &lt;/div&gt;
          ) : (
            &lt;div className="w-full h-full flex justify-center items-center"&gt;
              &lt;div className="flex flex-col items-center"&gt;
                &lt;p className="text-black text-sm"&gt;
                  This board is empty. Create a new column to get started.
                &lt;/p&gt;
                &lt;button className="bg-blue-500 text-black px-4 py-2 flex mt-6 rounded-3xl items-center space-x-2"&gt;
                  &lt;p&gt;+ Add New Column&lt;/p&gt;
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          )}
        &lt;/&gt;
      )}
    &lt;/div&gt;
  );
}
</code></pre>
<p>After you make these edits, your UI should now reflect the changes as demonstrated in the GIF below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/13.gif" alt="Populated boardTasks component" width="600" height="400" loading="lazy"></p>
<p>Next, we'll turn our attention to implementing CRUD (Create, Read, Update, and Delete) operations throughout our application.</p>
<h2 id="heading-how-to-implement-crud-operations">How to Implement CRUD Operations</h2>
<p>Before we dive into implementing CRUD functionalities throughout our app, we need to establish the <code>updateBoardToDb</code> mutation endpoint within the <code>apiSlice</code>. This endpoint will allow us to make necessary updates to our database for CRUD actions.</p>
<p>Integrate the following code into your <code>redux/services/apiSlice.ts</code> file to include the mutation endpoint:</p>
<pre><code class="lang-tsx">import { createApi, fakeBaseQuery } from "@reduxjs/toolkit/query/react";
import { getSession } from "next-auth/react";
// additionally import the doc and updateDoc method from firestore to get user document reference and update the document, respectively
import { collection, doc, getDocs, updateDoc } from "firebase/firestore";
import { db } from "@/components/app/utils/firebaseConfig";

export const fireStoreApi = createApi({
  reducerPath: "firestoreApi",
  baseQuery: fakeBaseQuery(),
  tagTypes: ["Tasks"],
  endpoints: (builder) =&gt; ({
    fetchDataFromDb: builder.query&lt;{ [key: string]: any }[], void&gt;({
      async queryFn() {
        try {
          const session = await getSession();
          if (session?.user) {
            const { user } = session;
            const ref = collection(db, `users/${user.email}/tasks`);
            const querySnapshot = await getDocs(ref);
            return { data: querySnapshot.docs.map((doc) =&gt; doc.data()) };
          }
        } catch (e) {
          return { error: e };
        }
      },
      providesTags: ["Tasks"],
    }),
    // endpoint for CRUD actions
    updateBoardToDb: builder.mutation({
      async queryFn(boardData) {
        try {
          const session = await getSession();
          if (session?.user) {
            const { user } = session;
            const ref = collection(db, `users/${user.email}/tasks`);
            const querySnapshot = await getDocs(ref);
            const boardId = querySnapshot.docs.map((doc) =&gt; {
              return doc.id;
            });
            await updateDoc(doc(db, `users/${user.email}/tasks/${boardId}`), {
              boards: boardData,
            });
          }
          return { data: null };
        } catch (e) {
          return { error: e };
        }
      },
      invalidatesTags: ["Tasks"], // this will be used to invalidate the initially fetched data. 
      // Data will have to be refetched once this enpoint has been called
    }),
  }),
});

// Export hooks for using the created endpoint
export const { useFetchDataFromDbQuery, useUpdateBoardToDbMutation } =
  fireStoreApi;
</code></pre>
<p>Upon calling the <code>useUpdateBoardToDbMutation</code> endpoint, our database data will be updated accordingly. </p>
<p>Following each update, Redux seamlessly performs background refreshes to ensure we're operating with the latest data. This functionality is enabled by the <code>invalidatesTags</code> property we passed to the <code>updateBoardToDb</code> endpoint.</p>
<p>Having successfully implemented the CRUD endpoint, our next step is to implement the features for adding and editing boards.</p>
<h3 id="heading-how-to-add-and-edit-a-board">How to add and edit a board</h3>
<p>Once we've completed the UI implementation, the modal for adding a new board should resemble the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/14.png" alt="add board modal" width="600" height="400" loading="lazy"></p>
<p>Similarly, for editing a board:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/15.png" alt="edit board modal" width="600" height="400" loading="lazy"></p>
<p>If you look at the images above, you can see that both modals share a striking resemblance, differing only in their titles. </p>
<p>This presents an excellent opportunity to implement the DRY (Don't Repeat Yourself) concept in programming. In a few steps, we'll explore how to leverage a single modal to fulfill both purposes.</p>
<p>First, we'll use the <a target="_blank" href="https://www.npmjs.com/package/react-modal"><code>react-modal</code></a>  library to create a custom modal component. This allows us to avoid building from scratch. </p>
<p>To begin, install the <code>react-modal</code> library by running the following command:</p>
<pre><code class="lang-npm"> npm i react-modal
</code></pre>
<p>Then create a <code>Modal.tsx</code> file in the <code>app/components</code> directory and add the provided code. This code defines a custom modal component with styling.</p>
<pre><code class="lang-tsx">import ReactModal from "react-modal";

interface ModalProps {
  children?: React.ReactNode;
  isOpen: boolean;
  onRequestClose: () =&gt; void;
}

ReactModal.setAppElement("*");

export function Modal({ children, isOpen, onRequestClose }: ModalProps) {
  const modalStyle = {
    overlay: {
      zIndex: "900000",
      backgroundColor: "rgba(0,0,0,0.45)",
      display: "flex",
      justifyContent: "center",
      alignItems: "center",
    },
    content: {
      top: "50%",
      left: "50%",
      right: "auto",
      bottom: "auto",
      marginRight: "-50%",
      transform: "translate(-50%, -50%)",
      padding: "0px",
      borderRadius: ".5rem",
      width: "auto",
      backgroundColor:  "#fff",
      border: "none",
    },
  };

  return (
    &lt;ReactModal
      onRequestClose={onRequestClose}
      isOpen={isOpen}
      style={modalStyle}
    &gt;
      {children}
    &lt;/ReactModal&gt;
  );
}

interface ModalBody {
  children: React.ReactNode;
}

export function ModalBody({ children }: ModalBody) {
  return &lt;form className="w-[21.4rem] md:w-[30rem] p-8"&gt;{children}&lt;/form&gt;;
}
</code></pre>
<p>In this code, we have implemented and styled the overlay and body (content) of the modal.</p>
<p>Now, create a folder named <code>AddAndEditBoardModal.tsx</code> and paste the provided code into it as a placeholder. Don't worry about the red squiggly lines you get in your code editor for now – we'll address them in a bit.</p>
<pre><code class="lang-tsx">   import { Modal, ModalBody } from "./Modal";

   export default function AddAndEditBoardModal() {

    return (
      &lt;Modal isOpen onRequestClose&gt;
        &lt;ModalBody&gt;
         &lt;p&gt;Add and Edit Board Modal&lt;/p&gt;
        &lt;/ModalBody&gt;
      &lt;/Modal&gt;
    );
   }
</code></pre>
<p>In this code, we imported our custom modal component, and we've wrapped it around a placeholder text.</p>
<p>Next, render the newly created modal component in the <code>app/page.tsx</code> component:</p>
<pre><code class="lang-tsx">   // rest of imports here
   import AddAndEditBoardModal from "./components/AddAndEditBoardModal";
   // rest of the code here
   export default function Home() {
   return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
      {/* render modal component here */}
      &lt;AddAndEditBoardModal /&gt;
    &lt;/main&gt;
   );
   }
</code></pre>
<p>In this step, we've created a placeholder for the <code>AddAndEditBoardModal</code> component and rendered it in the <code>Page.tsx</code> component. </p>
<p>Next, we'll implement the functions to trigger the modal and manage the open and close state in the redux store to maintain clean code and avoid prop drilling.</p>
<p>Navigate to your <code>redux/features/appSlice.ts</code> file and update it with the code below:</p>
<pre><code class="lang-tsx">   import { createSlice, PayloadAction } from "@reduxjs/toolkit";
   import { RootState } from "../store";

   const initialState = {
   currentBoardName: "",
   // Manage the state for opening and closing the Add and Edit Board modal
   isAddAndEditBoardModal: { isOpen: false, variant: "" },
   };

   export const features = createSlice({
    name: "features",
    initialState,

    reducers: {
     setCurrentBoardName: (state, action: PayloadAction&lt;string&gt;) =&gt; {
      state.currentBoardName = action.payload;
    },
    // Open the Add and Edit Board modal with a specified variant (add or edit)
    openAddAndEditBoardModal: (state, { payload }) =&gt; {
      state.isAddAndEditBoardModal.isOpen = true;
      // Set the kind of modal to open (add board or edit board) based on the variant parameter
      state.isAddAndEditBoardModal.variant = payload;
    },
    // Close the Add and Edit Board modal
    closeAddAndEditBoardModal: (state) =&gt; {
      state.isAddAndEditBoardModal.isOpen = false;
      state.isAddAndEditBoardModal.variant = "";
    },
   },
   });
   export const {
   setCurrentBoardName,
   openAddAndEditBoardModal,
   closeAddAndEditBoardModal,
   } = features.actions;
   export const getCurrentBoardName = (state: RootState) =&gt; state.features.currentBoardName;
   // Selector functions to retrieve isOpen value of state from the isAddAndRditBoardModal state
   export const getAddAndEditBoardModalValue = (state: RootState) =&gt; state.features.isAddAndEditBoardModal.isOpen;
   // Selector functions to retrieve isOpen value of state from the isAddAndRditBoardModal state
   export const getAddAndEditBoardModalVariantValue = (state: RootState) =&gt; state.features.isAddAndEditBoardModal.variant;
   // Export the reducer for use in the Redux store
   export default features.reducer;
</code></pre>
<p>Then, navigate back to the <code>AddAndEditBoardModal.tsx</code> component and update it as seen below:</p>
<pre><code class="lang-tsx">   import { Modal, ModalBody } from "./Modal";
   import { useAppSelector, useAppDispatch } from "@/components/redux/hooks";
   //import needed functions from the appSlice
   import {
   getAddAndEditBoardModalValue,
   getAddAndEditBoardModalVariantValue,
   closeAddAndEditBoardModal,
   } from "@/components/redux/features/appSlice";

   export default function AddAndEditBoardModal() {
   // get the variant of the modal
   const modalVariant = useAppSelector(getAddAndEditBoardModalVariantValue);
   const dispatch = useAppDispatch();
   // opens that modal is isOpen evaluates to true
   const isOpen = useAppSelector(getAddAndEditBoardModalValue);
   // close the modal
   const closeModal = () =&gt; dispatch(closeAddAndEditBoardModal());

   return (
    &lt;Modal isOpen={isOpen} onRequestClose={closeModal}&gt;
      &lt;ModalBody&gt;
        {/* display the variant(title) of the modal */}
        &lt;p&gt;{modalVariant}&lt;/p&gt;
      &lt;/ModalBody&gt;
    &lt;/Modal&gt;
   );
   }
</code></pre>
<p>Following these updates, we can safely implement the trigger for the add and edit board modal.</p>
<p>Next, navigate to the <code>Sidebar</code> component and update the button with the "+ Create new board" text so it opens the "Add Board" modal when clicked: </p>
<pre><code class="lang-tsx">   // add this to the imports
   import { openAddAndEditBoardModal } from "@/components/redux/features/appSlice";

   export default function Sidebar() {
    // rest of code here
   return (
     &lt;aside className="w-[18.75rem] flex-none dark:bg-dark-grey h-full py-6 pr-6"&gt;
       {/* rest of code here */}
       {/* trigger the create new board modal */}
       &lt;button
         onClick={() =&gt; dispatch(openAddAndEditBoardModal("Add New Board"))}
         className="flex items-center space-x-2 pl-[2.12rem] py-3"
       &gt;
         &lt;p className="text-base font-bold capitalize text-main-purple"&gt;
           + Create New Board
         &lt;/p&gt;
       &lt;/button&gt;
     &lt;/aside&gt;
   );
   }
</code></pre>
<p>Now, upon clicking the "+ Create new board" button in the sidebar, the modal containing the "Add new board" text should appear. You should also be able to close it by clicking on the overlay:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/16.gif" alt="Add new board modal pops up" width="600" height="400" loading="lazy"></p>
<p>Next, we'll implement the trigger for the edit board modal.</p>
<p>Navigate to the <code>app/components/Dropdown.tsx</code> component and update the "Edit board" button as follows:</p>
<pre><code class="lang-tsx">   import { useAppDispatch } from '@/components/redux/hooks'
   import { openAddAndEditBoardModal } from '@/components/redux/features/appSlice';

   interface IDropdown {
    show: boolean
   }

   export default function Dropdown({ show }: IDropdown) {

    const dispatch = useAppDispatch()

    return (
      &lt;div
        className={`${
          show ? "block" : "hidden"
        } w-48 absolute top-full bg-white
         border shadow-lg right-0 py-2 rounded-2xl`}
      &gt;
        &lt;div className="hover:bg-gray-300"&gt;
        {/* trigger Edit Board modal here */}
          &lt;button
           onClick={() =&gt; dispatch(openAddAndEditBoardModal('Edit Board'))}
           className="text-sm px-4 py-2"&gt;Edit Board&lt;/button&gt;
        &lt;/div&gt;
        &lt;div className="hover:bg-gray-300"&gt;
          &lt;button className="text-sm px-4 py-2"&gt;
            Delete Board
          &lt;/button&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    );
   }
</code></pre>
<p>After making this update, clicking on the "Edit board" button in the dropdown will open the edit board modal, as illustrated in the GIF below:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/17.gif" alt="Edit board modal pops up" width="600" height="400" loading="lazy"></p>
<p>The option to add a new column to the <code>BoardTasks</code> component should also open this modal when clicked. So navigate to the <code>BoardTasks</code> component and import the <code>openAddEditBoardModal</code> function and <code>useAppDispatch</code> hook from <code>appSlice</code> and redux hooks, respectively. </p>
<p>Then declare the dispatch function in the component with this statement: <code>const dispatch = useAppDispatch()</code></p>
<p>Finally, update the "+New Column" <code>div</code> element to open the "Edit board" modal when clicked:</p>
<pre><code class="lang-tsx">   // rest of the code 
    &lt;div
    onClick={() =&gt; dispatch(openAddAndEditBoardModal("Edit Board"))
    className="rounded-md bg-white w-[17.5rem] mt-12 shrink-0 flex justify-center items-center"&gt;
   &lt;p className="cursor-pointer font-bold text-black text-2xl"&gt;  + New Column &lt;/p&gt;
   &lt;/div&gt;
   //rest of the code
</code></pre>
<p>After these updates, the "Edit board" modal should open up when the "+New Column" card is clicked:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/18.gif" alt="Edit board modal pops up" width="600" height="400" loading="lazy"></p>
<p>In the upcoming steps, we'll construct the complete markup and functionalities for our modal.</p>
<p>Referring to the images of both modals presented at the start of this section, in the "Add New Board" modal, the fields for board and column names should be blank. In contrast, the "Edit Board" modal should display the existing name and columns of the board and should be editable.</p>
<p>The "+ Add New Column" button in both modals allows the addition of more fields to the board's columns, and subsequently, the updated data is sent to the database.  </p>
<p>Keep in mind that, given the frontend-centric nature of this project, a significant portion of the business logic will be handled on the front-end. However, don’t worry; we will take this snippet by snippet until we completely implement all features.</p>
<p>To begin, update the <code>AddAndEditBoardModal</code> component by pasting the code below:</p>
<pre><code class="lang-tsx">import { useState, useEffect } from "react";
import { Modal, ModalBody } from "./Modal";
import { useAppSelector, useAppDispatch } from "@/components/redux/hooks";
//import needed functions from the appSlice
import {
  getAddAndEditBoardModalValue,
  getAddAndEditBoardModalVariantValue,
  closeAddAndEditBoardModal,
  getCurrentBoardName,
} from "@/components/redux/features/appSlice";
import {
  useFetchDataFromDbQuery,
  useUpdateBoardToDbMutation,
} from "@/components/redux/services/apiSlice";
import { FaTimes } from "react-icons/fa";
import { id } from '../utils/data'
// define types for boarddata
interface IBoardData {
  id: string,
  name: string;
  columns: {
    id: string;
    name: string;
    columns?: { name: string; tasks?: { [key: string]: any }[] };
  }[];
}
// dummy add board data for the "Add board" modal
let addBoardData = {
  id: id(),
  name: "",
  columns: [
    {
      id: id(),
      name: "",
      tasks:
 [],
    },
  ],};

export default function AddAndEditBoardModal() {
// rest of the code
}
</code></pre>
<p>Here, we have made the necessary imports and defined a type for board data - which we will use when populating the modal. We also implemented dummy data for the add board modal. We will see how this will be of use in a bit. </p>
<p>Next, go to the <code>AddAndEditBoardModal</code> function and paste the following code into it to declare variables and state values. The comments explain the future use of each of the declarations.</p>
<pre><code class="lang-tsx"> //manage the board data state
  const [boardData, setBoardData] = useState&lt;IBoardData&gt;();
  // check if the board name field is empty
  const [isBoardNameEmpty, setIsBoardNameEmpty] = useState&lt;boolean&gt;(false);
  // will be used to check if any of the board column field is empty
  const [emptyColumnIndex, setEmptyColumnIndex] = useState&lt;number&gt;();

  // get the variant of the modal
  const modalVariant = useAppSelector(getAddAndEditBoardModalVariantValue);
  // check the type of the open modal, whether Add new board, or Edit board
  const isVariantAdd = modalVariant === "Add New Board";
  const dispatch = useAppDispatch();
  // opens that modal if isOpen evaluates to true
  const isOpen = useAppSelector(getAddAndEditBoardModalValue);
  const currentBoardTitle = useAppSelector(getCurrentBoardName);
  // close the modal
  const closeModal = () =&gt; dispatch(closeAddAndEditBoardModal());
  // Fetch data from the database to populate the edit board modal
  let { data } = useFetchDataFromDbQuery();
  // Mutation hook for updating the board in the database
  const [updateBoardToDb, { isLoading }] = useUpdateBoardToDbMutation();
</code></pre>
<p>Here, we’ll implement the functions that will be responsible for the modal’s functionality. Paste the following code just below the declarations above:</p>
<pre><code class="lang-tsx">  // Effect to set initial data for the modal based on the variant
  useEffect(() =&gt; {
    if (data) {

      if (isVariantAdd) {
        setBoardData(addBoardData);
      } else {
        const activeBoard = data[0].boards.find(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        setBoardData(activeBoard);
      }
    }
  }, [data, modalVariant]);

  // Effect to clear error messages after a certain time
  useEffect(() =&gt; {
    const timeoutId = setTimeout(() =&gt; {
      setIsBoardNameEmpty(false);
      setEmptyColumnIndex(undefined);
    }, 3000);
    return () =&gt; clearTimeout(timeoutId);
  }, [emptyColumnIndex, isBoardNameEmpty]);

  // Handler for board name change
  const handleBoardNameChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (boardData) {
      const newName = { ...boardData, name: e.target.value };
      setBoardData(newName);
    }
  };

  // Handler for column name change. These kind of functions are called closures

  const handleColumnNameChange = (index: number) =&gt; {
    return function (e: React.ChangeEvent&lt;HTMLInputElement&gt;) {
      // handle change for create new board modal
      if (boardData) {
        const modifyColumns = boardData.columns.map((column, columnIndex) =&gt; {
          if (columnIndex === index) {
            return { ...column, name: e.target.value };
          }
          return column;
        });
        const modifiedColumn = { ...boardData, columns: modifyColumns };
        setBoardData(modifiedColumn);
      }
    };
  };

  // Handler for adding a new column to the form
  const handleAddNewColumn = () =&gt; {
    // max columns we want to have in a board is 7
    if (boardData &amp;&amp; boardData.columns.length &lt; 6) {
      // Make a copy of the existing boardData
      const updatedBoardData = { ...boardData };
      // Create a new column object
      const newColumn = { id: id(), name: "", tasks: [] };
      // Push the new column to the columns array in the copy
      updatedBoardData.columns = [...updatedBoardData.columns, newColumn];
      // Update the state with the modified copy
      setBoardData(updatedBoardData);
    }
  };

  // Handler for deleting a column in the form
  const handleDeleteColumn = (index: number) =&gt; {
    if (boardData) {
      const filteredColumns = boardData.columns.filter(
        (_column, columnIndex) =&gt; columnIndex !== index
      );
      setBoardData({ ...boardData, columns: filteredColumns });
    }
  };

  // Handler for adding a new board to the database
  const handleAddNewBoardToDb = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
    e.preventDefault();

    // check if any of the column names are empty before submiting
    const emptyColumnStringChecker = boardData?.columns.some(
      (column) =&gt; column.name === ""
    ); 

    //condition to run if the board name is empty
    if (boardData?.name === "") {
      setIsBoardNameEmpty(true);
    }

    //if any of the column names is empty, update the emptyColumnIndex with its index
    if (emptyColumnStringChecker) {
      const emptyColumn = boardData?.columns.findIndex(
        (column) =&gt; column.name == ""
      );
      setEmptyColumnIndex(emptyColumn);
    }

    if (boardData?.name !== "" &amp;&amp; !emptyColumnStringChecker) {
      //submit to the database after verifying that the board name and none of the column names aren't empty
      if (data) {
        let [boards] = data;
        const addBoard = [...boards.boards, boardData];
        boards = addBoard;
        updateBoardToDb(boards);
      }
    }
  };

  // Handler for editing a board in the database
  const handleEditBoardToDb = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
    e.preventDefault();
    const emptyColumnStringChecker = boardData?.columns.some(
      (column) =&gt; column.name === ""
    );
    //condition to run if the board name is empty
    if (boardData?.name === "") {
      setIsBoardNameEmpty(true);
    }
    //if any of the column names is empty, update the emptyColumnIndex with its index
    if (emptyColumnStringChecker) {
      const emptyColumn = boardData?.columns.findIndex(
        (column) =&gt; column.name == ""
      );
      setEmptyColumnIndex(emptyColumn);
    }
    //submit to the database after verifying that the board name and none of the column names aren't empty
    if (boardData?.name !== "" &amp;&amp; !emptyColumnStringChecker) {
      if (data) {
        const [boards] = data;
        const boardsCopy = [...boards.boards]; 
        const activeBoardIndex = boardsCopy.findIndex(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const updatedBoard = {
          ...boards.boards[activeBoardIndex],
          name: boardData!.name,
          columns: boardData!.columns,
        } ;
        boardsCopy[activeBoardIndex] = updatedBoard;
        updateBoardToDb(boardsCopy);
      }
    }
  };
</code></pre>
<p>Finally, update the return statement of the component by pasting the below code snippet into it:</p>
<pre><code class="lang-tsx">return (
    &lt;Modal isOpen={isOpen} onRequestClose={closeModal}&gt;
      &lt;ModalBody&gt;
        {boardData &amp;&amp; (
          &lt;&gt;
            {/* display the variant(title) of the modal */}
            &lt;p className="text-lg font-bold"&gt;{modalVariant}&lt;/p&gt;
            &lt;div className="py-6"&gt;
              &lt;div&gt;
                &lt;label htmlFor="boardName" className="text-sm"&gt;
                  Board Name
                &lt;/label&gt;
                &lt;div className="pt-2"&gt;
                  &lt;input
                    id="boardName"
                    className={`${
                      isBoardNameEmpty ? "border-red-500" : "border-stone-200"
                    } border w-full p-2 rounded text-sm cursor-pointer focus:outline-none`}
                    placeholder="Name"
                    value={boardData.name}
                    onChange={handleBoardNameChange}
                  /&gt;
                &lt;/div&gt;
                {/* display this error if the board name is empty */}
                {isBoardNameEmpty ? (
                  &lt;p className="text-xs text-red-500"&gt;
                    Board name cannot be empty
                  &lt;/p&gt;
                ) : (
                  ""
                )}
              &lt;/div&gt;

              &lt;div className="mt-6"&gt;
                &lt;label htmlFor="" className="text-sm"&gt;
                  Board Column
                &lt;/label&gt;
                {boardData &amp;&amp;
                  boardData.columns.map(
                    (column: { name: string, id: string }, index: number) =&gt; {
                      let { name, id } = column;
                      return (
                        &lt;div key={id} className="pt-2"&gt;
                          &lt;div className="flex items-center space-x-2"&gt;
                            &lt;input
                              className={`${
                                emptyColumnIndex === index
                                  ? "border-red-500"
                                  : "border-stone-200"
                              } border border-stone-200 focus:outline-none text-sm cursor-pointer w-full p-2 rounded`}
                              placeholder="e.g Doing"
                              onChange={(e) =&gt; handleColumnNameChange(index)(e)}
                              value={name!}
                            /&gt;
                            &lt;div&gt;
                              &lt;FaTimes
                                onClick={() =&gt; handleDeleteColumn(index)}
                              /&gt;
                            &lt;/div&gt;
                          &lt;/div&gt;
                          {/* display this error if the board name is empty */}
                          {emptyColumnIndex === index ? (
                            &lt;p className="text-xs text-red-500"&gt;
                              Column name cannot be empty
                            &lt;/p&gt;
                          ) : (
                            ""
                          )}
                        &lt;/div&gt;
                      );
                    }
                  )}
                &lt;div className="mt-3"&gt;
                  &lt;button
                    type="button"
                    onClick={handleAddNewColumn}
                    className="bg-stone-200 rounded-3xl py-2 w-full text-sm font-bold"
                  &gt;
                    &lt;p&gt;+ Add New Column&lt;/p&gt;
                  &lt;/button&gt;
                &lt;/div&gt;
              &lt;/div&gt;
              &lt;div className="pt-6"&gt;
                &lt;button
                  type="submit"
                  onClick={(e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
                    // function to run depending on the variant of the modals
                    isVariantAdd
                      ? handleAddNewBoardToDb(e)
                      : handleEditBoardToDb(e);
                  }}
                  className="bg-blue-500 rounded-3xl py-2 w-full text-sm font-bold"
                &gt;
                  {/* text to display depending on the variant of the modal */}
                  &lt;p&gt;
                    {isLoading
                      ? "Loading"
                      : `${isVariantAdd ? "Create New Board" : "Save Changes"}`}
                  &lt;/p&gt;
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          &lt;/&gt;
        )}
      &lt;/ModalBody&gt;
    &lt;/Modal&gt;
  );
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/19.gif" alt="Add board to the database" width="600" height="400" loading="lazy"></p>
<p>In the above GIF, we introduced a "Marketing" board with "Todo" and "Doing" columns to our app. You can also see the real-time update of the boards in the sidebar.</p>
<p>Likewise, you can perform edits on a board:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/20.gif" alt="Edit board" width="600" height="400" loading="lazy"></p>
<p>Here, a new column, "After," was added to the "Roadmap" board.</p>
<p>In the upcoming section, we will implement the "Add new task" and "Edit task" functionalities.</p>
<h3 id="heading-how-to-add-and-edit-tasks">How to add and edit tasks</h3>
<p>Once you've completed this section, the "Add New Task" modal should resemble the following:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/21.png" alt="Add new task complete modal" width="600" height="400" loading="lazy"></p>
<p>Similarly, for the "Edit Task" modal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/22.png" alt="Edit task complete modal" width="600" height="400" loading="lazy"></p>
<p>You'll see that these modals share similarities, so we will implement them using the same approach employed in the previous section.</p>
<p>We'll start by updating the <code>initialState</code> object in our <code>appSlice</code> to manage the state of the "Add and Edit tasks" modal. </p>
<pre><code class="lang-ts">   <span class="hljs-keyword">const</span> initialState = {
   <span class="hljs-comment">//add and edit tasks modal state</span>
   isAddAndEditTaskModal: { isOpen: <span class="hljs-literal">false</span>, variant: <span class="hljs-string">""</span>, title: <span class="hljs-string">""</span>, index: <span class="hljs-number">-1</span>, name: <span class="hljs-string">""</span>},
   };
</code></pre>
<p>The keys <code>title</code> and <code>index</code> will respectively store the title and index of the task being edited, while the <code>name</code> key will retrieve the name of the task's column. We'll explore how to utilize this information to edit a task in the upcoming steps.</p>
<p>Next, include the following functions in the <code>reducers</code> object. These will be the functions that will be called to open and close the modal:</p>
<pre><code class="lang-ts">    <span class="hljs-comment">// Open the Add and Edit task modal with a specified variant (add or edit), title, description, status</span>
    openAddAndEditTaskModal: <span class="hljs-function">(<span class="hljs-params">state, { payload }</span>) =&gt;</span> {
      state.isAddAndEditTaskModal.isOpen = <span class="hljs-literal">true</span>;
      state.isAddAndEditTaskModal.variant = payload.variant;
      state.isAddAndEditTaskModal.title = payload.title;
      state.isAddAndEditTaskModal.index = payload.index;
     state.isAddAndEditTaskModal.name = payload.name;
    },
    <span class="hljs-comment">// Close the Add and Edit task modal</span>
    closeAddAndEditTaskModal: <span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> {
      state.isAddAndEditTaskModal.isOpen = <span class="hljs-literal">false</span>;
      state.isAddAndEditTaskModal.variant = <span class="hljs-string">""</span>;
      state.isAddAndEditTaskModal.title = <span class="hljs-string">""</span>;
      state.isAddAndEditTaskModal.index = <span class="hljs-string">""</span>;
      state.isAddAndEditTaskModal.name = <span class="hljs-string">""</span>;
    },
</code></pre>
<p>Lastly, include the newly implemented functions and the selector functions in the exports:</p>
<pre><code class="lang-ts">   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> {
   openAddAndEditTaskModal,
   closeAddAndEditTaskModal,
   <span class="hljs-comment">//rest of the imports</span>
   } = features.actions;

   <span class="hljs-comment">// Selector function to retrieve isOpen state value  </span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.isOpen;
   <span class="hljs-comment">// Selector function to retrieve variant state value </span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalVariantValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.variant;
   <span class="hljs-comment">// Selector function to retrieve title state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalTitleValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.title;
   <span class="hljs-comment">// Selector function to retrieve index state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalIndexValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.index;
   <span class="hljs-comment">// Selector function to retrieve name state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getAddAndEditTaskModalNameValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isAddAndEditTaskModal.name;
   <span class="hljs-comment">//rest of the imports</span>
</code></pre>
<p>Now, we'll implement the <code>onClick</code> functions that enable users to interact with the modal and perform task-related actions. These functions will allow users to open the "Add new task" modal from the navbar and the "Edit task" modal by clicking the edit icon within individual task cards.</p>
<p>In the <code>components/Navbar</code>, include the <code>openAddAndEditTaskModal</code> among the imported functions from the <code>appSlice</code>:</p>
<pre><code class="lang-ts"> <span class="hljs-keyword">import</span> { setCurrentBoardName, getCurrentBoardName, openAddAndEditTaskModal } <span class="hljs-keyword">from</span> <span class="hljs-string">'../../redux/features/appSlice'</span>
</code></pre>
<p>Then, modify the "+Add new task" button to incorporate an <code>onClick</code> function that triggers the "Add new task" modal:</p>
<pre><code class="lang-tsx">    &lt;button 
     type='button'
     onClick={() =&gt; dispatch(openAddAndEditTaskModal({variant: 'Add New Task'}))}
     className="bg-blue-500 text-black px-4 py-2 flex rounded-3xl items-center space-x-2"&gt;
         &lt;p&gt;+ Add New Task&lt;/p&gt;
    &lt;/button&gt;
</code></pre>
<p>Next, navigate to the <code>BoardTasks</code> component, where we will also implement the trigger for the "Edit task" modal.</p>
<p>Here, include the <code>openAddAndEditTaskModal</code> function among the imported functions from the <code>appSlice</code>:</p>
<pre><code class="lang-tsx">import { openAddAndEditBoardModal, openAddAndEditTaskModal } from "@/components/redux/features/appSlice";
</code></pre>
<p>Then, update the <code>&lt;MdEdit/&gt;</code> React icon to incorporate the <code>onClick</code> function that triggers the "Edit Task" modal:</p>
<pre><code class="lang-tsx">    &lt;MdEdit
    onClick={() =&gt;
    dispatch(
      openAddAndEditTaskModal({
        variant: "Edit Task", title, index, name
      }),
    )
   }
   className="text-lg cursor-pointer"
   /&gt;;
</code></pre>
<p>Next, we'll create the Add and Edit Board modal component, also integrating its functionalities.</p>
<p>As depicted in the modal images presented at the start of this section, within the "Add New Task" modal, the title field is intended for the task's title a user wishes to add, and the status field should exclusively contain the accurate names of the columns. Any attempt to input a column name that doesn't exist will result in an error.</p>
<p>In the "Edit Task" modal, the title and status fields will display the current title and status of a task. Altering the title will update the task's title while modifying the status will relocate it to the desired column.</p>
<p>To begin, within your <code>src/app/components</code> directory, create a file named <code>AddAndEditTaskModal.tsx</code>, and firstly, insert the provided code to make the necessary imports, type definitions, and initial data for the add task modal:</p>
<pre><code class="lang-tsx"> "use client";

import { useEffect, useState } from "react";
import { Modal, ModalBody } from "./Modal";
import { useAppDispatch, useAppSelector } from "@/components/redux/hooks";
import {
  getAddAndEditTaskModalValue,
  getAddAndEditTaskModalVariantValue,
  getAddAndEditTaskModalTitle,
  closeAddAndEditTaskModal,
  getCurrentBoardName,
  getAddAndEditTaskModalIndex,
  getAddAndEditTaskModalName,
} from "@/components/redux/features/appSlice";
import {
  useFetchDataFromDbQuery,
  useUpdateBoardToDbMutation,
} from "@/components/redux/services/apiSlice";
import { id } from '../utils/data'

interface ITaskData {
  id: string,
  title: string;
  status: string;
}
// initial task data for the add task modal
let initialTaskData: ITaskData = {
  id: id(),
  title: "",
  status: "",
};

export default function AddOrEditTaskModal() {
//variable declarations, functions, JSX
}
</code></pre>
<p>Next, go to the <code>AddAndEditTaskModal</code> function and paste the following code into it to declare variables and state values. The comments provided explain the future use of each of the declarations.</p>
<pre><code class="lang-tsx">  let { data } = useFetchDataFromDbQuery();
  let [updateBoardToDb, { isLoading }] = useUpdateBoardToDbMutation();
  const [taskData, setTaskData] = useState&lt;ITaskData&gt;();
  const [isTaskTitleEmpty, setIsTaskTitleEmpty] = useState&lt;boolean&gt;();
  const [isTaskStatusEmpty, setIsTaskStatusEmpty] = useState&lt;boolean&gt;();
  const [statusExists, setStatusExists] = useState&lt;boolean&gt;(true);
  const [columnNames, setColumnNames] = useState&lt;[]&gt;();
  const dispatch = useAppDispatch();
  const isModalOpen = useAppSelector(getAddAndEditTaskModalValue);
  const modalVariant = useAppSelector(getAddAndEditTaskModalVariantValue);
  const isVariantAdd = modalVariant === "Add New Task";
  const closeModal = () =&gt; dispatch(closeAddAndEditTaskModal());
  const currentBoardTitle = useAppSelector(getCurrentBoardName);
  // get task title, index and name from redux store
  const currentTaskTitle = useAppSelector(getAddAndEditTaskModalTitle);
  const currentTaskIndex = useAppSelector(getAddAndEditTaskModalIndex);
  const initialTaskColumn = useAppSelector(getAddAndEditTaskModalName);
</code></pre>
<p>Here, we’ll implement functions responsible for the modal functionality. Just below the variable definitions above, paste the following functions:</p>
<pre><code class="lang-tsx">  // Effect to set initial data for the modal based on the variant
  useEffect(() =&gt; {
    if (data) {
      const activeBoard = data[0].boards.find(
        (board: { name: string }) =&gt; board.name === currentBoardTitle
      );
      if (activeBoard) {
        const { columns } = activeBoard;
        const columnNames = columns.map(
          (column: { name: string }) =&gt; column.name
        );

        if (columnNames) {
          setColumnNames(columnNames);
        }

        if (isVariantAdd) {
          setTaskData(initialTaskData);
        }

        else {
          const activeTask = columns
            .map((column: { tasks: [] }) =&gt; column.tasks)
            .flat()
            .find((task: { title: string }) =&gt; task.title === currentTaskTitle);
          setTaskData(activeTask);
        }
      }
    }
  }, [data, modalVariant]);

  // Effect to clear error messages after a certain time
  useEffect(() =&gt; {
    const timeoutId = setTimeout(() =&gt; {
      setIsTaskStatusEmpty(false);
      setIsTaskStatusEmpty(false);
      setStatusExists(true);
    }, 3000);
    return () =&gt; clearTimeout(timeoutId);
  }, [isTaskStatusEmpty, isTaskTitleEmpty, statusExists]);

  // Handler for task title change
  const handleTaskTitleChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (taskData) {
      const newTitle = { ...taskData, title: e.target.value };
      setTaskData(newTitle);
    }
  };

  // Handler for task status change
  const handleTaskStatusChange = (e: React.ChangeEvent&lt;HTMLInputElement&gt;) =&gt; {
    if (taskData) {
      const newTitle = { ...taskData, status: e.target.value };
      setTaskData(newTitle);
    }
  };

  // Handler to add new task to the db
  const handleAddNewTaskToDb = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {

    e.preventDefault();
    const { title, status } = taskData!;

    if (!title) {
      setIsTaskTitleEmpty(true);
    }

    if (!status) {
      setIsTaskStatusEmpty(true);
    }

    // check if the status input exists among the existing columns
    const doesStatusExists = columnNames?.some(
      (column) =&gt; column === taskData?.status
    );

    if (!doesStatusExists) {
      setStatusExists(false);
    }

    // if all conditions are met
    if (title &amp;&amp; status &amp;&amp; doesStatusExists) {
      if (data) {
        const [boards] = data;
        const boardsCopy = [...boards.boards];
        const activeBoard = boardsCopy.find(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const activeBoardIndex = boardsCopy.findIndex(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const { columns } = activeBoard;
        // find the column in the board to update
        const getStatusColumn = columns?.find(
          (column: { name: string }) =&gt; column.name === status
        );
        const getStatusColumnIndex = columns?.findIndex(
          (column: { name: string }) =&gt; column.name === status
        );
        // desctructure tasks in a column. "Now" for example.
        const { tasks } = getStatusColumn;
        const addNewTask = [...tasks, { id: id(), title, status }]; //add new task
        const updatedStatusColumn = { ...getStatusColumn, tasks: addNewTask };
        //update the columns in a board
        const columnsCopy = [...columns];
        columnsCopy[getStatusColumnIndex] = updatedStatusColumn;
        const updatedBoard = {
          ...boards.boards[activeBoardIndex],
          columns: columnsCopy,
        };
        //update the board in the db
        boardsCopy[activeBoardIndex] = updatedBoard;
        updateBoardToDb(boardsCopy);
      }
    }
  };

  const handleEditTaskToDb = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
    e.preventDefault();
    const { title, status } = taskData!;
    if (!title) {
      setIsTaskTitleEmpty(true);
    }
    if (!status) {
      setIsTaskStatusEmpty(true);
    }
    // check if the status input exists among the existing status
    const doesStatusExists = columnNames?.some(
      (column) =&gt; column === taskData?.status
    );
    if (!doesStatusExists) {
      setStatusExists(false);
    }
    if (title &amp;&amp; status &amp;&amp; doesStatusExists) {
      if (data) {
        const [boards] = data;
        const boardsCopy = [...boards.boards];
        const activeBoard = boardsCopy.find(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const activeBoardIndex = boardsCopy.findIndex(
          (board: { name: string }) =&gt; board.name === currentBoardTitle
        );
        const { columns } = activeBoard;
        const getStatusColumnIndex = columns?.findIndex(
          (column: { name: string }) =&gt; column.name === status
        );

        // Check if the task status to edit is equal to the column.name
        if (status === initialTaskColumn) {
          const updatedStatusColumn = {
            ...columns[getStatusColumnIndex],
            tasks: columns[getStatusColumnIndex]?.tasks?.map(
              (task: any, index: number) =&gt; {
                if (index === currentTaskIndex) {
                  return { title, status };
                }
                return task;
              }
            ),
          };
          const columnsCopy = [...columns];
          columnsCopy[getStatusColumnIndex] = updatedStatusColumn;
          const updatedBoard = {
            ...boards.boards[activeBoardIndex],
            columns: columnsCopy,
          };
          //update the board in the db
          boardsCopy[activeBoardIndex] = updatedBoard;
          updateBoardToDb(boardsCopy);
        } else {
          // Find the column with the name in the task status and append the edited task
          const getStatusColumn = columns?.find(
            (column: { name: string }) =&gt; column.name === status
          );
          // delete task from previous column
          const getPrevStatusColumn = columns?.find(
            (column: { name: string }) =&gt; column.name === initialTaskColumn
          );
          const getPrevStatusColumnIndex = columns?.findIndex(
            (column: { name: string }) =&gt; column.name === initialTaskColumn
          );
          //update the previous column of the task
          const updatedPrevStatusColumn = {
            ...getPrevStatusColumn,
            tasks: getPrevStatusColumn?.tasks.filter(
              (_task: [], index: number) =&gt; index !== currentTaskIndex
            ),
          };
          // update the new column of the task
          const updatedStatusColumn = {
            ...getStatusColumn,
            tasks: [...getStatusColumn?.tasks, { title, status }],
          };
          const columnsCopy = [...columns];
          columnsCopy[getStatusColumnIndex] = updatedStatusColumn;
          columnsCopy[getPrevStatusColumnIndex] = updatedPrevStatusColumn;
          const updatedBoard = {
            ...boards.boards[activeBoardIndex],
            columns: columnsCopy,
          };
          //update the board in the db
          boardsCopy[activeBoardIndex] = updatedBoard;
          updateBoardToDb(boardsCopy);
        }
      }
    }
  };
</code></pre>
<p>Finally, in this component, paste the code below to implement the JSX of the modal:</p>
<pre><code class="lang-tsx">return (
    &lt;Modal isOpen={isModalOpen} onRequestClose={closeModal}&gt;
      &lt;ModalBody&gt;
        &lt;p className="font-bold text-lg"&gt;{modalVariant}&lt;/p&gt;
        &lt;div className="py-6"&gt;
          &lt;div&gt;
            &lt;label htmlFor="title" className="text-sm"&gt;
              Title
            &lt;/label&gt;
            &lt;div className="pt-2"&gt;
              &lt;input
                id="title"
                className={`${
                  isTaskTitleEmpty ? "border-red-500" : "border-stone-200"
                } border w-full p-2 rounded text-sm cursor-pointer focus:outline-none`}
                placeholder="Name"
                value={taskData?.title}
                onChange={handleTaskTitleChange}
              /&gt;
            &lt;/div&gt;
            {isTaskTitleEmpty ? (
              &lt;p className="text-xs text-red-500"&gt;Task title cannot be empty&lt;/p&gt;
            ) : (
              ""
            )}
          &lt;/div&gt;

          &lt;div className="mt-3"&gt;
            &lt;label htmlFor="status" className="text-sm"&gt;
              Status
            &lt;/label&gt;
            &lt;div className="pt-2"&gt;
              &lt;input
                id="status"
                className={`${
                  isTaskStatusEmpty || !statusExists
                    ? "border-red-500"
                    : "border-stone-200"
                } border w-full p-2 rounded text-sm cursor-pointer focus:outline-none`}
                placeholder={columnNames?.join(", ")}
                value={taskData?.status}
                onChange={handleTaskStatusChange}
              /&gt;
            &lt;/div&gt;
            {isTaskStatusEmpty ? (
              &lt;p className="text-xs text-red-500"&gt;
                Task status cannot be empty
              &lt;/p&gt;
            ) : !statusExists ? (
              &lt;p className="text-xs text-red-500"&gt;Column does not exist&lt;/p&gt;
            ) : (
              ""
            )}
          &lt;/div&gt;
          &lt;div className="pt-6"&gt;
            &lt;button
              type="submit"
              onClick={(e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
                // function to run depending on the variant of the modals
                isVariantAdd ? handleAddNewTaskToDb(e) : handleEditTaskToDb(e);
              }}
              className="bg-blue-500 rounded-3xl py-2 w-full text-sm font-bold"
            &gt;
              &lt;p&gt;
                {isLoading
                  ? "Loading"
                  : `${isVariantAdd ? "Create Task" : "Save Changes"}`}
              &lt;/p&gt;
            &lt;/button&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/ModalBody&gt;
    &lt;/Modal&gt;
  );
</code></pre>
<p>Lastly, import and render the component in your <code>src/app/page.tsx</code> file as seen below:</p>
<pre><code class="lang-tsx">   //rest of the imports
   import AddAndEditTaskModal from "./components/AddAndEditTaskModal";
     //rest of the code
     return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
      &lt;AddAndEditBoardModal /&gt;
      &lt;AddAndEditTaskModal/&gt;  //render here
    &lt;/main&gt;
    );
</code></pre>
<p>With this functionality, you can effortlessly add tasks to any desired columns. For instance, let's add a new task titled "Buy tomatoes" to the "Next" column:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/23.gif" alt="Add buy tomatoes task to the board" width="600" height="400" loading="lazy"></p>
<p>Likewise, we'll illustrate the task-editing feature by changing the column of "Launch version two" from "Now" to "Later":</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/24.gif" alt="Edit task column" width="600" height="400" loading="lazy"></p>
<p>Finally, in the next section, we'll implement the delete functionalities for both boards and tasks. </p>
<h3 id="heading-how-to-delete-boards-and-tasks">How to delete boards and tasks</h3>
<p>By the end of this section, the "Delete Board" modal should look like this:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/25.png" alt="Delete board markup" width="600" height="400" loading="lazy"></p>
<p>Likewise, the "Delete Task" modal:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/26.png" alt="Delete task markup" width="600" height="400" loading="lazy"></p>
<p>As you can see, these modals share similarities, so we will use the same methodology as we did for the previous modal implementations.</p>
<p>To begin, let's update the <code>initialState</code> object in our <code>appSlice</code> to manage the state of the "Delete Board and Tasks" modal. Integrate the <code>isDeleteBoardAndTaskModal</code> state into the <code>initialState</code> object as illustrated below:</p>
<pre><code class="lang-ts">    <span class="hljs-keyword">const</span> initialState = {
      <span class="hljs-comment">//rest of the state</span>
      isDeleteBoardAndTaskModal: { isOpen: <span class="hljs-literal">false</span>, variant: <span class="hljs-string">""</span>,  title:<span class="hljs-string">''</span>, status: <span class="hljs-string">""</span>, index: <span class="hljs-number">-1</span> },
</code></pre>
<p>Next, include the following functions in the <code>reducers</code> object. These functions will be invoked to open and close the modal:</p>
<pre><code class="lang-ts">    <span class="hljs-comment">// Open the delete board and task modal with a specified variant (delete board or task)</span>
   openDeleteBoardAndTaskModal: <span class="hljs-function">(<span class="hljs-params">state, { payload }</span>) =&gt;</span> {
      state.isDeleteBoardAndTaskModal.isOpen = <span class="hljs-literal">true</span>;
      state.isDeleteBoardAndTaskModal.variant = payload.variant;
      state.isDeleteBoardAndTaskModal.title = payload.title;
      state.isDeleteBoardAndTaskModal.status = payload.status;
      state.isDeleteBoardAndTaskModal.index = payload.index;
    },
   <span class="hljs-comment">// Close the delete board and task modal</span>
   closeDeleteBoardAndTaskModal: <span class="hljs-function">(<span class="hljs-params">state</span>) =&gt;</span> {
      state.isDeleteBoardAndTaskModal.isOpen = <span class="hljs-literal">false</span>;
      state.isDeleteBoardAndTaskModal.variant = <span class="hljs-string">""</span>;
      state.isDeleteBoardAndTaskModal.title = <span class="hljs-string">""</span>;
      state.isDeleteBoardAndTaskModal.status = <span class="hljs-string">""</span>;
      state.isDeleteBoardAndTaskModal.index = <span class="hljs-number">-1</span>;
    },
</code></pre>
<p>Lastly, include the newly implemented functions and the selector functions in the exports:</p>
<pre><code class="lang-ts"> <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> {
    openDeleteBoardAndTaskModal,
    closeDeleteBoardAndTaskModal,
   } = features.actions;

   <span class="hljs-comment">// Delete task and board</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.isOpen;
   <span class="hljs-comment">// Selector function to retrieve variant state value </span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalVariantValue = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.variant;
   <span class="hljs-comment">// Selector function to retrieve title state value </span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalTitle = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.title;
   <span class="hljs-comment">// Selector function to retrieve status state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalStatus = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.status;
   <span class="hljs-comment">// Selector function to retrieve index state value</span>
   <span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getDeleteBoardAndTaskModalIndex = <span class="hljs-function">(<span class="hljs-params">state: RootState</span>) =&gt;</span> state.features.isDeleteBoardAndTaskModal.index;
</code></pre>
<p>Following that, we'll implement the <code>onClick</code> functions to enable users to interact with the modal and execute delete-related actions. These functions will permit users to open the "Delete board" modal from the dropdown in the navbar and the "Delete task" modal by clicking the delete icon within individual task cards.</p>
<p>In the <code>components/Dropdown.tsx</code> file, add the <code>openDeleteBoardAndTaskModal</code> function to the list of imported functions from the <code>appSlice</code>:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> { openDeleteBoardAndTaskModal } <span class="hljs-keyword">from</span> <span class="hljs-string">'@/components/redux/features/appSlice'</span>;
</code></pre>
<p>Then adjust the "Delete board" button to incorporate the <code>onClick</code> function to open the modal. This action will trigger the “Delete board” modal:</p>
<pre><code class="lang-ts">      &lt;div className=<span class="hljs-string">"hover:bg-gray-300"</span>&gt;
         &lt;button
            onClick={<span class="hljs-function">() =&gt;</span> dispatch(openDeleteBoardAndTaskModal({variant: <span class="hljs-string">"Delete this board?"</span>}))}
            className=<span class="hljs-string">"text-sm px-4 py-2"</span>&gt;
            Delete Board
         &lt;/button&gt;
      &lt;/div&gt;
</code></pre>
<p>Move on to the <code>BoardTasks</code> component, and similarly, include the function for deleting tasks and boards among the imports from the <code>appSlice</code>:</p>
<pre><code class="lang-ts">      <span class="hljs-keyword">import</span> {
         <span class="hljs-comment">//other imports</span>
         openDeleteBoardAndTaskModal
      } <span class="hljs-keyword">from</span> <span class="hljs-string">"@/components/redux/features/appSlice"</span>;
</code></pre>
<p>Adjust the delete React icon to include the <code>onClick</code> function to open the modal:</p>
<pre><code class="lang-tsx">      &lt;MdDelete
         onClick={() =&gt;
            dispatch(
               openDeleteBoardAndTaskModal({
                  variant: "Delete this Task?",
                  status,
                  index,
               }),
            )
         }
         className="text-lg cursor-pointer text-red-500"
      /&gt;;
</code></pre>
<p>Now we'll start building the markup for the delete board and tasks modal, coupled with the implementation of its functionalities.</p>
<p>In your <code>app/components</code> folder, create a file named <code>DeleteBoardAndTask</code> modal and paste the provided code inside of it:</p>
<pre><code class="lang-tsx">   import { Modal, ModalBody } from "./Modal";
   import { useAppDispatch, useAppSelector } from "@/components/redux/hooks";
   import {
   closeDeleteBoardAndTaskModal,
   getDeleteBoardAndTaskModalValue,
   getDeleteBoardAndTaskModalVariantValue,
   getDeleteBoardAndTaskModalTitle,
   getDeleteBoardAndTaskModalIndex,
   getDeleteBoardAndTaskModalStatus,
   getCurrentBoardName,
   } from "@/components/redux/features/appSlice";
   import {
   useFetchDataFromDbQuery,
   useUpdateBoardToDbMutation,
   } from "@/components/redux/services/apiSlice";

   export default function DeleteBoardAndTaskModal() {
     //variable declarations, functions, JSX
   }
</code></pre>
<p>Next, go to the <code>DeleteBoardAndTaskModal</code> function and paste the following code into it to declare variables and state values. The comments provided explain the future use of each of the declarations.</p>
<pre><code class="lang-tsx">   const dispatch = useAppDispatch();
   const isModalOpen = useAppSelector(getDeleteBoardAndTaskModalValue);
   const closeModal = () =&gt; dispatch(closeDeleteBoardAndTaskModal());
   const currentBoardName = useAppSelector(getCurrentBoardName);
   const modalVariant = useAppSelector(getDeleteBoardAndTaskModalVariantValue);
   const taskTitle = useAppSelector(getDeleteBoardAndTaskModalTitle);
   const taskIndex = useAppSelector(getDeleteBoardAndTaskModalIndex);
   const taskStatus = useAppSelector(getDeleteBoardAndTaskModalStatus);
   let { data } = useFetchDataFromDbQuery();
   const [updateBoardToDb, { isLoading }] = useUpdateBoardToDbMutation();
</code></pre>
<p>Here, we’ll implement the function responsible for the modal functionality. Just below the variable definitions above, paste the following function:</p>
<pre><code class="lang-tsx">   const handleDelete = (e: React.FormEvent&lt;HTMLButtonElement&gt;) =&gt; {
    e.preventDefault();
    if (data) {
      if (modalVariant === "Delete this board?") {
        // Implement the logic for deleting the board
        if (currentBoardName) {
          //  Assuming data is available, you need to handle the logic to update the data
          const [boards] = data;
          const updatedBoards = boards.boards.filter(
            (board: { name: string }) =&gt; board.name !== currentBoardName
          );
          updateBoardToDb(updatedBoards);
        }
      } else {
        // Implement the logic for deleting a task
        if (taskIndex !== undefined &amp;&amp; taskStatus &amp;&amp; currentBoardName) {
          const [boards] = data;
          //  Handle the logic to update the tasks
          const updatedBoards = boards.boards.map(
            (board: {
              name: string;
              columns: [{ name: string; tasks: [] }];
            }) =&gt; {
            // check the board active board
              if (board.name === currentBoardName) {
                // loop through the columns of the board to find the column in which the task to edit is
                const updatedColumns = board.columns.map((column) =&gt; {
                  if (column.name === taskStatus) {
                    // delete the the task
                    const updatedTasks = column.tasks.filter(
                      (_, index: number) =&gt; index !== taskIndex
                    );
                    return { ...column, tasks: updatedTasks };
                  }
                  return column;
                });
                return { ...board, columns: updatedColumns };
              }
              return board;
            }
          );
          updateBoardToDb(updatedBoards);
        }
      }
    }
   };
</code></pre>
<p>Finally, in this component, paste the code below to implement the JSX of the modal:</p>
<pre><code>   <span class="hljs-keyword">return</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Modal</span> <span class="hljs-attr">isOpen</span>=<span class="hljs-string">{isModalOpen}</span> <span class="hljs-attr">onRequestClose</span>=<span class="hljs-string">{closeModal}</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">ModalBody</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-red font-bold text-lg"</span>&gt;</span>{modalVariant}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"pt-6"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"text-sm text-medium-grey leading-6"</span>&gt;</span>
            {modalVariant === "Delete this board?"
              ? `Are you sure you want to delete the '${currentBoardName}' board? This action will remove all columns
                and tasks and cannot be reversed.`
              : `Are you sure you want to delete the '${taskTitle}' tasks? This action cannot be reversed.`}
          <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> <span class="hljs-attr">className</span>=<span class="hljs-string">"pt-6 flex space-x-2"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-1/2"</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>
              <span class="hljs-attr">onClick</span>=<span class="hljs-string">{(e:</span> <span class="hljs-attr">React.FormEvent</span>&lt;<span class="hljs-attr">HTMLButtonElement</span>&gt;</span>) =&gt;
                handleDelete(e)
              }
              className="bg-red-500 rounded-3xl py-2 w-full text-sm font-bold"
            &gt;
              {" "}
              {isLoading ? "Loading" : "Delete"}
            <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">div</span> <span class="hljs-attr">className</span>=<span class="hljs-string">"w-1/2"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
              <span class="hljs-attr">onClick</span>=<span class="hljs-string">{closeModal}</span>
              <span class="hljs-attr">className</span>=<span class="hljs-string">"bg-stone-200 rounded-3xl py-2 w-full text-sm font-bold"</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 class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">ModalBody</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">Modal</span>&gt;</span>
   );
   }</span>
</code></pre><p>After making this update, import the component in the <code>page.tsx</code> and render it as seen below:</p>
<pre><code class="lang-tsx">     //rest of the imports
     import DeleteBoardOrTaskModal from "./components/DeleteBoardAndTaskModal";
     //rest of the code
       return (
    &lt;main className="flex h-full"&gt;
      &lt;Sidebar /&gt;
      &lt;BoardTasks /&gt;
      &lt;AddAndEditBoardModal /&gt;
      &lt;AddAndEditTaskModal/&gt;
      &lt;DeleteBoardAndTaskModal/&gt;
    &lt;/main&gt;
    );
</code></pre>
<p>After rendering the component, you can now delete a board. As an example, we’ll delete the “Marketing” board we previously created:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/27.gif" alt="Delete board functionality" width="600" height="400" loading="lazy"></p>
<p>Likewise, you can delete a task:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/28.gif" alt="Delete task functionality" width="600" height="400" loading="lazy"></p>
<p>In the next section, we’ll explore the implementation of the drag and drop functionality with the <code>react-beautiful-dnd</code> library.</p>
<h2 id="heading-how-to-implement-drag-and-drop-functionality">How to Implement Drag and Drop Functionality</h2>
<p>At the end of this section, you should be able to move tasks between columns and across columns. </p>
<p>To begin, install the <code>react-beautiful-dnd</code> library with the following command:</p>
<pre><code class="lang-npm">npm i react-beautiful-dnd
</code></pre>
<p>It’s worth noting that <a target="_blank" href="https://github.com/atlassian/react-beautiful-dnd/issues/2399#issuecomment-1111169234">the react-beautiful-dnd library does not work inside the <code>StrictMode</code> wrapper</a> which is <a target="_blank" href="https://nextjs.org/docs/pages/api-reference/next-config-js/reactStrictMode">enabled by default in the app router</a>. So we have to create a custom hook which will enable us use the <code>react-beautiful-dnd</code> library safely with <code>StrictMode</code>. </p>
<p>Create a file named <code>StrictModeDroppable.tsx</code> inside your <code>src/app/components</code> folder and paste the provided code below inside of it:</p>
<pre><code class="lang-tsx">import { useEffect, useState } from "react";
import { Droppable, DroppableProps } from "react-beautiful-dnd";

export const StrictModeDroppable = ({ children, ...props }: DroppableProps) =&gt; {
  const [enabled, setEnabled] = useState(false);

  useEffect(() =&gt; {
    const animation = requestAnimationFrame(() =&gt; setEnabled(true));

    return () =&gt; {
      cancelAnimationFrame(animation);
      setEnabled(false);
    };
  }, []);

  if (!enabled) {
    return null;
  }

  return &lt;Droppable {...props}&gt;{children}&lt;/Droppable&gt;;
};
</code></pre>
<p>This way, we have made it compatible with <code>StrictMode</code>, allowing us to safely implement the drag and drop feature.</p>
<p>Next, navigate to the <code>BoardTasks.tsx</code> component and update it with the code below:</p>
<p>Firstly, import needed components from the <code>react-beautiful-dnd</code> library and also from our custom <code>StrictModeDroppable.tsx</code> component:</p>
<pre><code class="lang-tsx">//import useRef hook
import { useEffect, useState, useRef } from "react";
import { DragDropContext, Draggable } from "react-beautiful-dnd";
// import Droppable from the custom hook
import { StrictModeDroppable as Droppable } from "./StrictModeDroppable";
</code></pre>
<p>After updating the imports, go to the BoardTasks function and include the following functions:</p>
<pre><code class="lang-tsx">// check if it’s the first render
const initialRender = useRef(true);


  const handleDragEnd = async ({ destination, source }: any) =&gt; {
    // Check if the destination is not null (i.e., it was dropped in a valid droppable)
    if (!destination) return;


    // get a deep nested copy of the columns state
    const newColumns = columns.map((column) =&gt; ({
      ...column,
      tasks: [...column.tasks], // Create a new array for tasks
    }));


    // Find the source and destination columns based on their droppableIds
    const sourceColumnIndex = newColumns.findIndex(
      (col) =&gt; col.id === source.droppableId
    );
    const destinationColumnIndex = newColumns.findIndex(
      (col) =&gt; col.id === destination.droppableId
    );


    // Task that was dragged
    const itemMoved = newColumns[sourceColumnIndex]?.tasks[source.index];


    // Remove from its source
    newColumns[sourceColumnIndex].tasks.splice(source.index, 1);


    // Insert into its destination
    newColumns[destinationColumnIndex].tasks.splice(
      destination.index,
      0,
      itemMoved
    );


    // Update the state
    setColumns(newColumns);
  };


  useEffect(() =&gt; {
    // Check if it's the initial render, to avoid sending the data to the backend on mount
    if (!initialRender.current) {
      // Update the backend with the new order
      try {
        if (data) {
          const [boards] = data;
          const boardsCopy = [...boards.boards];
          const activeBoardIndex = boardsCopy.findIndex(
            (board: { name: string }) =&gt; board.name === currentBoardTitle
          );
          const updatedBoard = {
            ...boards.boards[activeBoardIndex],
            columns,
          };
          boardsCopy[activeBoardIndex] = updatedBoard;
          updateBoardToDb(boardsCopy);
        }
      } catch (error) {
        // Handle error
        console.error("Error updating board:", error);
      }
    } else {
      // Set initial render to false after the first render
      initialRender.current = false;
    }
  }, [columns]);
</code></pre>
<p>So far here, we implemented a function which will be triggered after a task has been dragged. After each trigger of this function, the columns data is being updated and sent to the Cloud Firestore via the <code>useEffect</code> hook. I added some more comments in the code to help you understand better. </p>
<p>Finally, update the JSX in the return statement as seen below:</p>
<pre><code class="lang-tsx"> return (
    &lt;div className="overflow-x-auto overflow-y-auto w-full p-6 bg-stone-200"&gt;
      {/* If data has not been fetched successfully, display a loading state, else display the column of tasks */}
      {isLoading ? (
        &lt;p className="text-3xl w-full text-center font-bold"&gt;
          Loading tasks...
        &lt;/p&gt;
      ) : (
        &lt;&gt;
          {/* If columns of tasks isn't empty: display the tasks, else display the prompt to add a new column */}
          {columns.length &gt; 0 ? (
            &lt;DragDropContext onDragEnd={handleDragEnd}&gt;
              &lt;div className="flex space-x-6"&gt;
                {columns.map((column, index) =&gt; {
                  const { id, name } = column;
                  return (
                    &lt;div key={id} className="w-[17.5rem] shrink-0"&gt;
                      &lt;p className="text-black"&gt;{`${column.name} (${
                        column.tasks ? column.tasks?.length : 0
                      })`}&lt;/p&gt;
                      &lt;Droppable droppableId={id}&gt;
                        {(provided) =&gt; (
                          &lt;div
                            ref={provided.innerRef}
                            {...provided.droppableProps}
                            className="h-full"
                          &gt;
                            {column.tasks &amp;&amp;
                              // Display the tasks if there are tasks in the column, if not, display an empty column
                              (column.tasks.length &gt; 0 ? (
                                column.tasks.map((task, index) =&gt; {
                                  const { id, title, status } = task;
                                  return (
                                    &lt;Draggable
                                      key={id}
                                      draggableId={id}
                                      index={index}
                                    &gt;
                                      {(provided) =&gt; (
                                        &lt;div
                                          ref={provided.innerRef}
                                          {...provided.draggableProps}
                                          {...provided.dragHandleProps}
                                          className="bg-white p-6 rounded-md mt-6 flex items-center justify-between border"
                                        &gt;
                                          &lt;p&gt;{task.title}&lt;/p&gt;
                                          &lt;div className="flex items-center space-x-1"&gt;
                                            &lt;MdEdit
                                              onClick={() =&gt;
                                                dispatch(
                                                  openAddAndEditTaskModal({
                                                    variant: "Edit Task",
                                                    title,
                                                    index,
                                                    name,
                                                  })
                                                )
                                              }
                                              className="text-lg cursor-pointer"
                                            /&gt;
                                            &lt;MdDelete
                                              onClick={() =&gt;
                                                dispatch(
                                                  openDeleteBoardAndTaskModal({
                                                    variant:
                                                      "Delete this task?",
                                                    title,
                                                    status,
                                                    index,
                                                  })
                                                )
                                              }
                                              className="text-lg cursor-pointer text-red-500"
                                            /&gt;
                                          &lt;/div&gt;
                                        &lt;/div&gt;
                                      )}
                                    &lt;/Draggable&gt;
                                  );
                                })
                              ) : (
                                &lt;div className="mt-6 h-full rounded-md border-dashed border-4 border-white" /&gt;
                              ))}
                            {provided.placeholder}
                          &lt;/div&gt;
                        )}
                      &lt;/Droppable&gt;
                    &lt;/div&gt;
                  );
                })}
                {/* If the number of columns of tasks is less than 7, display an option to add more columns */}
                {columns.length &lt; 7 ? (
                  &lt;div
                    onClick={() =&gt;
                      dispatch(openAddAndEditBoardModal("Edit Board"))
                    }
                    className="rounded-md bg-white w-[17.5rem] mt-12 shrink-0 flex justify-center items-center"
                  &gt;
                    &lt;p className="cursor-pointer font-bold text-black text-2xl"&gt;
                      + New Column
                    &lt;/p&gt;
                  &lt;/div&gt;
                ) : (
                  ""
                )}
              &lt;/div&gt;
            &lt;/DragDropContext&gt;
          ) : (
            &lt;div className="w-full h-full flex justify-center items-center"&gt;
              &lt;div className="flex flex-col items-center"&gt;
                &lt;p className="text-black text-sm"&gt;
                  This board is empty. Create a new column to get started.
                &lt;/p&gt;
                &lt;button className="bg-blue-500 text-black px-4 py-2 flex mt-6 rounded-3xl items-center space-x-2"&gt;
                  &lt;p&gt;+ Add New Column&lt;/p&gt;
                &lt;/button&gt;
              &lt;/div&gt;
            &lt;/div&gt;
          )}
        &lt;/&gt;
      )}
    &lt;/div&gt;
  );
</code></pre>
<p>In the code snippet above, we wrapped <code>DragDropContext</code> around the columns of tasks with its <code>onDragEnd</code> attribute, which accepts the <code>handleDragEnd</code> function, which will be triggered after a task has been dragged. </p>
<p>Don’t forget that after each trigger of this function, the columns data is being updated and sent to the Cloud Firestore via the <code>useEffect</code> hook. </p>
<p>Each column of task is also wrapped around the <code>Droppable</code> component. This signifies that this is a location you can drop a task. It also accepts a <code>droppableId</code> attribute which we passed the <code>id</code> of each column to it. </p>
<p>Each task card is also wrapped around the <code>Draggable</code> component, this makes them draggable within and among columns. </p>
<p>With these changes, we have easily implemented the drag and drop feature for our app:</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/29.gif" alt="drag and drop functionality" width="600" height="400" loading="lazy"></p>
<h2 id="heading-conclusion">Conclusion</h2>
<p>This tutorial guided you through implementing authentication using the <code>next-auth</code> library, setting up a Redux store, and integrating Firebase with its <code>RTK Query</code> in Next.js applications. </p>
<p>You also learned to implement CRUD operations in a Kanban task management app, and looked into form validations with JavaScript. </p>
<p>And finally, we covered the implementation of drag-and-drop functionality using the <code>react-beautiful-dnd</code> library.</p>
<p>Across the tutorial, we also leveraged existing libraries to streamline development rather than building everything from scratch.</p>
<p>If you want to see all the code, you can visit the project's GitHub repository <a target="_blank" href="https://github.com/SiR-PENt/kanban-app-tutorial">here</a>. Feel free to fork the project and open a PR if you feel the need for any improvements. If you’d also like to play around with the live site, you can find it <a target="_blank" href="https://kanban-app-tutorial.vercel.app">here</a>. </p>
<p>If you'd also like to explore this project with more advanced features, like dark mode, sleeker UI design, and better functionalities, visit it <a target="_blank" href="https://kanban-task-management-app-delta.vercel.app">here</a>.</p>
 ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ MERN Stack Roadmap – How to Learn MERN and Become a Full-Stack Developer ]]>
                </title>
                <description>
                    <![CDATA[ Have you ever wondered how modern web applications are built? How you can learn and master the technologies that you can use to build your own full stack projects from scratch? In this handbook, I'm going to introduce you to the MERN stack, a widely-... ]]>
                </description>
                <link>https://www.freecodecamp.org/news/mern-stack-roadmap-what-you-need-to-know-to-build-full-stack-apps/</link>
                <guid isPermaLink="false">66c8c8f7fe21816c4cb75d21</guid>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                    <category>
                        <![CDATA[ full stack ]]>
                    </category>
                
                    <category>
                        <![CDATA[ MongoDB ]]>
                    </category>
                
                    <category>
                        <![CDATA[ node js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ React ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chris Blakely ]]>
                </dc:creator>
                <pubDate>Thu, 04 Jan 2024 00:57:45 +0000</pubDate>
                <media:content url="https://www.freecodecamp.org/news/content/images/2024/01/Copy-of-mern-stack-hotel-booking-website--1-.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>Have you ever wondered how modern web applications are built? How you can learn and master the technologies that you can use to build your own full stack projects from scratch?</p>
<p>In this handbook, I'm going to introduce you to the MERN stack, a widely-used technology stack embraced by many leading companies. I'll guide you through 7 essential steps to start learning these technologies on your own. </p>
<p>By the time you finish reading, you'll have a solid understanding of what the MERN stack entails, its component technologies, and various resources for learning. You'll also have 10 project ideas that you can develop and showcase in your portfolio.</p>
<h1 id="heading-table-of-contents">Table of Contents</h1>
<ol>
<li><a class="post-section-overview" href="#heading-what-is-the-mern-stack">What is the MERN stack?</a></li>
<li><a class="post-section-overview" href="#heading-the-mern-stack-roadmap">The MERN Stack Roadmap</a><ul>
<li><a class="post-section-overview" href="#heading-step-1-learn-the-right-amount-of-html-javascript-and-css">STEP 1: Learn the right amount of HTML, JavaScript, and CSS</a></li>
<li><a class="post-section-overview" href="#heading-step-2-get-familiar-with-react">STEP 2: Get familiar with React</a></li>
<li><a class="post-section-overview" href="#heading-step-3-understand-rest-apis-and-how-a-backend-server-works-using-expressnode">STEP 3: Understand REST API's and how a backend server works using Express/Node</a></li>
<li><a class="post-section-overview" href="#heading-step-4-storing-data-with-mongodb-and-mongoose">STEP 4: Storing data with MongoDB and Mongoose</a></li>
<li><a class="post-section-overview" href="#heading-step-5-writing-tests">STEP 5: Writing tests</a></li>
<li><a class="post-section-overview" href="#heading-step-6-using-git">STEP 6: Using Git</a></li>
<li><a class="post-section-overview" href="#heading-step-7-deployments">STEP 7: Deployments</a></li>
</ul>
</li>
<li><a class="post-section-overview" href="#heading-top-resources-to-learn-the-mern-stack">Top Resources to learn the MERN Stack</a></li>
<li><a class="post-section-overview" href="#heading-10-project-ideas-you-can-try-today">10 project ideas you can try today</a></li>
<li><a class="post-section-overview" href="#heading-wrapping-up-the-mern-stack-journey">Wrapping up the MERN stack journey</a></li>
</ol>
<h2 id="heading-what-is-the-mern-stack">What is the MERN stack?</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/MERN-Stack-wallpaper-gigapixel-hq-scale-6_00x.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The MERN stack, comprising MongoDB, Express.js, React, and Node.js, is a cohesive set of technologies used for building efficient and scalable web applications. </p>
<p>Its popularity stems from the seamless integration of each component: MongoDB's flexible data handling, Express.js's efficient server-side networking, React's dynamic user interfaces, and Node.js's powerful back-end runtime environment. </p>
<p>For beginners, the MERN stack is a smart choice because it uses JavaScript across all layers, simplifying the learning curve. This uniformity, coupled with a strong community and ample learning resources, makes it an accessible and practical toolkit for anyone looking to dive into full-stack development.</p>
<p>The MERN stack is also heavily utilized in the industry, favored by startups and large enterprises alike for its efficiency and the robust, modern web applications it can produce. This industry adoption not only validates its effectiveness but also opens up numerous career opportunities for those skilled in these technologies.</p>
<p>Let's look at a brief overview of what each part of the MERN Stack looks like:</p>
<h3 id="heading-frontend-react">Frontend (React)</h3>
<p>The frontend of a website is like the dining area of a restaurant. It's everything you see and interact with directly on a website – the layout, design, buttons, and text.  </p>
<p><strong>Example</strong>: When you visit a website and see a beautiful homepage, interact with menus, or fill out forms, you're experiencing the frontend.</p>
<h3 id="heading-backend-nodejs">Backend (Node.js)</h3>
<p>The backend is like the kitchen in a restaurant. It's where all the behind-the-scenes work happens. It includes the server, applications, and databases that work together to process the information you see on the frontend.  </p>
<p><strong>Example</strong>: When you order food (submit a form on the website), the kitchen (backend) processes the order (the data) and prepares your meal (the information or service you requested).</p>
<h3 id="heading-database-mongodb">Database (MongoDB)</h3>
<p>A database in web development is similar to a restaurant’s pantry or storage where all the ingredients (data) are kept. It stores and manages all the information needed by the website, like user profiles, content, and other data.  </p>
<p><strong>Example</strong>: In an online store, the database stores product information, prices, user reviews, and customer details.</p>
<h3 id="heading-rest-apis-express">REST APIs (Express)</h3>
<p>REST APIs are like the waiters in a restaurant. They are the messengers or go-betweens for the frontend and backend. They take requests (like orders) from the frontend (customer), fetch or update data in the backend (kitchen), and then return responses (prepared orders). </p>
<p>The terms POST, PUT, DELETE, and GET are types of requests used in REST APIs:</p>
<ul>
<li><strong>POST</strong>: Used to create new data. Like placing a new order in the restaurant.</li>
<li><strong>PUT</strong>: Used to update existing data. Similar to changing an order you've already placed.</li>
<li><strong>DELETE</strong>: Used to remove data. Like cancelling an order.</li>
<li><strong>GET</strong>: Used to retrieve data. Comparable to asking about the menu or checking the status of your order.</li>
</ul>
<h2 id="heading-the-mern-stack-roadmap">The MERN Stack Roadmap</h2>
<h3 id="heading-step-1-learn-the-right-amount-of-html-javascript-and-css">STEP 1: Learn the right amount of HTML, JavaScript, and CSS</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/b59a78e2ed76c705f3c0dcb300f3f222aefdcd99-gigapixel-hq-scale-6_00x.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>The MERN stack uses JavaScript heavily, so its a natural first step to learn. In this section you will look at the main things you will use day-to-day when creating full-stack MERN apps. </p>
<p>Understanding JavaScript is like knowing the right amount of ingredients needed for a recipe. You don't need to master everything at once, just the essential ingredients that make your particular dish (or your web project) come to life.</p>
<h3 id="heading-variables">Variables</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/javascript-variables-beginners-guide/">Variables in JavaScript</a> are like labelled jars in your kitchen. You can store things in them (like numbers, text) and use these jars later in your cooking (or coding).</p>
<p><strong>Example</strong>: A variable storing the user's name, so you can use it later to say "Hello, [Name]!"</p>
<h3 id="heading-functions">Functions</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/javascript-functions-and-scope/">Functions</a> are like recipes in a cookbook. They are sets of instructions that perform a specific task. You can reuse these recipes whenever you need to perform that task again.</p>
<p><strong>Example</strong>: A function that calculates the total price of items in a shopping cart.</p>
<h3 id="heading-objects-amp-arrays">Objects &amp; Arrays</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/javascript-basics-strings-arrays-objects/">Objects</a> are like information cards holding details about something (like a contact card), and arrays are like lists.  </p>
<p><strong>Example of an Object</strong>: A card holding a user's information (name, age, email).<br><strong>Example of an Array</strong>: A list of all the user's favorite book titles.</p>
<h3 id="heading-ifelse-statements-switch-statements">If/else Statements, Switch Statements</h3>
<p>These are like decision-making processes. <a target="_blank" href="https://www.freecodecamp.org/news/javascript-if-else-and-if-then-js-conditional-statements/">If/else statements</a> are like choosing what to wear based on the weather, and switch statements are like a more complex decision, like choosing what to cook based on multiple ingredients you have.  </p>
<p><strong>Example</strong>: If it's raining (if), take an umbrella (else), take sunglasses.</p>
<h3 id="heading-callbackspromisesasync-await">Callbacks/Promises/Async Await</h3>
<p>These are ways to handle tasks that take some time, like ordering food and waiting for it. <a target="_blank" href="https://www.freecodecamp.org/news/what-is-a-callback-function-in-javascript-js-callbacks-example-tutorial/">Callbacks</a> are like calling a friend to do something when they’re free. <a target="_blank" href="https://www.freecodecamp.org/news/javascript-promises-async-await-and-promise-methods/">Promises</a> are like your friend promising to do it. <a target="_blank" href="https://www.freecodecamp.org/news/javascript-async-await/">Async-await</a> is like making a plan to do tasks one after another in an organized way.  </p>
<p><strong>Example</strong>: Ordering a coffee (a task) and waiting to get it before leaving the cafe (ensuring order of actions).</p>
<h3 id="heading-ecmascript-template-strings-destructuring-assignment-spread-operator-default-parameters-and-so-on">ECMAScript (Template Strings, Destructuring Assignment, Spread Operator, Default Parameters, and so on)</h3>
<p>These are advanced tools and shortcuts in JavaScript to make coding easier and cleaner. It's like having a food processor in your kitchen that makes chopping and mixing faster and more efficient.  </p>
<p><strong>Example</strong>: Automatically creating a welcome message like "Hello, [Name]!" without manually joining words and variables.</p>
<h3 id="heading-typescript">TypeScript</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/typescript-tutorial-for-react-developers/">TypeScript</a> is like JavaScript but with more rules for organizing your code (like a more detailed recipe book). It helps in managing larger projects by adding types to your code, making sure you don’t mix incompatible ingredients. <a target="_blank" href="https://www.freecodecamp.org/news/learn-typescript-beginners-guide/">This guide</a> teaches you TypeScript basics.  </p>
<p><strong>Example</strong>: Specifying that a function should only take a number as an input, not text or anything else. </p>
<h2 id="heading-step-2-get-familiar-with-react">STEP 2: Get Familiar with React</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/communityIcon_4g1uo0kd87c61.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Once you've got a feel for JavaScript, its time to venture into the wonderful world of frontend development. </p>
<p>React.js is a popular JavaScript library used for building user interfaces, particularly known for its efficiency in rendering dynamic, interactive web pages. It enables developers to create large web applications that can change data, without reloading the page, making for a smoother user experience.</p>
<p>Below is a list of common things you want to know when working with React.</p>
<h3 id="heading-components">Components</h3>
<p>Think of <a target="_blank" href="https://www.freecodecamp.org/news/how-to-use-react-components/">components</a> as individual LEGO blocks in a large LEGO model. Each block is a small, reusable piece that you can use to build different parts of your web application.  </p>
<p><strong>Example</strong>: A 'button' component in a website that can be used in many places, like for submitting a form or closing a pop-up.</p>
<h3 id="heading-jsx-javascript-xml">JSX (JavaScript XML)</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/jsx-in-react-introduction/">JSX</a> lets you write your website's design code (like HTML) inside your JavaScript code. It's like writing the recipe and the cooking instructions in one place for easier reference.  </p>
<p><strong>Example</strong>: Writing a piece of JSX code that includes both JavaScript and HTML-like tags to create a user login form.</p>
<h3 id="heading-props-properties">Props (Properties)</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/props-in-react/">Props are like instructions</a> or settings you pass to your LEGO blocks (components) to tell them what they should look like or do.  </p>
<p><strong>Example</strong>: Passing a 'color' prop to a 'button' component to make it red or blue depending on the situation.</p>
<h3 id="heading-state">State</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/usestate-vs-redux-state-management/">State</a> is like a personal notebook for each component, where it keeps track of its own information, like whether a button is clicked or not. Here's a course all about <a target="_blank" href="https://www.freecodecamp.org/news/how-to-manage-state-in-react/">state management in React</a>.</p>
<p><strong>Example</strong>: A 'like' button keeping track of whether it has been clicked (liked) or not.</p>
<h3 id="heading-hooks">Hooks</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/react-hooks-useeffect-usestate-and-usecontext/">Hooks are special tools in React</a> that let you add features like state to your components without needing to use complex code structures.</p>
<p><strong>Example</strong>: Using the useState hook to keep track of a counter in a component.</p>
<h3 id="heading-event-handling">Event Handling</h3>
<p>This is how you tell a component to do something when a user interacts with it, like clicking a button or entering text in a form.  </p>
<p><strong>Example</strong>: Setting up an event handler so that when a user clicks a 'submit' button, it sends their information to the server.</p>
<h3 id="heading-conditional-rendering">Conditional Rendering</h3>
<p>This is like having a magic painting that can change its picture based on certain conditions. In React, you can <a target="_blank" href="https://www.freecodecamp.org/news/react-conditional-rendering/">show different components based on different conditions</a>.</p>
<p><strong>Example</strong>: Showing a 'login' button if a user is not logged in, and a 'logout' button if they are.</p>
<h3 id="heading-lists-and-keys">Lists and Keys</h3>
<p>Keys are like name tags for items in a list. They help React keep track of which items are new, changed, or removed.  </p>
<p><strong>Example</strong>: Displaying a list of messages in a chat app, where each message has a unique key.</p>
<h3 id="heading-context-api">Context API</h3>
<p><a target="_blank" href="https://www.freecodecamp.org/news/context-api-in-react/">The Context API</a> a way to share information (like a theme setting or user data) across many components without passing the information through each level manually.</p>
<p><strong>Example</strong>: Using Context API to share the current logged-in user's information across different components in a web app.</p>
<h3 id="heading-fragment">Fragment</h3>
<p>Fragments let you group several components or elements together without adding extra layers to the website. It's like putting multiple LEGO pieces on a baseplate without the baseplate being part of the final model.</p>
<p><strong>Example</strong>: Grouping a list of items together in a menu without adding extra wrappers or divs around them.</p>
<h2 id="heading-step-3-understand-rest-apis-and-how-a-backend-server-works-using-expressnode">STEP 3: Understand REST API's and how a backend server works using Express/Node</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/express-and-node-opengraph-v1.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Every UI needs a way to store and retrieve the data it needs to make the frontend work. This is where our backend comes in. </p>
<p>In the MERN stack, the backend is composed of 3 main bits: Express, a Node.js server, and a database. We'll cover the database shortly, for now we'll focus on the Express/Node pieces of the MERN stack, as they're closely related.  </p>
<p>Express.js is a lightweight, flexible framework for Node.js, designed to build web applications and <a target="_blank" href="https://www.freecodecamp.org/news/build-consume-and-document-a-rest-api/">REST APIs</a> with ease and efficiency. Node.js is a powerful JavaScript runtime that allows for the development of scalable server-side applications, making the duo a popular choice for backend development.</p>
<h3 id="heading-nodejs-foundation-for-building-web-applications">Node.js: Foundation for Building Web Applications</h3>
<p>Think of Node.js as the foundation for constructing a modern building. It's a platform that lets you build web applications, similar to how you would use a set of basic tools and materials to start building a house. You might hear this being to referred to as the "backend".  </p>
<p><strong>Example</strong>: Building a chat application where multiple users can send messages in real-time.</p>
<h3 id="heading-express-streamlining-rest-api-development">Express: Streamlining REST API Development</h3>
<p>Express is a helper tool for Node.js. It's like having a pre-built kit that makes building certain parts of your house easier and faster, providing templates and shortcuts so you don't have to start from scratch. </p>
<p><strong>Example</strong>: Using Express to quickly set up routes for a website, like a route to a contact page or a product catalog.</p>
<h3 id="heading-modules-and-packages-ready-made-components">Modules and Packages: Ready-Made Components</h3>
<p>In Node.js, modules are like pre-made components or sections of a house (like a bathroom unit or a kitchen set) that you can simply choose and add to your building project.</p>
<p><strong>Example</strong>: Adding a 'date-time' module to display current times and dates on your web application.</p>
<h3 id="heading-node-package-manager-npm-the-tool-and-material-warehouse">Node Package Manager (NPM): The Tool and Material Warehouse</h3>
<p>NPM acts like a vast warehouse where you can find all sorts of tools and materials (modules) you might need. It's a central place to get additional resources for building your web applications.</p>
<p><strong>Example</strong>: Installing 'body-parser' from npm to handle JSON data in your web application.</p>
<h3 id="heading-routing-directing-web-traffic">Routing: Directing Web Traffic</h3>
<p>Routing in Express is like setting up roads and paths in a housing complex. It's about directing the flow of traffic (data and user requests) to different parts of your web application.</p>
<p><strong>Example</strong>: Creating routes in an online store, like <code>/products</code> for the product list and <code>/products/:id</code> for individual product details.</p>
<h3 id="heading-middleware-additional-functional-layers">Middleware: Additional Functional Layers</h3>
<p>Middleware in Express can be seen as extra layers or services in your building, like security, plumbing, or electrical systems. They add specific functionalities to your web application.</p>
<p><strong>Example</strong>: Adding 'cookie-parser' middleware to handle cookies in your web application.</p>
<h3 id="heading-request-and-response-communication-channels">Request and Response: Communication Channels</h3>
<p>Requests and responses in Express are like sending and receiving mail or messages. They are the way your web application communicates with users, sending them data or receiving their inputs.</p>
<p><strong>Example</strong>: Your application receiving a user's login request (request) and then sending back a confirmation message (response).</p>
<h3 id="heading-environment-variables-secure-storage-spaces">Environment Variables: Secure Storage Spaces</h3>
<p>Think of environment variables as secure storage spaces or safes in your building. They're used to store sensitive information like passwords or personal settings, keeping them secure and separate from the main construction.</p>
<p><strong>Example</strong>: Storing the database connection string in an environment variable to keep it secure.</p>
<h3 id="heading-security-building-safeguards">Security: Building Safeguards</h3>
<p>In the context of web applications, security is about building safeguards into your project. It's like installing locks, security systems, and fire safety measures in a building to protect it from various threats.</p>
<p><strong>Example</strong>: Implementing HTTPS to secure data transmission and using JWT (JSON Web Tokens) for user authentication to protect user data.</p>
<h2 id="heading-step-4-storing-data-with-mongodb-and-mongoose">STEP 4: Storing data with MongoDB and Mongoose</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/MongoDB_Logo.svg.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>MongoDB is a NoSQL database that offers high flexibility and scalability for storing and managing data, making it ideal for handling large volumes and diverse types of data. </p>
<p>Mongoose is an Object Data Modeling (ODM) library for MongoDB, providing a straightforward schema-based solution to model application data, enhancing database interaction with useful features and validation.</p>
<h3 id="heading-mongodb-a-nosql-database">MongoDB: A NoSQL Database</h3>
<p>MongoDB is a type of database that stores data in a flexible, JSON-like format. This makes it different from traditional databases, which use tables and rows. It's great for handling large volumes of data and is very scalable.</p>
<p><strong>Example</strong>: Using MongoDB to store user profiles where each profile can have different fields.</p>
<h3 id="heading-collections-and-documents">Collections and Documents</h3>
<p>In MongoDB, data is stored in 'collections', which are similar to tables in a relational database. Inside these collections, data is stored in 'documents'. Think of a document as a single record in your collection, like an entry in a diary or a contact in an address book.</p>
<p><strong>Example</strong>: A 'users' collection with documents each representing a user with details like name and email.</p>
<h3 id="heading-mongoose-a-mongodb-object-modeling-tool">Mongoose: A MongoDB Object Modeling Tool</h3>
<p>Mongoose is a library for Node.js that makes it easier to interact with MongoDB. It provides a straightforward, schema-based solution to model your application data. It's like having a personal assistant to help manage the communication between your application and MongoDB.</p>
<p><strong>Example</strong>: Using Mongoose to easily add, retrieve, and manage user data in a MongoDB database.</p>
<h3 id="heading-schemas">Schemas</h3>
<p>In Mongoose, a schema is a structure that defines the format of the data to be stored in MongoDB (like defining fields and data types). Think of it as a blueprint for how your data should look.</p>
<p><strong>Example</strong>: Creating a Mongoose schema for a blog post with fields like title, author, and content.</p>
<h3 id="heading-models">Models</h3>
<p>A model in Mongoose acts as a constructor, compiled from the Schema definitions. It represents documents in a MongoDB database. Models are responsible for creating and reading documents from the underlying MongoDB database.</p>
<p><strong>Example</strong>: Defining a 'User' model based on a user schema to interact with the 'users' collection in the database.</p>
<h3 id="heading-crud-operations">CRUD Operations</h3>
<p>CRUD stands for Create, Read, Update, Delete. These are the basic operations you can perform on the database. Mongoose provides easy methods to execute these operations on your data.</p>
<p><strong>Example</strong>: Using Mongoose methods to add new users, find users by name, update user information, or delete users.</p>
<h3 id="heading-connecting-to-mongodb">Connecting to MongoDB</h3>
<p>You can use Mongoose to connect your Node.js application to a MongoDB database. This is like setting up a phone line between your application and the database so they can talk to each other.</p>
<p><strong>Example</strong>: Writing a Mongoose connect function to link your Node.js application to a MongoDB database hosted on Atlas.</p>
<h3 id="heading-querying-data">Querying Data</h3>
<p>Mongoose allows you to query your MongoDB database. This means you can search for specific data, filter your data based on certain criteria, and more.</p>
<p><strong>Example</strong>: Using a Mongoose query to find all blog posts written by a specific author.</p>
<h3 id="heading-data-validation">Data Validation</h3>
<p>Mongoose provides built-in validation. This is a way to make sure the data being saved to your database is in the right format, like checking if an email address looks like an email address.</p>
<p><strong>Example</strong>: Defining a schema in Mongoose where the email field must match the format of an email address.</p>
<h3 id="heading-middleware-pre-and-post-hooks">Middleware (Pre and Post Hooks)</h3>
<p>Mongoose middleware are functions which can be executed automatically before or after certain operations, like saving a document. They're useful for complex logic like hashing passwords before saving them to the database.</p>
<p><strong>Example</strong>: Using a pre-save middleware in Mongoose to hash user passwords before saving them to the database.</p>
<h3 id="heading-indexes">Indexes</h3>
<p>Indexes in MongoDB are used to improve the performance of searches. They are similar to indexes in a book, helping the database find data faster.</p>
<p><strong>Example</strong>: Creating an index on the 'email' field in a user collection to speed up the search for users by email.</p>
<h3 id="heading-aggregation">Aggregation</h3>
<p>Aggregation in MongoDB is a powerful way to process data and get computed results. It's like having a sophisticated calculator to perform complex operations on your data, such as summing up values or averaging them.</p>
<p><strong>Example</strong>: Using MongoDB's aggregation framework to calculate the average number of comments on blog posts.</p>
<h2 id="heading-step-5-writing-tests">STEP 5: Writing Tests</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/opengraph.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Testing in software development is like a safety check to ensure everything in your application works as expected. It's a crucial step in the development process where you look for bugs or issues before your users do. Think of it like proofreading an essay or checking a car before a road trip – it helps catch and fix problems early.</p>
<p>There are different types of testing, each serving a unique purpose:</p>
<h3 id="heading-unit-testing">Unit Testing</h3>
<p>This is the most basic form of testing. Here, you test individual parts of your code (like functions or components) in isolation. It's like checking each light bulb in a string of Christmas lights. </p>
<p>In the MERN stack, tools like Jest or Mocha are commonly used for this. They let you write small tests to check if a specific part of your application behaves as expected.</p>
<h3 id="heading-integration-testing">Integration Testing</h3>
<p>This type of testing checks how different parts of your application work together. It's like making sure all the light bulbs light up when connected and the string is plugged in. </p>
<p>For the MERN stack, you might still use Jest or Mocha, combined with other tools like Chai for assertions, to ensure that different components or services in your application interact correctly.</p>
<h3 id="heading-end-to-end-e2e-testing">End-to-End (E2E) Testing</h3>
<p>Here, you test your application's workflow from start to finish. It's like checking if the entire Christmas tree lights up and twinkles as expected. </p>
<p>For a MERN stack application, Cypress or Selenium are popular choices. They simulate real user scenarios, ensuring the entire application, from the front end in React to the back end with Express and Node.js, functions smoothly together.</p>
<h3 id="heading-performance-testing">Performance Testing</h3>
<p>This checks if your application can handle stress, like heavy traffic or data processing. It's akin to ensuring the Christmas tree lights don't blow a fuse when they're all on. Tools like Loader.io or Apache JMeter can be used here.</p>
<p>Each type of testing serves to ensure a different aspect of your application is working correctly, and together, they contribute to building a robust, reliable, and user-friendly application. </p>
<p>By employing these tests in the MERN stack, you not only catch bugs early but also maintain a high standard of quality for your application.</p>
<h2 id="heading-step-6-using-git">STEP 6: Using Git</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/629b7adc7c5cd817694c3231.png" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Git is a version control system, a tool that tracks changes in your code over time. Think of it like a detailed diary for your coding project. Every time you make changes to your code, Git keeps a record of what was changed, when, and by whom. This becomes incredibly useful when you’re working on complex projects, like those involving the MERN stack.</p>
<p>Why is Git so important when building with the MERN stack, or any software for that matter? </p>
<h3 id="heading-collaboration">Collaboration</h3>
<p>Git is like a team playbook. It allows multiple developers to work on the same project without stepping on each other's toes. </p>
<p>Everyone can work on different features or parts of the application simultaneously (like MongoDB database schemas, React components, or Express.js routes). Git helps manage these contributions, ensuring changes can be merged smoothly into the main project.</p>
<h3 id="heading-tracking-changes">Tracking Changes</h3>
<p>Imagine you've made changes to your React component, and suddenly things aren't working as they used to. Git allows you to go back in time and see what changes were made and by whom. </p>
<p>This historical data is invaluable for understanding how your project evolved and for fixing issues without having to start from scratch.</p>
<h3 id="heading-branching-and-merging">Branching and Merging</h3>
<p>Git’s branching feature lets you diverge from the main line of development and experiment with new features or ideas in a controlled way. </p>
<p>You can create a branch, make your changes, and then merge those changes back into the main project when they're ready. This ensures the main project (often referred to as the 'master' or 'main' branch) remains stable.</p>
<h3 id="heading-backup-and-restore">Backup and Restore</h3>
<p>With Git, your project's entire history is stored in the repository. If anything goes wrong, you can roll back to a previous state. It’s like having a fail-safe backup system.</p>
<h3 id="heading-documentation">Documentation</h3>
<p>Commit messages in Git provide a narrative for your project. They allow you to document what changes were made and why, which is extremely helpful for both your future self and other developers who might work on the project.</p>
<p>When building applications with the MERN stack, Git offers a safety net and a collaborative workspace. It keeps your project organized, tracks every change, and allows multiple developers to work together more efficiently. </p>
<p>Using Git is essential for managing complex development projects in today's software development world.</p>
<h2 id="heading-step-7-deployments">STEP 7: Deployments</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2024/01/img-blog-cico.jpg" alt="Image" width="600" height="400" loading="lazy"></p>
<p>Code deployment is the process of taking code written by developers and making it operational on a live environment where users can interact with it. </p>
<p>In the context of the MERN stack, which involves technologies like MongoDB, Express.js, React, and Node.js, deployment is the final step in the journey of bringing a full-stack application to life.</p>
<p>Imagine you've built a house (your web application). Deployment is like moving it from the construction site (development environment) to its actual location where people can live in it (production environment). This process involves several key steps to ensure that everything works as expected when users access your app.</p>
<h3 id="heading-preparing-for-deployment">Preparing for Deployment</h3>
<p>Before deploying, you need to prepare your application. This involves ensuring your code is complete and tested, dependencies are properly managed, and your app is configured for the production environment. </p>
<p>For MERN stack applications, this might mean setting up environment variables, configuring your database (MongoDB) connection for production, and optimizing your React front-end for performance.</p>
<h3 id="heading-hosting-and-servers">Hosting and Servers</h3>
<p>Choosing where to host your application is crucial. For MERN stack apps, you can use cloud-based hosting services like AWS, Heroku, or DigitalOcean. These platforms offer services to host both your Node.js backend and MongoDB database, and they often provide additional features like scaling, monitoring, and security.</p>
<h3 id="heading-continuous-integrationcontinuous-deployment-cicd">Continuous Integration/Continuous Deployment (CI/CD)</h3>
<p>CI/CD is a methodology that automates the deployment process. Whenever you make changes to your codebase (like fixing a bug or adding a new feature), CI/CD tools automatically test your code and deploy it if the tests pass. This ensures that your application is always up-to-date with the latest changes. </p>
<p>Tools like Jenkins, Travis CI, or GitHub Actions are commonly used for this purpose.</p>
<h3 id="heading-monitoring-and-maintenance">Monitoring and Maintenance</h3>
<p>After deployment, it’s important to monitor your application for any issues and perform regular maintenance. This could involve checking server logs, updating dependencies, or rolling out fixes for any bugs that might arise.</p>
<h3 id="heading-rollbacks">Rollbacks</h3>
<p>A good deployment strategy also includes plans for rollbacks. If something goes wrong after deployment, you need to be able to revert to the previous version quickly to minimize downtime.</p>
<p>In the MERN stack, each component (MongoDB, Express.js, React, and Node.js) might require specific considerations for deployment. For instance, you might use Docker containers to package your Node.js and Express.js backend, ensuring it runs consistently across different environments. React apps might be minified and bundled for optimal performance.</p>
<p>In essence, deployment in the MERN stack is about getting your application from your local machine to a server where it can serve users reliably and efficiently. It involves careful planning, choosing the right hosting solutions, automating the process as much as possible, and being prepared to address issues post-deployment.</p>
<h2 id="heading-top-resources-to-learn-the-mern-stack">Top Resources to Learn the MERN Stack</h2>
<p>Now that you have a pretty good idea of what you need to learn to master the MERN stack, lets look at a list of free resources you can start using today to begin your journey!</p>
<ul>
<li>The <a target="_blank" href="https://www.freecodecamp.org/learn/">freeCodeCamp curriculum</a> is one of the best places to learn how to code for free. The curriculum is super in depth and covers alot of the topics mentioned in this post.</li>
<li>The freeCodeCamp YouTube channel has a ton of free courses you can try as well. <a target="_blank" href="https://www.youtube.com/watch?v=-42K44A1oMA&amp;t=2950s">This MERN stack book project is an excellent one for beginners</a>.</li>
<li><a target="_blank" href="https://fullstackopen.com/">Full Stack open</a> is another good resource. It doesn't teach MongoDB but you learn about SQL instead which is equally as useful.</li>
<li>My <a target="_blank" href="https://www.youtube.com/watch?v=YdBy9-0pER4">MERN stack hotel booking app project on YouTube is a free 15 hour course</a> where you will learn everything talked about in this post.</li>
</ul>
<h2 id="heading-10-project-ideas-you-can-try-today">10 Project Ideas You Can Try Today</h2>
<p>One of my favourite ways to learn new technologies is to build projects. If you're struggling with coming up with ideas, here's 10 projects you can start building today. I'll be building some of these on my <a target="_blank" href="https://www.youtube.com/@ChrisBlakely">YouTube channel</a> so feel free to subscribe to stay up to date!</p>
<h3 id="heading-personal-blog-website">Personal Blog Website</h3>
<ul>
<li><strong>Functionality</strong>: Users can read posts, and the admin can create, edit, or delete posts.</li>
<li><strong>Learning Focus</strong>: Basic CRUD operations, user authentication, integrating React with Express API, and using MongoDB for data storage.</li>
</ul>
<h3 id="heading-task-manager-to-do-list">Task Manager (To-Do List)</h3>
<ul>
<li><strong>Functionality</strong>: Users can add, view, edit, and delete tasks. Tasks can have deadlines and priority levels.</li>
<li><strong>Learning Focus</strong>: React state management, Express route handling, MongoDB operations, and basic UI development.</li>
</ul>
<h3 id="heading-simple-e-commerce-site">Simple E-commerce Site</h3>
<ul>
<li><strong>Functionality</strong>: Display products, add to cart, and checkout functionality. Admin features for adding or removing products.</li>
<li><strong>Learning Focus</strong>: React components for product listing, cart management, Express.js for product APIs, MongoDB for product data, and handling user inputs.</li>
</ul>
<h3 id="heading-recipe-sharing-application">Recipe Sharing Application</h3>
<ul>
<li><strong>Functionality</strong>: Users can post, view, edit, and delete recipes. Implement a rating or comment system for interaction.</li>
<li><strong>Learning Focus</strong>: File upload for images, user-generated content management, and MongoDB schema design.</li>
</ul>
<h3 id="heading-budget-tracker">Budget Tracker</h3>
<ul>
<li><strong>Functionality</strong>: Users can input their expenses and income, categorize them, and view a summary of their finances.</li>
<li><strong>Learning Focus</strong>: Handling forms in React, creating RESTful services with Express, and effective data structuring in MongoDB.</li>
</ul>
<h3 id="heading-event-planning-application">Event Planning Application:</h3>
<ul>
<li><strong>Functionality</strong>: Users can create and manage events, send invitations, and track RSVPs.</li>
<li><strong>Learning Focus</strong>: Date handling in JavaScript, complex MongoDB documents, and Express middleware for authentication.</li>
</ul>
<h3 id="heading-fitness-tracker">Fitness Tracker:</h3>
<ul>
<li><strong>Functionality</strong>: Log workouts, track progress over time, set fitness goals.</li>
<li><strong>Learning Focus</strong>: Data visualization with React, creating API endpoints for varied data types, and MongoDB for storing time-series data.</li>
</ul>
<h3 id="heading-chat-application">Chat Application:</h3>
<ul>
<li><strong>Functionality</strong>: Real-time chat rooms, private messaging, and user profiles.</li>
<li><strong>Learning Focus</strong>: WebSockets with Node.js for real-time communication, MongoDB for message storage, and React for dynamic UI updates.</li>
</ul>
<h3 id="heading-book-review-platform">Book Review Platform:</h3>
<ul>
<li><strong>Functionality</strong>: Users can post book reviews, rate books, and browse reviews by other users.</li>
<li><strong>Learning Focus</strong>: Integrating external APIs for book data, user-generated content moderation, and complex querying in MongoDB.</li>
</ul>
<h3 id="heading-local-business-directory">Local Business Directory:</h3>
<ul>
<li><strong>Functionality</strong>: Listing of local businesses with categories, user reviews, and contact information.</li>
<li><strong>Learning Focus</strong>: Geolocation, advanced querying and indexing in MongoDB, and creating a responsive layout with React.</li>
</ul>
<h2 id="heading-wrapping-up-the-mern-stack-journey">Wrapping Up the MERN Stack Journey</h2>
<p>As we reach the end of our exploration of the MERN stack, I hope this guide has illuminated the path for your journey into the world of full-stack development. </p>
<p>We've covered the fundamental components – MongoDB, Express.js, React, and Node.js – and delved into the practical aspects of building, testing, and deploying applications using this versatile stack.</p>
<p>Remember, the road to mastering the MERN stack is both challenging and rewarding. Each project you undertake will add to your skills and confidence. Use the resources and project ideas provided as stepping stones to build your portfolio and deepen your understanding. </p>
<p>The beauty of the MERN stack lies in its community-driven nature, so don't hesitate to seek help, collaborate, and share your experiences.  </p>
<p>If you want to get in touch, you can reach me over at <a target="_blank" href="https://www.linkedin.com/in/chrisblakely01/">LinkedIn</a>, or drop a comment on my <a target="_blank" href="https://www.youtube.com/@ChrisBlakely">YouTube channel</a>. Good luck and happy coding! </p>
<div class="embed-wrapper">
        <iframe width="560" height="315" src="https://www.youtube.com/embed/undefined" 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 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[ 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>
        
    </channel>
</rss>
