<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://jj09.net/feed.xml" rel="self" type="application/atom+xml" /><link href="https://jj09.net/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-04-12T04:23:06+00:00</updated><id>https://jj09.net/feed.xml</id><title type="html">Jacob Jedryszek</title><subtitle>Software Engineer at Meta (Ex-MSFT)</subtitle><entry><title type="html">Beyond the Prompt: Redefining the Software Engineer’s Value in the Age of AI</title><link href="https://jj09.net/beyond-the-prompt/" rel="alternate" type="text/html" title="Beyond the Prompt: Redefining the Software Engineer’s Value in the Age of AI" /><published>2026-04-11T00:00:00+00:00</published><updated>2026-04-11T00:00:00+00:00</updated><id>https://jj09.net/beyond-the-prompt</id><content type="html" xml:base="https://jj09.net/beyond-the-prompt/"><![CDATA[<p><img src="/assets/2026/beyond-the-prompt.jpg" alt="Beyond the Prompt - Redefining the Software Engineer's Value in the Age of AI" title="Beyond the Prompt - Redefining the Software Engineer's Value in the Age of AI" /></p>

<p>We are witnessing the most significant shift in software engineering since the move to high-level languages. With AI coding tools handling the “manual labor” (boilerplate, unit tests, and refactoring), it’s easy to feel like the value of the human engineer is being compressed.
But the opposite is true. As the cost of generating code drops to near zero, the value of human judgment and the necessity of personal sustainability scale exponentially.</p>

<h3>Judgment Is the Primary Resource</h3>

<p>In a world of infinite code, the real engine of production isn’t the model - it’s human initiative. Real wealth in engineering is built on knowledge and the ability to define intent. AI can write code, but it cannot “own” a problem. Your unique value is the judgment that tells you what to build and why. If you treat yourself as a mere prompt operator, you’re missing the point. Your judgment is the one thing that doesn’t depreciate.</p>

<h3>Don't Optimize for Doing More Just for the Sake of It</h3>

<p>There is a new trap in the AI era: valuing output volume. Simply because it’s easier to generate. If your work doesn’t actually enhance your life or improve the product in a meaningful way, you’re just optimizing for the wrong KPI. The goal is to use AI to give us the freedom to be more. To think deeper and solve bigger problems. Not just to fill a repo with more “stuff” and half-baked ideas.</p>

<h3>Build What You Believe In</h3>

<p>A productive, AI-augmented environment is an incredible tool, but efficiency isn’t a substitute for conviction. It is tempting to use the speed of AI to throw everything at the wall to see what sticks, but impactful products require a foundation of belief and purpose. If you aren’t building with intention, you’re just generating noise.</p>

<h3>Optimize for the Long-Term</h3>

<p>It’s easy to get sucked into a 24/7 dopamine cycle of “prompt, review, ship” because the feedback loop is so addictive. You are also not blocked by humans anymore. Just the speed of inference. You are in full control now. But the objective is to build a life that is sustainable over decades, not just weeks. Work is a subset of a meaningful life, not the entirety of it. True success is found in long-term happiness and the satisfaction of being present for the people who matter most. If your workflow requires you to burn your “human capital” today at the expense of your future joy, social relationships, and family stability, the architecture is fundamentally flawed. Pace yourself, and keep in mind that you have years, or even decades, ahead. Often something that seems like a major, super important thing today would be some small, irrelevant blip in the perspective of a month or a year, not even mentioning your entire career.</p>

<h3>The Bottom Line</h3>

<p>Ship smarter code, not just more code. Leverage the tools, but don’t become a component of them. Your creativity and your presence for your family are finite resources that require maintenance.</p>

<p>The best engineers in the AI era won’t be the ones who prompt the fastest; they’ll be the ones who stay human enough to know which problems are actually worth solving. Prioritize the foundation of your life. Not just for your sake, but for the sake of the work itself.</p>]]></content><author><name></name></author><category term="programming" /><category term="AI" /><category term="GenAI" /><summary type="html"><![CDATA[We are witnessing the most significant shift in software engineering since the move to high-level languages. With AI coding tools handling the "manual labor" (boilerplate, unit tests, and refactoring), it’s easy to feel like the value of the human engineer is being compressed. But the opposite is true. As the cost of generating code drops to near zero, the value of human judgment and the necessity of personal sustainability scale exponentially.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jj09.net/assets/2026/beyond-the-prompt.jpg" /><media:content medium="image" url="https://jj09.net/assets/2026/beyond-the-prompt.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">It’s never been a better time to be a developer!</title><link href="https://jj09.net/its-never-been-a-better-time-to-be-a-developer/" rel="alternate" type="text/html" title="It’s never been a better time to be a developer!" /><published>2026-01-14T00:00:00+00:00</published><updated>2026-01-14T00:00:00+00:00</updated><id>https://jj09.net/its-never-been-a-better-time-to-be-a-developer</id><content type="html" xml:base="https://jj09.net/its-never-been-a-better-time-to-be-a-developer/"><![CDATA[<p><img src="/assets/2026/ai-engineer.png" alt="AI Engineer" title="AI Engineer" /></p>

<p><a href="https://www.linkedin.com/in/guthriescott/">Scott Guthrie</a> (Microsoft VP of Cloud) used to finish his tech talks at <a href="https://www.linkedin.com/posts/guthriescott_msbuild-activity-7202101710213816321--WRP">various Microsoft conferences</a> with the phrase <strong>“It’s never been a better time to be a developer”</strong>. It was an inspirational catch-phrase, which of course, was correct: technology is always better today than it was yesterday.</p>

<p>However, in 2026, we enter a new era of “better”!</p>

<h3>Improved Developer Efficiency</h3>

<p>We used to dream about AI pair programmers. Now we have them, and they’re shockingly good.
Claude Code, Github Copilot, Cursor, and other agentic coding assistants aren’t just autocomplete on steroids. They’re turning weeks of work into hours, and it’s not stopping at code completion:</p>
<ul>
  <li>Natural language → full apps (v0, Replit Agent, Devin-style agents)</li>
  <li>AI reviewing your PRs and catching bugs before humans do</li>
  <li>Automated testing generation (<a href="https://martinfowler.com/articles/is-tdd-dead/">Is TDD finally dead?</a>)</li>
  <li>Documentation that writes itself</li>
  <li>Refactoring legacy codebases with a single prompt (or <a href="https://www.reddit.com/r/technology/comments/1pu1bop/microsoft_to_replace_all_cc_code_with_rust_by_2030/">a few prompts</a>)</li>
  <li>Fixing errors by simply copy/pasting them into Claude Code</li>
</ul>

<p>We’re moving from “write code” → “guide AI to write code” → “describe what you want and let the AI figure out the how.”</p>

<p><img src="/assets/2026/writing-guiding-describing.jpg" alt="Writing code to guiding AI to write code to describing what you want and let AI figure it out" title="Writing code to guiding AI to write code to describing what you want and let AI figure it out" /></p>

<p>A lot of old friction is quietly gone. Developers spend less time on plumbing and more time on creative, high-value work: solving real problems and inventing new features. We can do things much more efficiently now!</p>

<h3>The era of solo-preneurs</h3>

<p>Perhaps the most mind-blowing shift in 2026 is how AI has supercharged the rise of the <a href="https://x.com/solofounding/status/2013671125147099308"><strong>solo-preneurs</strong></a> - developers building profitable businesses entirely alone, with no employees, no VC, and often while traveling the world.</p>

<p>A single engineer can build something that would have required a full engineering team 10 years ago. AI agents handle marketing, customer support, content creation, and even outbound sales. No-code + AI enables anyone to spin up MVPs in just days. Founders are launching AI-powered tools that solve “boring but profitable” problems - and scaling to 6–7 figures without ever hiring.</p>

<p>Solo founders now start 36.3% of all new companies (<a href="https://x.com/solofounding/status/2013671125147099308">source</a>)!</p>

<h3>But wait... aren’t we all going to lose our jobs?</h3>

<p>The <strong>AI doomer</strong> crowd has been loud. Headlines scream that <strong>90% of code will be AI-generated by the end of 2026</strong>, entry-level jobs are collapsing, and predictions from big names like Dario Amodei or Geoffrey Hinton warn of massive white-collar displacement - even up to 50% of entry-level roles gone in the next few years.</p>

<p>The job market has shifted. Routine coding gigs are shrinking, juniors face a tougher ramp, and some companies are leaning harder on fewer, more senior people augmented by AI.</p>

<p>However, <strong>AI isn’t replacing developers - it’s replacing un-augmented developers</strong>. Demand for AI engineers, system architects, security specialists, and people who can steer these powerful tools is actually increasing.</p>

<p>History shows tech shifts don’t destroy jobs net-net. They create more jobs in the long run. Example: the internet didn’t eliminate jobs - it multiplied them. AI is doing the same: more software is being built, more problems solved, more industries digitized. Companies that adopt AI don’t shrink teams forever. They scale output, tackle bigger challenges, and open new markets.</p>

<p>The winners? Those who adapt. Human + AI hybrids - AI Engineers.</p>

<p>Think about how many problems still have to be solved: <a href="/genai-healthcare-education/">healthcare</a>, <a href="/genai-healthcare-education/">education</a>, transportation, package delivery, meal prep and shopping, even planning and booking a trip still requires tremendous effort spent on research. In the AI era all these things should be more efficient. Here is the opportunity!</p>

<h3>Is This Time Really Different?</h3>

<p>Yes! It’s not just a better time to be a developer.</p>

<p><strong>It’s the best time in history…and tomorrow will probably top it.</strong></p>

<p>We’re not just getting better tools. We’re getting a new kind of collaborator that gets smarter every month. Mundane tasks can be delegated to agents, and we can focus on the fun part of the job. Thinking clearly, using AI deliberately, and building unfair advantages in the area of your passion is the way! Becoming AI-native, and adapting AI-maximalists approach is low-risk high-reward approach.</p>

<p>Happy coding (and prompting) 🚀</p>]]></content><author><name></name></author><category term="programming" /><category term="AI" /><category term="GenAI" /><summary type="html"><![CDATA[Scott Guthrie (Microsoft VP of Cloud) used to finish his tech talks at various Microsoft conferences with the phrase “It’s never been a better time to be a developer”. It was an inspirational catch-phrase, which of course, was correct - technology is always better today than it was yesterday. However, in 2026, we enter a new era of “better”!]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jj09.net/assets/2026/ai-engineer.png" /><media:content medium="image" url="https://jj09.net/assets/2026/ai-engineer.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">AI in 2025 is what the Internet was in 1999</title><link href="https://jj09.net/ai-era/" rel="alternate" type="text/html" title="AI in 2025 is what the Internet was in 1999" /><published>2025-01-01T00:00:00+00:00</published><updated>2025-01-01T00:00:00+00:00</updated><id>https://jj09.net/ai-era</id><content type="html" xml:base="https://jj09.net/ai-era/"><![CDATA[<p><img src="/assets/2025/the-internet-age.png" alt="From the Internet age to AI age" title="From the Internet age to AI age" /></p>

<p>Back in 1999, the internet was an emerging phenomenon poised to reshape the global economy. Web-based companies were popping up almost daily, and adventurous entrepreneurs saw the writing on the wall: the internet was not just a fad—it was the future. Fast forward to 2025, and we find ourselves at a similar inflection point with Artificial Intelligence (AI). The same sense of untapped potential and transformative power that characterized the internet in its early days now permeates the world of AI.</p>

<!--more-->

<h3>The Parallels Between AI in 2025 and the Internet in 1999</h3>

<ol>
  <li><b>Exponential Growth</b>: In 1999, the internet user base grew at staggering rates. Similarly, AI models are proliferating and improving in capability at an unprecedented pace. The continual refinement of machine learning algorithms and increases in computational power are driving hyper-growth in AI adoption.</li>
  <li><b>New Business Models</b>: Remember how e-commerce, digital advertising, and online media changed the landscape in the early 2000s? AI is now doing the same, enabling entirely new business models—from personalized AI-driven healthcare to autonomous robots delivering groceries.</li>
  <li><b>Massive Infrastructure Needs</b>: Just as the dot-com era demanded new infrastructure (broadband, servers, global connectivity), AI demands specialized hardware (GPUs, TPUs, advanced cloud services) and robust data pipelines to handle its ever-growing computing needs.</li>
  <li><b>Emerging Standards and Regulatory Frameworks</b>: Much like the early web’s “Wild West” era before regulations caught up, AI standards and policies are still being formulated. This creates both uncertainty and opportunity for businesses able to navigate these evolving landscapes.</li>
</ol>

<h3>Areas Worth Investing in the World of AI Agents</h3>

<p>There are numerous <a href="https://singularityhub.com/2024/12/27/is-2025-the-year-ai-agents-take-over-industry-bets-billions-on-ais-killer-app/">articles</a> and <a href="https://x.com/bindureddy/status/1874568118439461254">tweets (Xs)</a> predicting that 2025 will be the year of AI Agents. Here are ChatGPT o1 predictions for areas worth investing in 2025:</p>

<ol>
  <li><b>Generative AI for Content Creation</b>
    <ul>
      <li><b>Why It Matters</b>: Large Language Models (LLMs) are revolutionizing how businesses automate writing, design, and even coding tasks. These generative models are enabling new tools for bloggers, marketers, and software developers alike.</li>
      <li><b>Potential Opportunities</b>: AI-driven copywriting platforms, automated marketing suites, and creative design tools.</li>
    </ul>
  </li>
  <li><b>Healthcare and Life Sciences</b>
    <ul>
      <li><b>Why It Matters</b>: From AI-assisted diagnostics to drug discovery, intelligent systems can process vast amounts of data more efficiently than humans, speeding up medical breakthroughs and improving patient outcomes. I wrote more about this in my previous blog post <a href="/genai-healthcare-education/">Unlocking the Future: How GenAI is Set to Revolutionize Healthcare and Education
</a>.</li>
      <li><b>Potential Opportunities</b>: AI-based diagnostic imaging, predictive analytics for patient care, drug discovery platforms, and precision medicine.</li>
    </ul>
  </li>
  <li><b>Personalized Education and Training</b>
    <ul>
      <li><b>Why It Matters</b>: The era of 1 teacher in the classroom teaching the same thing at the same pace for different kids is over. The pandemic highlighted the importance of flexible and effective e-learning solutions. Adaptive learning platforms that leverage AI can tailor course materials to individual needs. I wrote more about this in my previous blog post <a href="/genai-healthcare-education/">Unlocking the Future: How GenAI is Set to Revolutionize Healthcare and Education
</a>.</li>
      <li><b>Potential Opportunities</b>: AI tutors, automated grading systems, personalized curriculum design, and educational analytics.</li>
    </ul>
  </li>
  <li><b>AI Integration in Robotics and Automation</b>
    <ul>
      <li><b>Why It Matters</b>: AI-driven robotics can revolutionize manufacturing, supply chain, and logistics. Self-navigating drones, automated warehouse systems, and assistive robots are no longer concepts but realities.</li>
      <li><b>Potential Opportunities</b>: Autonomous vehicles, industrial robotics, robotic process automation for businesses, and human-robot collaboration software.</li>
    </ul>
  </li>
  <li><b>Smart Infrastructure and IoT</b>
    <ul>
      <li><b>Why It Matters</b>: The Internet of Things (IoT) has grown immensely, but leveraging real-time data streams effectively demands AI’s predictive and analytical muscle.</li>
      <li><b>Potential Opportunities</b>: Smart city management solutions, energy efficiency tools, predictive maintenance platforms, and consumer-facing IoT devices.</li>
    </ul>
  </li>
  <li><b>AI-Powered Cybersecurity</b>
    <ul>
      <li><b>Why It Matters</b>: As AI proliferates, so do cyber threats. Sophisticated attackers leverage AI to breach systems; robust AI-based defense tools are essential to counter these challenges.</li>
      <li><b>Potential Opportunities</b>: Next-generation threat detection systems, automated incident response, and AI-driven vulnerability assessment tools.</li>
    </ul>
  </li>
</ol>

<h3>Looking Ahead</h3>

<p>In 1999, countless internet startups failed, while a select few soared to unimaginable heights. A similar phenomenon will play out in 2025 with AI. Not every AI idea will succeed, but the ones that do could shape entire industries and redefine how we live and work.</p>

<p>For investors, entrepreneurs, and tech enthusiasts, the lesson from the dot-com era is clear: do your due diligence but don’t be afraid to place strategic bets on AI. Whether it’s innovative hardware, scalable software platforms, or specialized services, the AI revolution has arrived. Much like the internet did at the dawn of the new millennium, AI is set to generate trillions of dollars in economic value—and change the world in the process.</p>

<p>Just as the internet in 1999 opened a new digital frontier, AI in 2025 is the gateway to a future where machines and humans work together more intelligently, efficiently, and creatively than ever before. Now is the time to jump on board. The next wave of tech disruption is here, and it’s driven by AI.</p>

<p>Happy New Year!</p>]]></content><author><name></name></author><category term="programming" /><category term="AI" /><category term="GenAI" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jj09.net/assets/2025/the-internet-age.png" /><media:content medium="image" url="https://jj09.net/assets/2025/the-internet-age.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Deep Work for Software Engineers: 3 steps to productivity</title><link href="https://jj09.net/productivity/" rel="alternate" type="text/html" title="Deep Work for Software Engineers: 3 steps to productivity" /><published>2024-10-09T00:00:00+00:00</published><updated>2024-10-09T00:00:00+00:00</updated><id>https://jj09.net/productivity</id><content type="html" xml:base="https://jj09.net/productivity/"><![CDATA[<p><img src="/assets/2024/focus-deep-work.webp" alt="Deep focus work" title="Deep focus work" /></p>

<p>In a world filled with constant notifications, meetings, and distractions, staying productive can feel like a never-ending battle. But with the right strategies in place, you can take control of your time and energy. Here are four powerful productivity tips to help you work smarter, not harder.</p>

<h3>1. Block Mornings for Focused Work</h3>

<p>Mornings are a golden time for productivity, but only if you protect them. Block off your mornings (ideally until lunch) for <a href="https://amzn.to/4gVozd3">deep, focused work</a>. This is when your mind is at its freshest and you’re less likely to be interrupted. By avoiding meetings and non-essential tasks in the morning, you can dive into your most challenging projects with clarity and concentration. This will enable you to get into the <a href="https://amzn.to/4eIy1Px">Flow</a> state.</p>

<p>Shoot for at least 2h uninterrupted time in the morning.</p>

<p>Tip: Use your calendar to set aside this time as “focus time” and communicate it to your team. Let them know you’re unavailable for meetings or calls during these hours. Reserve the afternoon for meetings, which can be more distracting and disrupt your flow.</p>

<h3>2. Eat That Frog First Thing in the Morning</h3>

<p>This strategy comes from Brian Tracy’s famous concept: <a href="https://amzn.to/4gX90lc">Eat That Frog</a>. The idea is simple — tackle the most important and often the most difficult task first thing in the morning.</p>

<p>It is important to identify the task day before! Then, without checking emails, messages, or getting sidetracked by other projects, dive right into it when you start your day.</p>

<p>This approach helps you overcome procrastination and ensures that you’re making meaningful progress on your priorities. Plus, once you’ve handled the toughest task, the rest of the day feels easier in comparison.</p>

<h3>3. Identify 3 top priorities for the week (and month, and year)</h3>

<p>At the start of every week, create a list of the top 3 most important things you need to accomplish. These should be tasks that will make the biggest impact on your work or long-term goals. Having this list ensures that you always know where to focus your energy, even when the week gets busy.</p>

<p>Once you have your top three tasks, prioritize them ruthlessly. Be willing to say no to distractions, lower-priority tasks, and meetings that don’t align with these goals. By focusing on the most impactful work, you can ensure that your time is spent on what truly matters, rather than getting lost in endless to-dos or smaller tasks.</p>

<p>Tip: Revisit your list every morning and align your daily tasks with these top three priorities. This helps you maintain momentum throughout the week and keeps you on track.</p>

<p>Tip 2: add new things to the backlog for next week. Most of the time, the ‘urgent’ thing can wait. Many times, after a few days, the ‘urgent’ thing is not urgent anymore :)</p>

<h3>4. Bonus: No Meetings Wednesday</h3>

<p>Meetings can be one of the biggest productivity killers. One way to manage them more effectively is to institute a “No Meetings Wednesday” policy. Dedicate one full day of your week to deep work, uninterrupted by meetings or calls. Use this time to catch up on big projects, focus on strategic thinking, or tackle work that requires extended concentration.</p>

<p>With this day blocked off, you’ll not only have a regular opportunity to get ahead on your work, but you’ll also give your mind the space it needs to recharge creatively.</p>

<h3>Conclusion</h3>

<p>Incorporating these strategies into your routine can help you take control of your time and energy. By blocking mornings for focus work, tackling your most important tasks first, focusing on your top three priorities for the week, and keeping Wednesdays meeting-free, you’ll create the conditions for consistent productivity and meaningful progress.</p>

<p>Start small by testing these ideas and see what works best for you!</p>]]></content><author><name></name></author><category term="productivity" /><category term="productivity" /><category term="tips" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jj09.net/assets/2024/focus-deep-work.webp" /><media:content medium="image" url="https://jj09.net/assets/2024/focus-deep-work.webp" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Unlocking the Future: How GenAI is Set to Revolutionize Healthcare and Education</title><link href="https://jj09.net/genai-healthcare-education/" rel="alternate" type="text/html" title="Unlocking the Future: How GenAI is Set to Revolutionize Healthcare and Education" /><published>2024-10-02T00:00:00+00:00</published><updated>2024-10-02T00:00:00+00:00</updated><id>https://jj09.net/genai-can-revolutionize-healthcare-and-education</id><content type="html" xml:base="https://jj09.net/genai-healthcare-education/"><![CDATA[<p><img src="/assets/2024/genai-healthcare-education.png" alt="Generative AI in Healthcare and Education" title="Generative AI in Healthcare and Education" /></p>

<p>Healthcare is currently lagging behind in fully leveraging existing technology. We have technology that can predict what products you might want to buy before you even realize it (like targeted ads), but we aren’t yet applying similar advancements in healthcare to predict potential diseases.</p>

<p>In education, the days of needing to physically go to a building to hear from someone with specialized knowledge are long gone. The traditional “one-size-fits-all” approach, where everyone is taught the same material at the same pace, can be greatly enhanced with personalized curriculums. These tailored programs can focus on students’ strengths while addressing their learning gaps, creating a more effective and individualized educational experience.</p>

<p>Generative AI (GenAI) is rapidly transforming various industries, but its potential to revolutionize healthcare and education stands out. The combination of advanced data processing, machine learning, and natural language understanding has given GenAI the ability to reshape how we approach learning and patient care.</p>

<h3>GenAI in Healthcare: A New Era of Personalized Medicine</h3>

<p>In healthcare, the adoption of artificial intelligence has already made significant strides, but GenAI offers new possibilities for innovation. Here are key areas where it can make a substantial difference:</p>

<h4>1. Medical Diagnosis and Decision Support</h4>

<p>GenAI models, trained on massive datasets, can analyze patient records, medical images, and lab results to help clinicians make more accurate diagnoses. These models can provide suggestions for treatment options based on the latest medical research, patient history, and even subtle patterns that may be overlooked by human professionals. This can drastically reduce diagnostic errors and support decision-making in complex cases.</p>

<p>For example, a GenAI system could scan thousands of radiology images in seconds, identifying early signs of cancer or other conditions with remarkable precision. As the system learns from new cases, it becomes even more adept at identifying anomalies that may escape the human eye, speeding up diagnosis and improving outcomes.</p>

<h4>2. Personalized Treatment Plans</h4>

<p>Traditional healthcare often follows a “one-size-fits-all” approach, but GenAI opens the door to personalized medicine. By analyzing a patient’s genetic makeup, lifestyle factors, and medical history, GenAI can suggest highly tailored treatment plans. This can lead to more effective therapies and fewer adverse reactions, improving patient outcomes and satisfaction.</p>

<p>Imagine an AI-driven platform that can create a personalized healthcare plan that adjusts as new information becomes available, like changes in a patient’s condition or the discovery of new research. This real-time adaptability can help physicians stay on top of their patients’ evolving needs.</p>

<h4>3. Virtual Health Assistants</h4>

<p>Another exciting development is the rise of virtual health assistants powered by GenAI. These digital tools can interact with patients to answer health-related questions, provide medication reminders, or even monitor chronic conditions.</p>

<p>By offering continuous, 24/7 assistance, these tools can alleviate pressure on healthcare professionals, ensuring that patients receive timely advice and support without overwhelming the healthcare system.</p>

<p>Every person should have their own Health wallet with their medical history that can access disease prediction and health recommendation models.</p>

<h3>GenAI in Education: Transforming Learning Experiences</h3>

<p>While healthcare is one of the most obvious beneficiaries, education is another field where GenAI is poised to make a massive impact. Here’s how it can change the landscape:</p>

<h4>1. Personalized Learning Pathways</h4>

<p>In education, students often struggle with standardized curricula that may not align with their learning styles or pace. GenAI can help create personalized learning experiences that adapt to the needs of individual students. By analyzing performance data, learning preferences, and areas of difficulty, GenAI-driven platforms can offer customized exercises, recommend resources, and provide real-time feedback to enhance learning outcomes.</p>

<p>For instance, students who are struggling in math could receive tailored problem sets that gradually build their skills, while those excelling might be offered more challenging material to push their abilities further. This adaptive approach allows students to learn at their own pace and receive the targeted support they need to thrive.</p>

<h4>2. AI-Powered Tutors and Assistance</h4>

<p>AI-driven tutoring systems can offer one-on-one support to students, helping explain difficult concepts or answering questions in real time. These systems can be accessible around the clock, providing students with a resource when teachers or human tutors may not be available.</p>

<p>These virtual tutors don’t just help with academic content—they can also monitor emotional states and adjust their responses based on the student’s engagement level. By recognizing when a student is confused or frustrated, AI systems can adapt their teaching approach, ensuring that learning remains productive and stress-free.</p>

<h4>3. Enhancing Educational Content Creation</h4>

<p>One of the most promising uses of GenAI in education is its ability to create high-quality educational content quickly. Teachers can use AI to generate lesson plans, quizzes, and learning materials that are tailored to specific student needs or curriculum standards. In addition, GenAI can help generate multimedia content, including interactive simulations, videos, and digital textbooks, making education more engaging and accessible.</p>

<p>By offloading the more time-consuming aspects of content creation to AI, teachers can focus on delivering more personalized, hands-on instruction, ultimately improving the classroom experience.</p>

<h3>Call to Action</h3>

<p>Are you passionate about transforming healthcare and education through Generative AI? If you’re interested in exploring the incredible potential of AI-driven innovation in these fields, let’s connect! Whether you’re a healthcare professional, educator, or simply an enthusiast ready to make an impact, I’d love to hear from you.</p>

<p>Reach out to me if you’re ready to be part of this exciting journey and collaborate on shaping the future of personalized medicine and adaptive learning.</p>

<p>Contact me today—together, we can drive revolutionary change!</p>]]></content><author><name></name></author><category term="programming" /><category term="AI" /><category term="GenAI" /><category term="BigIdeas" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jj09.net/assets/2024/genai-healthcare-education.png" /><media:content medium="image" url="https://jj09.net/assets/2024/genai-healthcare-education.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Generative AI: 10 things to know</title><link href="https://jj09.net/generative-ai-10-things-to-know/" rel="alternate" type="text/html" title="Generative AI: 10 things to know" /><published>2023-08-26T00:00:00+00:00</published><updated>2023-08-26T00:00:00+00:00</updated><id>https://jj09.net/generative-ai-10-things-to-know</id><content type="html" xml:base="https://jj09.net/generative-ai-10-things-to-know/"><![CDATA[<p><img src="/assets/2023/generative-ai.jpeg" alt="Generative AI" title="Generative AI" /></p>

<p>Generative Artificial Intelligence (GenAI) has shown remarkable advancements in recent years and has the potential to revolutionize various industries by automating creative tasks, generating realistic content, and assisting humans in creative endeavors. GenAI is a subset of AI techniques and models that are designed to generate new text, images, videos, or audio content that is similar to what humans produce. This article explains the 10 most important things you need to know about the Generative AI world.</p>

<h3>1. Large Language Model (LLM)</h3>

<p>A large language model (LLM) is characterized by its large size. LLM can have over a trillion parameters, and take multiple Gigabytes of storage. Its size is enabled by AI accelerators, which can process vast amounts of text data, mostly scraped from the Internet. LLM is a <a href="https://en.wikipedia.org/wiki/Artificial_neural_network">neural network</a> that can contain from tens of millions and up to billions of weights and is (pre-)trained using <a href="https://en.wikipedia.org/wiki/Self-supervised_learning">self-supervised learning</a> and <a href="https://en.wikipedia.org/wiki/Weak_supervision">semi-supervised learning</a>.</p>

<p>Language models work by taking an input text and repeatedly predicting the next token or word. Up to 2020, fine-tuning was the only way a model could be adapted to be able to accomplish specific tasks. Larger-sized models, such as <a href="https://openai.com/gpt-4">GPT-4</a> or <a href="https://ai.meta.com/llama/">Llama 2</a>, however, can be prompt-engineered to achieve similar results.</p>

<p>You can find a lot of open-source Large Language Models on <a href="https://huggingface.co/models">HuggingFace</a>, and build products on top of them!</p>

<h3>2. Fine-tuning</h3>

<p>You take a trained LLM and use new data to update its parameters for new settings or repurpose it for new applications. Fine-tuning can be done on the entire neural network, or on only a subset of its layers, in which case the layers that are not being fine-tuned are “frozen” (not updated during the backpropagation step).</p>

<p>To get a glimpse into the fine-tuning process check <a href="https://www.youtube.com/watch?v=Q9zv369Ggfk">“okay, but I want GPT to perform 10x for my specific use case” - Here is how</a>.</p>

<p>To learn more details about Fine-tuning LLMs I recommend:</p>
<ul>
  <li><a href="https://www.deeplearning.ai/short-courses/finetuning-large-language-models/">Fine-tuning Large Language Models</a> from <a href="https://www.deeplearning.ai/">DeepLearning.AI</a></li>
  <li><a href="https://teetracker.medium.com/fine-tuning-llms-9fe553a514d0">Fine-tuning LLMs</a></li>
  <li><a href="https://www.simform.com/blog/completeguide-finetuning-llm/">A Complete Guide to Fine Tuning Large Language Models</a></li>
</ul>

<h3>3. Prompt engineering</h3>

<p>Prompt engineering is a technique used in working with AI models, especially in the context of text-to-text models. It involves crafting well-structured and specific instructions or queries, known as prompts, to guide the AI in generating desired outputs. These prompts help the AI understand the task and generate relevant and accurate responses. Prompt engineering can include providing context, examples, instructions, or even specifying styles to ensure the AI produces the intended results.</p>

<p>You might be heard about Zero-shot prompting or few-shot prompting. Zero-shot prompting involves instructing a model to perform a task or generate text without providing any specific examples or labeled data during inference. This is what we usually do when using ChatGPT.</p>

<p>Example of zero-shot prompt:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>What's the sentiment of this review: "It doesnt work!"
</code></pre></div></div>

<p>ChatGPT response:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The sentiment of the review "It doesn't work!" is negative.
</code></pre></div></div>

<p>Few-shot prompting, on the other hand, provides the model with a few examples or context during inference.</p>

<p>Example of a few-shot prompt:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Great product, 10/10: positive
Didn't work very well: negative
Super helpful, worth it: positive
It doesnt work!:
</code></pre></div></div>

<p>ChatGPT response:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The sentiment of the review "It doesn't work!" is negative.
</code></pre></div></div>

<p>To learn more about Prompt Engineering I recommend <a href="https://learn.deeplearning.ai/chatgpt-prompt-eng">ChatGPT Prompt Engineering for Developers</a> from <a href="https://www.deeplearning.ai/">DeepLearning.AI</a>.</p>

<h3>4. Hallucination</h3>

<p>In the context of Generative AI, “hallucination” refers to a situation where an AI model generates content that is not accurate or coherent based on the input or context. It essentially creates information that doesn’t exist in the source data or doesn’t align with the intended output. Hallucinations can occur when AI models extrapolate too much from the given data or when they lack proper training on specific aspects, leading to the generation of unrealistic or inaccurate content.</p>

<p>Imagine a language model trained on a large dataset of medical information. When asked to generate a description of a medical condition, the model responds with a detailed explanation that includes symptoms and treatments that don’t exist in reality. This response is a hallucination because the AI has generated information that is not accurate based on its training data, creating a scenario that doesn’t align with actual medical knowledge.</p>

<p>Hallucination is one of the biggest challenges in creating Large Language Models. It’s often hard to distinguish hallucination from accurate information in AI-generated content. That’s why it’s important to critically evaluate AI outputs and cross-reference them with reliable sources before considering them as factual information.</p>

<h3>5. Transformer</h3>

<p>A Transformer is a type of deep learning model architecture used in natural language processing tasks. Transformers are designed to handle sequential data, like sentences in language, in a more effective and context-aware way. Thanks to transformers we can train LLMs faster.</p>

<p>The core innovation of the Transformer is its “self-attention” mechanism, which allows the model to weigh the importance of different words in a sentence relative to each other. This enables the model to capture contextual relationships between words, making it well-suited for tasks like machine translation, text summarization, question-answering, and more. Transformers have become the foundation for various state-of-the-art natural language processing models, including <a href="https://en.wikipedia.org/wiki/Generative_pre-trained_transformer">GPT</a> (Generative Pre-trained Transformer) and <a href="https://en.wikipedia.org/wiki/BERT_(language_model)">BERT</a> (Bidirectional Encoder Representations from Transformers).</p>

<p>The Transformer model is highly parallelizable, which makes it well-suited for training on large datasets using modern hardware such as GPUs or TPUs. It has been shown to achieve state-of-the-art performance on a wide range of natural language processing tasks, including machine translation, language modeling, and text classification.</p>

<h3>6. GPT</h3>

<p>GPT stands for “Generative Pre-trained Transformer.” GPT models are designed to understand and generate human-like text by learning patterns from massive amounts of text data. The “Transformer” architecture they’re based on enables them to consider the context of words and phrases in a text, making their generated content coherent and contextually relevant. These models have been widely used for tasks like text generation, language translation, and more.</p>

<p><a href="https://openai.com/gpt-4">GPT-4</a> is used by <a href="https://chat.openai.com/">ChatGPT</a>.</p>

<h3>7. BERT</h3>

<p>BERT stands for “Bidirectional Encoder Representations from Transformers.” It’s a sophisticated pre-trained language model developed by Google AI. BERT’s key innovation is its bidirectional context understanding, meaning it considers both the left and right context of words in a sentence. This makes it particularly skilled at understanding the nuances of language and context.</p>

<p>BERT is often used as a base model for various natural language processing tasks, like text classification, question answering, sentiment analysis, and more. By fine-tuning BERT on specific tasks, it can provide impressive results due to its advanced contextual understanding.</p>

<p>There are a lot of <a href="https://huggingface.co/models?sort=trending&amp;search=bert">BERT-based models on HuggingFace</a>.</p>

<h3>8. Embeddings</h3>

<p>Embeddings in AI are numerical representations of objects or concepts within a higher-dimensional space. They are used to capture the relationships and meanings between these objects in a way that a machine-learning model can understand and work with.</p>

<p>In natural language processing, word embeddings are commonly used to represent words as dense vectors. These vectors are designed so that similar words have similar embeddings, and their relative distances in the vector space reflect semantic relationships. Embeddings allow AI models to process and analyze textual data by converting words into numerical forms that retain their contextual meanings.</p>

<p>To create embeddings you need a pre-trained <a href="https://huggingface.co/blog/getting-started-with-embeddings">embedding model</a>. There are a lot of <a href="https://huggingface.co/spaces/mteb/leaderboard">open-source embedding models on HuggingFace</a>.</p>

<p>To learn more about embeddings check <a href="https://towardsdatascience.com/neural-network-embeddings-explained-4d028e6f0526">Neural Network Embeddings Explained</a> - it’s a great, detailed overview of embeddings.</p>

<h3>9. Vector databases</h3>

<p>Vector databases are specialized databases designed to efficiently store and query high-dimensional vector data. These databases are optimized for working with data that can be represented as vectors in a multi-dimensional space, such as embeddings from machine learning models or other numerical representations.</p>

<p>Vector databases offer efficient indexing and querying mechanisms that allow you to perform similarity searches, finding vectors that are closest in terms of distance to a given query vector. They are commonly used in various applications, including recommendation systems, content retrieval, image search, and natural language processing tasks where vector representations play a crucial role in understanding and comparing data points.</p>

<p>The most popular Vector databases are <a href="https://www.pinecone.io/">Pinecone</a> and <a href="https://www.trychroma.com/">chroma</a>.</p>

<p>Other database engines are starting to introduce vector search capabilities:</p>
<ul>
  <li><a href="https://www.mongodb.com/blog/post/introducing-atlas-vector-search-build-intelligent-applications-semantic-search-ai">MongoDB Vector Search</a></li>
  <li><a href="https://redis.io/docs/interact/search-and-query/search/vectors/">Redis Vector Similarity</a></li>
  <li><a href="https://www.youtube.com/watch?v=Bd9LWW4cxEU">Vector Search in Azure Search</a></li>
  <li><a href="https://www.elastic.co/elasticsearch/vector-database">Elasticsearch Vector Database</a></li>
</ul>

<p>The most powerful application of Vector databases is to use them as <a href="https://www.linkedin.com/pulse/3-ways-vector-databases-take-your-llm-use-cases-next-level-mishra/">knowledge extensions or long-term memory for Large Language Models</a>. This is how I created <a href="/chatgpt-for-your-data">ChatGPT for your data</a>.</p>

<p>Check <a href="https://www.pinecone.io/learn/vector-database/">What is a Vector Database?</a> from Pinecone to learn about details of how vector databases work.</p>

<h3>10. Prompt injection</h3>

<p>Prompt injection is a technique of adding carefully crafted prompts or instructions to the input of a language model to influence its generated output. This is commonly used in the context of fine-tuning or guiding the behavior of a generative AI model. By injecting prompts, developers can guide the model’s responses toward specific themes, tones, or styles, making the generated content more aligned with their desired outcomes. Prompt injection is a way to shape the AI’s creative process while maintaining control over the generated results.</p>

<p>Prompt injection can be also a vulnerability. Prompt Injection in the world of Generative AI is similar to what SQL Injection is in the world of databases. Check <a href="https://simonwillison.net/2022/Sep/12/prompt-injection/">Prompt injection attacks against GPT-3</a> to learn more.</p>

<h3>Summary</h3>

<p>To learn more GenAI terms check out <a href="https://medium.com/@shuchaobi/glossary-of-llm-and-generative-ai-b3111da41da7">Glossary of LLM and Generative AI</a>.</p>

<p>Check out my previous post about <a href="/chatgpt-for-your-data">ChatGPT for your data</a> where I created Q&amp;A app for querying your own data with natural language. Like you would ask ChatGPT.</p>]]></content><author><name></name></author><category term="programming" /><category term="AI" /><category term="GenAI" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://jj09.net/assets/2023/generative-ai.jpeg" /><media:content medium="image" url="https://jj09.net/assets/2023/generative-ai.jpeg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">ChatGPT for your data</title><link href="https://jj09.net/chatgpt-for-your-data/" rel="alternate" type="text/html" title="ChatGPT for your data" /><published>2023-08-20T00:00:00+00:00</published><updated>2023-08-20T00:00:00+00:00</updated><id>https://jj09.net/chatgpt-for-your-data</id><content type="html" xml:base="https://jj09.net/chatgpt-for-your-data/"><![CDATA[<p><img src="/assets/2023/data-information-knowledge-insight-wisdom.jpeg" alt="Data, information, knowledge, insight, wisdom" title="Data, information, knowledge, insight, wisdom" /></p>

<p>The everyday need to extract information from documents isn’t exclusive to analysts or data scientists—it’s a common requirement for all knowledge workers. Thanks to Large Language Models (LLMs), <a href="https://towardsdatascience.com/neural-network-embeddings-explained-4d028e6f0526">embeddings</a>, and <a href="https://www.pinecone.io/learn/vector-database/">vector databases</a> you can create a ChatBot, which can answer your questions about a particular set of data.</p>

<p>It’s a very powerful use case for building knowledge bases. Imagine that you want to become a triathlete. You probably have 100s of questions about how to even start. There are a lot of resources online to find answers, but it would be much better if you could just ask your questions and get answers right away.</p>

<p>ChatGPT is pretty good at answering questions you have, but it’s not yet good enough when it comes to nuanced details. The rationale behind this lies in its limited access to the entirety of online data. Hence, there is a huge market opportunity for building domain-specific knowledge bases.</p>

<h3>ChatBot for your data with LangChain and OpenAI</h3>

<p><a href="https://www.langchain.com/">LangChain</a> is a framework for building LLM-based apps. Using LangChain, and OpenAI API we can create a Q&amp;A app that points to chosen by you data source in a few lines of code.</p>

<p>To get started, you need Python, and a few packages:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip install langchain
pip install openai
pip install chromadb GitPython bs4 tiktoken
</code></pre></div></div>

<p>You also need an OpenAI API key. You can get it at <a href="https://platform.openai.com/account/api-keys">https://platform.openai.com/account/api-keys</a>.</p>

<p>New personal accounts get <span>$</span>5 free credit to use.</p>

<p>You need to put the OpenAI API key in <code class="language-plaintext highlighter-rouge">OPENAI_API_KEY</code> environmental variable. You can do that by adding the following line to your <code class="language-plaintext highlighter-rouge">~/.bash_profile</code> file on Mac:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export OPENAI_API_KEY=&lt;YOUR_OPENAI_API_KEY&gt;
</code></pre></div></div>

<p>Remember to run <code class="language-plaintext highlighter-rouge">source ~/.bash_profile</code> to load <code class="language-plaintext highlighter-rouge">OPENAI_API_KEY</code> into the environment.</p>

<h4>Query data from text file</h4>

<p>The simplest approach is to put your data into a text file.</p>

<p>This is a sample text file <code class="language-plaintext highlighter-rouge">data.txt</code> with a few details about me:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Jacob Jedryszek is a software engineer at Meta. He has experience in web and mobile development, product growth, cloud computing, and artificial intelligence.

He worked on the following products:
- 2014-2016 - Azure Portal
- 2016-2017 - Azure Mobile App
- 2017-2019 - Azure Search
- 2017-2019 - SeeingAI
- 2019-2021 - Facebook Marketplace
- 2022 - Super - Live Q&amp;A platform for creators

His hobbies are:
- cycling
- triathlons
- hiking
- sailing
- soccer
- martial arts
</code></pre></div></div>

<p>This Python program allows you to query that text file:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">langchain.document_loaders</span> <span class="kn">import</span> <span class="n">TextLoader</span>
<span class="kn">from</span> <span class="nn">langchain.indexes</span> <span class="kn">import</span> <span class="n">VectorstoreIndexCreator</span>

<span class="n">loader</span> <span class="o">=</span> <span class="n">TextLoader</span><span class="p">(</span><span class="s">"./data.txt"</span><span class="p">)</span>
<span class="n">index</span> <span class="o">=</span> <span class="n">VectorstoreIndexCreator</span><span class="p">().</span><span class="n">from_loaders</span><span class="p">([</span><span class="n">loader</span><span class="p">])</span>

<span class="k">print</span><span class="p">(</span><span class="n">index</span><span class="p">.</span><span class="n">query</span><span class="p">(</span><span class="s">"Who is Jacob?"</span><span class="p">))</span></code></pre></figure>

<p>The result of running the above program is:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; python qna-text.py
Jacob Jedryszek is a software engineer at Meta with experience in web 
and mobile development, product growth, cloud computing, and artificial 
intelligence. He has worked on several products, including Azure Portal, 
Azure Mobile App, Azure Search, SeeingAI, Facebook Marketplace, 
and Super. His hobbies include cycling, triathlons, hiking, sailing, 
soccer, and martial arts.
</code></pre></div></div>

<p>When I change the query to <code class="language-plaintext highlighter-rouge">When did Jacob work on SeeingAI?</code> I get the following answer:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; python qna-text.py
2017-2019
</code></pre></div></div>

<p>This is mind-blowing! With just 5 lines of code, you can easily extract insights from your data!</p>

<h4>Query data from web page</h4>

<p>You can also query data from a given URL. I recently wrote a blog post about weight loss. You can query it with LangChain by replacing <code class="language-plaintext highlighter-rouge">TextLoader</code> with <code class="language-plaintext highlighter-rouge">WebBaseLoader</code>:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">from</span> <span class="nn">langchain.document_loaders</span> <span class="kn">import</span> <span class="n">WebBaseLoader</span>
<span class="kn">from</span> <span class="nn">langchain.indexes</span> <span class="kn">import</span> <span class="n">VectorstoreIndexCreator</span>

<span class="n">loader</span> <span class="o">=</span> <span class="n">WebBaseLoader</span><span class="p">(</span><span class="s">"https://jj09.net/simple-path-to-weight-loss"</span><span class="p">)</span>
<span class="n">index</span> <span class="o">=</span> <span class="n">VectorstoreIndexCreator</span><span class="p">().</span><span class="n">from_loaders</span><span class="p">([</span><span class="n">loader</span><span class="p">])</span>

<span class="k">print</span><span class="p">(</span><span class="n">index</span><span class="p">.</span><span class="n">query</span><span class="p">(</span><span class="s">"How can I lose weight without exercising?"</span><span class="p">))</span></code></pre></figure>

<p>Running the above program gives the following answer:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; python qna-web.py
You can lose weight without exercising by counting calories and macros and eating fewer calories than you burn. A 500 calorie deficit per day can result in losing 1 pound per week.
</code></pre></div></div>

<h4>Other loaders</h4>

<p>As of today, there are a lot of different loaders available for LangChain. You can load data directly from PDF, Azure Storage, CSV files, Google Drive, Github, and more! You can learn how to use each loader at <a href="https://python.langchain.com/docs/integrations/document_loaders/">LangChain Loaders docs</a>.</p>

<h3>Using local LLM</h3>

<p>Using OpenAI API is pretty expensive. Depending on how large your document is you can quickly utilize free limit. Running 1000 queries on a document with ~3000 words would cost you <span>$7</span>. For more about pricing check <a href="https://gptforwork.com/tools/openai-chatgpt-api-pricing-calculator">OpenAI API Pricing Calculator</a>.</p>

<p>If you don’t want to pay for OpenAI API, you can use one of the open-source Large Language Models locally. OpenAI models are not open source, but there are a lot of <a href="https://huggingface.co/models">open-source models that you can find on HuggingFace</a>. Iván Martínez created an awesome Github repo <a href="https://github.com/imartinez/privateGPT">privateGPT</a>. It uses <a href="https://www.sbert.net/"><code class="language-plaintext highlighter-rouge">sentence-transformers</code></a> python package to create embeddings with one of the <a href="https://huggingface.co/spaces/mteb/leaderboard">Open Source embedding models</a>, and <a href="https://gpt4all.io/">GPT4All</a> - LLM wrapper to query your data with LangChain and LLM. <a href="https://www.youtube.com/watch?v=jxSPx1bfl2M">This Youtube video</a> shows step-by-step how to do it.</p>

<p>I got the best result by using <a href="https://huggingface.co/TheBloke/Nous-Hermes-13B-GGML/blob/main/nous-hermes-13b.ggmlv3.q4_0.bin">nous-hermes-13b.ggmlv3.q4_0.bin</a> model. It’s 7.32GB, while default <a href="https://gpt4all.io/models/ggml-gpt4all-j-v1.3-groovy.bin">ggml-gpt4all-j-v1.3-groovy.bin</a> is 3.79GB, but it produces much better results.</p>

<p>The tradeoff of running models locally is speed. OpenAI models are deployed in the cloud and run on super-fast GPUs. One query, on my local machine (MacBook M1), takes between 10 and 30 seconds! Of course, you can deploy open-source models to the cloud and run your apps on GPUs, but then you have to pay for it.</p>

<p>The big advantage is full control of your end-to-end flow. If OpenAI decides to close its API, or drastically increase price, there is very little you can do about it. Running open-source models in the cloud puts only one constraint on you: the cloud provider’s cost and their environment. These change much less often.</p>

<h3>Fine tunning LLM</h3>

<p>Another option to build a “ChatGPT for your data” is through Fine-tuning. It involves taking a pre-trained large language model (LLM) and adapting it to comprehend and respond to specific questions related to your dataset. By further training the model on a narrower dataset containing questions and answers relevant to your domain, you can enhance its ability to provide accurate and contextually relevant responses.</p>

<p>Finetuning allows the chatbot to learn the nuances of your data, making it more proficient in answering questions accurately. This approach can be particularly useful when you have a specific dataset with domain-specific information, and you want the chatbot to be a knowledgeable and effective tool for interacting with that data.</p>

<p>This approach is much more complicated than using embeddings, requires multiple steps, and more computational power. Additionally, you have to retrain your model whenever your data changes, which is more expensive than creating embeddings.</p>

<p>To see a sample Fine-tuning process, check out <a href="https://www.youtube.com/watch?v=Q9zv369Ggfk">how to make GPT perform 10x for your use case</a>.</p>

<h3>Summary</h3>

<p>You can build Q&amp;A ChatBot for your data with LangChain and OpenAI API. If you don’t want to pay for OpenAI API, you can use the Open Source model locally. This still requires computational power but might be cheaper than paying for OpenAI API if you run it in production.</p>

<p>You can also consider Fine-tuning the pre-trained model. It’s more complicated, requires significant computational power, and you need to re-train your model whenever your dataset changes.</p>

<p>In the end, you can build products like <a href="https://chatpdf.com">Chat with any PDF</a>.</p>

<p>To learn more details, and how to build a more advanced ChatBot for knowledge base check out <a href="https://www.deeplearning.ai/short-courses/langchain-chat-with-your-data/">LangChain Chat with Your Data</a>.</p>

<p>If you want to learn more about LangChain Framework, I recommend <a href="https://www.deeplearning.ai/short-courses/langchain-for-llm-application-development/">LangChain for LLM Application Development</a>.</p>

<p><a href="https://docs.langchain.com/docs/">LangChain documentation</a> is also a pretty solid source of knowledge.</p>]]></content><author><name></name></author><category term="programming" /><category term="AI" /><category term="ChatGPT" /><category term="LangChain" /><category term="OpenAI" /><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Simple path to weight loss</title><link href="https://jj09.net/simple-path-to-weight-loss/" rel="alternate" type="text/html" title="Simple path to weight loss" /><published>2023-07-06T00:00:00+00:00</published><updated>2023-07-06T00:00:00+00:00</updated><id>https://jj09.net/simple-path-to-weight-loss</id><content type="html" xml:base="https://jj09.net/simple-path-to-weight-loss/"><![CDATA[<p>Are you tired of endless diets, complex workout regimens, and conflicting advice on your quest to shed those extra pounds? Weight loss doesn’t have to be overwhelming or complicated. It’s all about calories in and calories out.</p>

<h3>My path to weight gain and weight loss</h3>

<p>At the end of last year, after becoming <a href="/i-am-an-ironman/">Ironman</a> I was roughly 10-15 lbs overweight. I was overeating on purpose to don’t get sick while training. With a full-time job, I didn’t have enough time to work, live, train, and rest enough. I assumed that food will compensate for lack of rest, and help with immunity. It worked. I didn’t get sick.</p>

<p>After Ironman I went for a week to Hawaii where I had way too much of Hula pie. On top of all that, there was Christmas food, and I got sick for over a month. None of these helped with weight. I gained another 5-10 lbs, which put me at 20-25 lbs overweight. I’ve never been so much overweight in my life.</p>

<p>I decided to make my weight a priority, and over the last 6 months, I lost almost 25 lbs. This put me at my optimal weight.</p>

<p>I didn’t use any rocket science diet. I ate moderately healthy, and I didn’t eat too much. I counted how many calories I consumed, and how many I burned. I also exercised, as that’s part of my routine now. I used a <a href="https://docs.google.com/spreadsheets/d/1ho7KNSEitzgiz5PB9VWgWV8A_0ZDG9HzVJyLJXNnGXA/edit?usp=sharing">spreadsheet</a> to keep track of everything.</p>

<p><img src="/assets/2023/weight-loss-chart.png" alt="Weight Loss Chart" title="Weight Loss Chart" /></p>

<h3>How many calories you can eat per day</h3>

<p>To find out how many calories you need daily you need to know your Basal Metabolic Rate (BMR). You can use <a href="https://www.calculator.net/bmr-calculator.html">Free BMR Calculator</a> to find that out.</p>

<p>For 6ft tall, and ~181 lbs male it is 1,799 Calories/day. That means that if you don’t do anything, but lie in bed, you need 1,799 Calories/day. You are usually moving, and doing things, thus your calorie intake needs to be higher. The BMR calculator provides estimates of how much more you need based on your activity level:</p>

<table>
  <thead>
    <tr>
      <th>Activity Level</th>
      <th>Calorie</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sedentary: little or no exercise</td>
      <td>2,159</td>
    </tr>
    <tr>
      <td>Exercise 1-3 times/week</td>
      <td>2,474</td>
    </tr>
    <tr>
      <td>Exercise 4-5 times/week</td>
      <td>2,636</td>
    </tr>
    <tr>
      <td>Daily exercise or intense exercise 3-4 times/week</td>
      <td>2,788</td>
    </tr>
    <tr>
      <td>Intense exercise 6-7 times/week</td>
      <td>3,103</td>
    </tr>
    <tr>
      <td>Very intense exercise daily, or physical job</td>
      <td>3,418</td>
    </tr>
  </tbody>
</table>

<p>I recommend using the sedentary value and measuring calories burned during your workouts separately.</p>

<p>You can measure how many calories you burn during the entire day with <a href="https://ouraring.com/discount/d7efcb9add">Oura Ring</a>. I recommend <a href="https://ouraring.com/discount/d7efcb9add">Oura Ring</a> also for <a href="https://ouraring.com/blog/how-accurate-is-oura/">sleep tracking</a>. It’s the best device on the market. Sleep is an important part of keeping your weight, health and fitness intact. If you want to learn why, read <a href="https://amzn.to/3JM6MpH">Why We Sleep</a>.</p>

<p>My approach for calculating my calorie budget was to use the Sedentary value as a base and add the calories I burn during workouts. I use a Garmin watch and Heart-Rate Monitor for that. I also used <a href="https://ouraring.com/discount/d7efcb9add">Oura Ring</a> total calories burn to cross-check the values.</p>

<h3>Counting Calories</h3>

<p>To burn 1 pound, you need to develop a deficit of 3500 Calories. Do not try to lose more than 1 pound per week, as it will cause undesired side effects like exhaustion, tiredness, and the yoyo effect later on. 1 pound per week is still not bad: you can lose 4 pounds over 1 month, and 26 pounds in half a year.</p>

<p>Sorry, losing all 10-20 lbs in 1 month is not sustainable. Many people fall into that trap, and they quickly regain the weight.</p>

<p>To count calories, I recommend <a href="https://www.myfitnesspal.com/">MyFitnessPal</a>. A premium subscription allows you to scan the bar code from foods, which will automatically log calories and all nutrients.</p>

<p>You can connect <a href="https://www.myfitnesspal.com/">MyFitnessPal</a> to your smart scale and track your progress daily. You can also connect with Garmin or Strava to automatically count calories burned during workouts.</p>

<p>I also recommend keeping track of calorie deficit with a <a href="https://docs.google.com/spreadsheets/d/1ho7KNSEitzgiz5PB9VWgWV8A_0ZDG9HzVJyLJXNnGXA/edit?usp=sharing">spreadsheet</a>.</p>

<table>
  <tbody>
    <tr>
      <td><img src="/assets/2023/myfitnesspal-weight.png" alt="MyFitnessPal - weight tracking" title="MyFitnessPal - weight tracking" /></td>
      <td><img src="/assets/2023/myfitnesspal-active-calories.png" alt="MyFitnessPal - active calories" title="MyFitnessPal - active calories" /></td>
      <td><img src="/assets/2023/myfitnesspal-macros.png" alt="MyFitnessPal - macros" title="MyFitnessPal - macros" /></td>
    </tr>
  </tbody>
</table>

<p>Counting calories itself results in eating less. When you don’t know how many calories you ate, you may think that you need more food. Just being aware that you ate enough can trick your brain into craving less.</p>

<h3>Healthy eating with chocolate and ice cream</h3>

<p>When I started caring for my weight for the first time, a few years ago, I decided that I need to figure out a sustainable diet, which I’ll be able to maintain throughout my entire life.</p>

<p>Many people want to go through torture, and just lose all additional weight in 1 month. Unfortunately, after that, they go back to old eating habits, and their weight is back. This results in demotivation and unwillingness to pick up that challenge again.</p>

<p>It’s important to keep a good balance of carbs, protein and fats. Ideally about 50% carbs, 30% protein and 20% fat. If you don’t do that, you will end up hungry, and tired. Not keeping this balance is the main reason for weight gain. Eating pasta, which has a lot of carbs is causing the total calorie count to go up, but macros are out of balance. Your body still needs protein. Usually, people overeat carbs and fats but undereat protein. They reach the needed amount of protein by simply eating too many calories, or they don’t at all.</p>

<p>I often struggle with reaching my protein goals per day. It is helpful to use <a href="https://amzn.to/3pv1gkG">protein powder</a> for that purpose. I add it to yogurt with blueberries or oatmeal if I have it for breakfast.</p>

<p>I love chocolate, and I like some not very-healthy foods like burgers, pizza, ramen, etc. It is fine to eat these foods, but in moderation, or occasionally. The most important thing is to balance macros. If you had a chicken salad (high protein) for lunch, you can have ramen (high carbs) for dinner.</p>

<p>The key is to treat the calories you consume like the money you spend. There are some good and bad investments.</p>

<p>Sample “bad investment foods”:</p>
<ul>
  <li>1 slice of cheese has ~100 kcal and a lot of fat</li>
  <li>1 tbsp of mayo has 90 kcal while ketchup or mustard has only 20-40</li>
  <li><a href="https://www.starbucks.com/menu/product/2122714/single">This small chocolate chip cookie at Starbucks</a> is 360 kcal!</li>
  <li><a href="https://www.starbucks.com/menu/product/1130/single">Starbucks’ double chocolate brownie</a> is 480 kcal!</li>
</ul>

<p>Everyone is a human, and we need some treat from time to time. Even daily! The key is to be aware of how much each treat costs calorie-wise. Eat only delicious sweets. Don’t eat “meh” and “ok” sweets!</p>

<p>I prefer 2 Snickers bars (220 kcal each) over IMO average tasting brownie.</p>

<p>Another strategy is to don’t eat sweets for a few days and have a feast once or twice a week.</p>

<p>When I want to eat something, I always ask myself: “Is it worth that many calories or I would rather eat something better?”.</p>

<p>Watch out for the sugar intake. Any sugar you eat, when you don’t exercise, goes into your sides. The best time to eat sugar is before or during exercise.</p>

<h3>Healthy and quick-to-make foods</h3>

<ul>
  <li>Unsweetened oatmeal with blueberries and <a href="https://amzn.to/3pv1gkG">protein powder</a></li>
  <li>2-3 eggs (soft-boiled or scrambled in olive oil with onion), <a href="https://amzn.to/3NDzHxC">Ezkiel Bread</a> with cream cheese, tomatoes and cucumbers</li>
  <li><a href="https://www.spoonfulofflavor.com/instant-pot-chicken-and-rice/">Instant Pot chicken and rice</a></li>
  <li>chicken salad</li>
  <li>turkey burgers</li>
  <li>Fage 0% fat yogurt with fruits (blueberries/strawberries/raspberries) and protein powder (most yogurts have a lot of sugar - don’t eat that)</li>
</ul>

<h3>What to avoid</h3>

<ul>
  <li>Foods with high saturated fats</li>
  <li>deep fried foods</li>
  <li>sugary drinks like Coke, <a href="https://www.starbucks.com/menu/product/424/iced/nutrition">Frapuccino from Starbucks</a>, pina coladas, etc.</li>
  <li>alcohol - when alcohol enters the body, it is used primarily as a fuel source as the body has no way of storing it. This means that any other calories consumed while drinking are more likely to be stored as fat. It applies especially to <a href="https://www.caloriesecrets.net/does-beer-cause-weight-gain/">beer</a>, but also to <a href="https://www.health.com/weight-loss/does-alcohol-make-you-gain-weight">other alcohols</a>. I noticed that when I drink more than 1-2 drinks per week it’s harder for me to lose or maintain my weight. It usually results in gaining weight.</li>
</ul>

<h3>Exercise</h3>

<p>It is <a href="https://health.gov/sites/default/files/2019-09/Physical_Activity_Guidelines_2nd_edition.pdf">recommended by the surgeon general</a> to exercise 2.5-5h per week. This is about 30 minutes per day.</p>

<p>Exercise has not only health benefits but also helps to increase your calorie budget. That allows you to accommodate things you like that are not necessarily healthy. Depending on your weight, and your exercise intensity you can burn even 500 kcal during 30 minutes workout! This is a nice big scoop of ice cream or 2 Snickers bars!</p>

<p>The higher your heart rate the more calories you burn. The easiest, and fastest way to burn calories is running. Just put your shoes on, go out for 30 minutes and you can burn 200-400 calories. I found biking a little harder to burn calories than running as it’s harder to get to equivalent HR with a similar effort. Swimming is even harder. When it comes to efficiency, it’s best to exercise for up to 1h. Longer runs or rides require extra nutrition before or during the workout. That takes calories from your budget, and you need more time to rest post-exercise.</p>

<p>I usually aim for 2 runs of 30 minutes or so and 1 long bike ride. During the summer I like to swim in the lake for 30-60 minutes. When I’m training for a triathlon, I exercise more. I like to sign up for a race as it motivates me to work out. I usually create an 8-12 weeks training plan before the race. Thanks to that I don’t have to go through a mental exercise of what type of workout should I do every day. I just look at the schedule.</p>

<p>I have observed that workouts are highly effective in curbing cravings. Whenever I find myself idle, my desire for chocolate tends to be stronger compared to after a run or a bike ride. This makes exercising doubly advantageous: not only does it increase my calorie budget, but it also helps reduce my cravings for junk food.</p>

<h3>Summary</h3>

<ol>
  <li>Count calories and macros. Don’t eat more than you burn. 500 kcal deficit per day results in losing 1 pound per week.</li>
  <li>Exercise for health, and fitness, and to increase your daily calorie budget.</li>
  <li>Keep track of progress with <a href="https://www.myfitnesspal.com/">MyFitnessPal</a> and <a href="https://docs.google.com/spreadsheets/d/1ho7KNSEitzgiz5PB9VWgWV8A_0ZDG9HzVJyLJXNnGXA/edit?usp=sharing">spreadsheet</a>.</li>
</ol>

<p>Losing weight is not about going through a process and then going back to the previous life. It’s about becoming a different person. A person that doesn’t overeat, and chooses their meals consciously.</p>

<p>I recommend reading <a href="https://amzn.to/3NJPtXA">Atomic Habits</a>. A great book about achieving goals through micro changes, and consistency. In the same way how 1% improvement per day results in a 37x improvement in 1 year. Losing 0.1 pounds per day results in 36.5 pounds in a year! Smaller changes are easier to adapt and maintain. If you keep repeating them for 20 days it’s more likely you will stick with them for longer. After 66 days new habits are becoming a normal part of your life.</p>

<p>Don’t get discouraged by days when you exceed your calories budgets, or small unexpected weight gains during the journey. Look at my weight chart at the beginning. There are a lot of ups and downs. There were weeks in which I gained weight, by sticking to the process. There were also weeks when I didn’t and I lost weight anyway. Our body is not a perfect machine, but being consistent eventually will pay off.</p>

<p>To learn more about nutrition for workouts I recommend <a href="https://fellrnr.com/wiki/Nutrition">the section about nutrition</a> from an awesome website <a href="https://fellrnr.com/">fellnr.com</a>.</p>

<p>If you want to develop a sustainable diet and get back to your desired weight, start tracking your weight and calories in <a href="https://docs.google.com/spreadsheets/d/1ho7KNSEitzgiz5PB9VWgWV8A_0ZDG9HzVJyLJXNnGXA/edit?usp=sharing">the Weight Loss Tracker spreadsheet</a>. It’s a great feeling when you see the weight chart going down.</p>

<p>Remember the <a href="https://amzn.to/2JUkNCr">importance of sleep</a>. Lack of sleep results in an energy deficit that is often compensated by eating more.</p>

<p>Update: in 2025 I found an awesome blog by <a href="https://karpathy.ai/">Andrej Karpathy</a>: <a href="https://karpathy.github.io/2020/06/11/biohacking-lite/">Biohacking Lite</a>.</p>]]></content><author><name></name></author><category term="lifestyle" /><category term="weight" /><category term="health" /><category term="fitness" /><summary type="html"><![CDATA[Are you tired of endless diets, complex workout regimens, and conflicting advice on your quest to shed those extra pounds? Weight loss doesn’t have to be overwhelming or complicated. It’s all about calories in and calories out.]]></summary></entry><entry><title type="html">The Complete Guide to Full Stack MARN Web Apps Development: MongoDB, Apollo (GraphQL), React, and Node.js</title><link href="https://jj09.net/the-complete-guide-to-full-stack-marn-web-apps-development-mongodb-apollo-graphql-react-nodejs/" rel="alternate" type="text/html" title="The Complete Guide to Full Stack MARN Web Apps Development: MongoDB, Apollo (GraphQL), React, and Node.js" /><published>2022-12-15T00:00:00+00:00</published><updated>2022-12-15T00:00:00+00:00</updated><id>https://jj09.net/the-complete-guide-to-full-stack-marn-web-apps-development-mongodb-apollo-graphql-react-nodejs</id><content type="html" xml:base="https://jj09.net/the-complete-guide-to-full-stack-marn-web-apps-development-mongodb-apollo-graphql-react-nodejs/"><![CDATA[<p>This is a step-by-step overview for creating the front-end, backend, and persistence layer with MARN Stack: MongoDB, Apollo Server, React and Node.js. I also created a video version of this tutorial, which you can find on <a href="https://www.youtube.com/embed/6YAKvGlFcwo">YouTube</a>.</p>

<h2>What is the MARN stack?</h2>

<p><strong>MARN</strong> stands for <strong>M</strong>ongoDB, <strong>A</strong>pollo Server, <strong>R</strong>eact and <strong>N</strong>ode.js.</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/MongoDB">MongoDB</a> — document database</li>
  <li><a href="https://www.apollographql.com/docs/apollo-server/">Apollo Server</a> — <a href="https://en.wikipedia.org/wiki/GraphQL">GraphQL</a> server</li>
  <li><a href="https://en.wikipedia.org/wiki/React_(JavaScript_library)">React</a> — a client-side JavaScript framework</li>
  <li><a href="https://en.wikipedia.org/wiki/Node.js">Node.js</a> — back-end JavaScript runtime environment</li>
</ul>

<p><strong>MARN Stack</strong> is the next generation of popular <a href="https://www.geeksforgeeks.org/mern-stack/">MERN Stack</a> (<a href="https://en.wikipedia.org/wiki/MongoDB">MongoDB</a>, <a href="https://en.wikipedia.org/wiki/Express.js">Express.js</a>, <a href="https://en.wikipedia.org/wiki/React_(JavaScript_library)">React</a>, <a href="https://en.wikipedia.org/wiki/Node.js">Node.js</a>). Using <a href="https://www.apollographql.com/docs/apollo-server/">Apollo Server</a> instead of <a href="https://en.wikipedia.org/wiki/Express.js">Express.js</a> makes it very easy to create <a href="https://en.wikipedia.org/wiki/GraphQL">GraphQL</a> APIs.</p>

<p>Why Apollo Server instead of Express? Everything has its pros and cons. I like Apollo Server for its ease of setup, GraphiQL console (very useful during development), and support for many front-end frameworks. For more check out <a href="https://github.com/apollographql/apollo-server/tree/cfb086227e623ba1531bb887c3919e224682ccbc#comparison-with-express-graphql">Comparison of Apollo Server with <code class="language-plaintext highlighter-rouge">express-graphql</code></a>.</p>

<p>There is a lot of documentation and tutorials for building GraphQL backend with Apollo Server. There is also a lot of documentation for consuming GraphQL API from React. Yet, there is nothing about building end-to-end web apps with React, Apollo Server backend, and MongoDB persistence layer.</p>

<p>In this article, I will show you how to build end to end web app with <a href="https://en.wikipedia.org/wiki/React_(JavaScript_library)">React</a> front-end, and <a href="https://en.wikipedia.org/wiki/Node.js">Node.js</a> backend with <a href="https://en.wikipedia.org/wiki/GraphQL">GraphQL</a> API (powered by <a href="https://www.apollographql.com/docs/apollo-server/">Apollo Server</a>) and <a href="https://en.wikipedia.org/wiki/MongoDB">MongoDB</a> persistence layer.</p>

<p><img src="/assets/2022/marn-architecture.png" alt="MARN Stack Architecture" title="MARN Stack Architecture" /></p>

<p>I created a video version of this guide:</p>

<iframe width="640" height="360" src="https://www.youtube.com/embed/6YAKvGlFcwo" title="Complete Guide to Web Apps Development with MARN Stack: MongoDB, Apollo Server, React, and Node.js" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<h2>Getting Started</h2>

<h3>Installing dependencies</h3>

<ul>
  <li>Install node.js: I recommend installing node with <a href="https://brew.sh/">brew</a> (I’m using node 16.14.2 and npm 8.5.0):
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install node
</code></pre></div>    </div>
  </li>
  <li>Install nodemon (I am using version 2.0.20):
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install nodemon
</code></pre></div>    </div>
  </li>
  <li>Install Mongo (<a href="https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-os-x/">instructions for Mac</a>, <a href="https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-windows/">instructions for Windows</a>). I’m using MongoDB Community Edition 6.0. I recommend installing with brew (on Mac):
    <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xcode-select --install  # installing XCode tools
brew tap mongodb/brew`
brew update`
brew install mongodb-community@6.0
</code></pre></div>    </div>
  </li>
</ul>

<h3>Setup Apollo Server (GraphQL API) with Node.js</h3>

<p>Create a new directory for your app:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir marn-app
</code></pre></div></div>

<p>Create a new directory for Apollo Server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir apollo-server
cd apollo-server
</code></pre></div></div>

<p>Install dependencies:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install @apollo/server graphql-tag
npm install @babel/core @babel/node --save-dev
</code></pre></div></div>

<p>Add script to run Apollo Server with nodemon to <code class="language-plaintext highlighter-rouge">package.json</code>:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nodemon --exec babel-node --presets='@babel/preset-env' -- src/index.js"</span><span class="w">
</span><span class="p">}</span><span class="err">,</span></code></pre></figure>

<p>Add <code class="language-plaintext highlighter-rouge">type=module</code> property to <code class="language-plaintext highlighter-rouge">package.json</code> to enable ES 6 modules:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module"</span></code></pre></figure>

<p>Create <code class="language-plaintext highlighter-rouge">src/index.js</code> file with <a href="https://www.apollographql.com/docs/apollo-server/schema/schema">GraphQL schema</a> and <a href="https://www.apollographql.com/docs/apollo-server/data/resolvers">resolvers</a>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloServer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/server</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">startStandaloneServer</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/server/standalone</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">gql</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">graphql-tag</span><span class="dl">'</span><span class="p">;</span>

<span class="c1">// GraphQL Schema</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
  type Query {
    hello: String
  }
`</span><span class="p">;</span>

<span class="c1">// GraphQL Resolvers</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">Query</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="dl">"</span><span class="s2">Hello from Apollo Server</span><span class="dl">"</span>
    <span class="p">}</span>
<span class="p">};</span>

<span class="kd">const</span> <span class="nx">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloServer</span><span class="p">({</span><span class="nx">typeDefs</span><span class="p">,</span> <span class="nx">resolvers</span><span class="p">});</span>

<span class="kd">const</span> <span class="p">{</span> <span class="nx">url</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">startStandaloneServer</span><span class="p">(</span><span class="nx">server</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">listen</span><span class="p">:</span> <span class="p">{</span> <span class="na">port</span><span class="p">:</span> <span class="mi">4000</span> <span class="p">},</span>
<span class="p">});</span>

<span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="s2">`🚀 Server ready at </span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2">`</span><span class="p">);</span></code></pre></figure>

<p><a href="https://www.apollographql.com/docs/apollo-server/schema/schema">GraphQL schema</a> describes the shape of your available data.</p>

<p><a href="https://www.apollographql.com/docs/apollo-server/data/resolvers">GraphQL resolvers</a> are responsible for populating data into fields in Schema.</p>

<p>The above code defines 1 field (<code class="language-plaintext highlighter-rouge">hello</code>) in GraphQL Schema, and the resolver function returns <code class="language-plaintext highlighter-rouge">"Hello from Apollo Server"</code> when querying that field.</p>

<p>Start Apollo Server with:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm start
</code></pre></div></div>

<p>If you go to http://localhost:4000 you should see GraphQL Playground where you can execute queries:</p>

<p><img src="/assets/2022/apollo-graphql-playground.png" alt="Apollo GraphQL Playground" title="Apollo GraphQL Playground" /></p>

<!-- References:
https://babeljs.io/setup#installation
https://www.apollographql.com/docs/apollo-server/migration -->

<h3>Create Web UI with React</h3>

<p>Create react app from your project root directory:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npx create-react-app web-ui
cd web-ui
npm install @apollo/client graphql
npm start
</code></pre></div></div>

<h2>Querying Apollo GraphQL API from React</h2>

<p>Install <code class="language-plaintext highlighter-rouge">@apollo/client</code> module:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd web-ui
npm install @apollo/client
</code></pre></div></div>

<p>To call Apollo Server with Apollo Client module, we need to do two things:</p>

<p>Initialize client:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span>
  <span class="na">uri</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:4000</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">(),</span>
<span class="p">});</span></code></pre></figure>

<p>Wrap React components with <code class="language-plaintext highlighter-rouge">ApolloProvider</code> component and pass <code class="language-plaintext highlighter-rouge">ApolloClient</code> instance to it:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">ApolloProvider</span> <span class="na">client</span><span class="p">=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">&gt;</span>
      ...
    <span class="p">&lt;/</span><span class="nc">ApolloProvider</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>To query the Apollo GraphQL endpoint we can use <code class="language-plaintext highlighter-rouge">useQuery</code> React hook provided by <code class="language-plaintext highlighter-rouge">@apollo/client</code> module.</p>

<h3>Call 'hello' query from React</h3>

<p>Let’s create <code class="language-plaintext highlighter-rouge">Hello</code> React component that will perform a call to GraphQL API and display the returned result. We need to define a GraphQL query and pass it to <code class="language-plaintext highlighter-rouge">useQuery</code>. The neat thing about the Apollo server is the ability to directly copy/paste queries from the playground.</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">gql</span><span class="p">,</span> <span class="nx">useQuery</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/client</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">HELLO_QUERY</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
query Query {
  hello
}
`</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Hello</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="p">{</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">loading</span><span class="p">,</span> <span class="nx">error</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useQuery</span><span class="p">(</span><span class="nx">HELLO_QUERY</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">loading</span><span class="p">)</span> <span class="k">return</span> <span class="p">&lt;</span><span class="nt">p</span><span class="p">&gt;</span>Loading...<span class="p">&lt;/</span><span class="nt">p</span><span class="p">&gt;;</span>
    <span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">HELLO_QUERY error</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
        <span class="si">{</span><span class="nx">loading</span> <span class="o">&amp;&amp;</span> <span class="dl">'</span><span class="s1">Loading...</span><span class="dl">'</span><span class="si">}</span>
        <span class="si">{</span><span class="nx">error</span> <span class="o">&amp;&amp;</span> <span class="dl">'</span><span class="s1">Error (check console logs)</span><span class="dl">'</span><span class="si">}</span>
        <span class="si">{</span><span class="o">!</span><span class="nx">loading</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">error</span> <span class="o">&amp;&amp;</span> <span class="nx">data</span><span class="p">?.</span><span class="nx">hello</span><span class="si">}</span>
    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;;</span>
<span class="p">}</span></code></pre></figure>

<p>To make it work we need to update <code class="language-plaintext highlighter-rouge">App.js</code> with changes mentioned in the previous section: initializing <code class="language-plaintext highlighter-rouge">ApolloCLient</code> and wrapping components with <code class="language-plaintext highlighter-rouge">ApolloProvider</code>:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloClient</span><span class="p">,</span> <span class="nx">InMemoryCache</span><span class="p">,</span> <span class="nx">ApolloProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/client</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Hello</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/Hello</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span>
  <span class="na">uri</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:4000</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">(),</span>
<span class="p">});</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">ApolloProvider</span> <span class="na">client</span><span class="p">=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Hello</span> <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nc">ApolloProvider</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>This should display <code class="language-plaintext highlighter-rouge">Hello from Apollo Server</code> in the browser coming from GraphQL API!</p>

<p><img src="/assets/2022/hello-from-apollo-server.png" alt="Hello from Apollo Server" title="Hello from Apollo Server" /></p>

<h3>Add parameter to GraphQL query</h3>

<p>So far, the query is pretty simple. Let’s make it more sophisticated by adding a parameter <code class="language-plaintext highlighter-rouge">name</code>, and changing the response to <code class="language-plaintext highlighter-rouge">Hello ${name}</code>.</p>

<p>To do that we need to modify GraphQL schema and resolvers in the backend:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    type Query {
        hello(name: String): String
    }
`</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">Query</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">hello</span><span class="p">:</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">name</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="s2">`Hello </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">};</span></code></pre></figure>

<p>Notice that we extract <code class="language-plaintext highlighter-rouge">name</code> param from the second argument of the resolver function.</p>

<p>We can use GraphQL Playground to test it, and help us to generate the query with a parameter:</p>

<p><img src="/assets/2022/apollo-graphql-playground-query-with-param.png" alt="Apollo GraphQL Playground: query with param" title="Apollo GraphQL Playground: query with param" /></p>

<h3>Call GraphQL query with parameter from React</h3>

<p>We need to update our <code class="language-plaintext highlighter-rouge">HELLO_QUERY</code>. You can copy/paste the query from GraphQL Playground:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="nx">HELLO_QUERY</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    query Query($name: String) {
        hello(name: $name)
    }
`</span><span class="p">;</span></code></pre></figure>

<p>We also need to pass <code class="language-plaintext highlighter-rouge">name</code> variable to <code class="language-plaintext highlighter-rouge">useQuery</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="kd">const</span> <span class="p">{</span> <span class="nx">data</span><span class="p">,</span> <span class="nx">loading</span><span class="p">,</span> <span class="nx">error</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">useQuery</span><span class="p">(</span><span class="nx">HELLO_QUERY</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">variables</span><span class="p">:</span> <span class="p">{</span><span class="na">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Jacob</span><span class="dl">"</span><span class="p">},</span>
<span class="p">});</span></code></pre></figure>

<p>This should result in displaying <code class="language-plaintext highlighter-rouge">Hello Jacob</code> in the browser:</p>

<p><img src="/assets/2022/hello-jacob.png" alt="Hello Jacob" title="Hello Jacob" /></p>

<!-- https://www.apollographql.com/docs/react/get-started -->

<h3>Refactoring resolvers and schema to separate components</h3>

<p>Before we move to the next section, let’s clean up our backend code by extracting schema and resolvers to separate modules.</p>

<p>Create new file <code class="language-plaintext highlighter-rouge">src/models/typeDefs.js</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="nx">gql</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">graphql-tag</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    type Query {
        hello(name: String): String
    }
`</span><span class="p">;</span></code></pre></figure>

<p>Create new file <code class="language-plaintext highlighter-rouge">src/resolvers.js</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">export</span> <span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">Query</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">hello</span><span class="p">:</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">name</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="s2">`Hello </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
    <span class="p">}</span>
<span class="p">};</span></code></pre></figure>

<p>Remove equivalent pieces of code from <code class="language-plaintext highlighter-rouge">src/index.js</code> and import modules instead:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span><span class="nx">resolvers</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./resolvers.js</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span><span class="nx">typeDefs</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./models/typeDefs.js</span><span class="dl">'</span><span class="p">;</span></code></pre></figure>

<p>This will set us up for the next section. You should always extract independent modules as much as possible to keep your code clean.</p>

<h2>CRUD with MongoDB</h2>

<p>CRUD stands for Create, Read, Update, Delete. It’s the backbone of every web app. Except Twitter. They didn’t support Update for a while xD</p>

<p>In this section, I’ll describe how to create simple CRUD for books. Every book will have a title and year when it was published. Data will be stored in MongoDB. Web UI will have an interface to display (Read), add (Create), edit (Update) and delete books through GraphQL API.</p>

<h3>Working with MongoDB</h3>

<p>To start mongo on Mac (<a href="https://www.mongodb.com/docs/manual/tutorial/install-mongodb-on-os-x/">docs</a>):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew services start mongodb-community@6.0
</code></pre></div></div>

<p>I recommend using <a href="https://www.mongodb.com/try/download/compass">MongoDB Compass</a> for working with data in MongoDB. It’s much easier than using <a href="https://www.mongodb.com/docs/mongodb-shell/">MongoDB Shell (mongosh)</a>. Of course, if you prefer using <code class="language-plaintext highlighter-rouge">mongosh</code>, go for it.</p>

<p>To get started with Books Library, let’s create new database called <code class="language-plaintext highlighter-rouge">marn</code> and new collection called <code class="language-plaintext highlighter-rouge">books</code>. It can be done easily with Compass:</p>

<p><img src="/assets/2022/compass-createdb.png" alt="Create MongoDB with Compass" title="Create MongoDB with Compass" /></p>

<h3>Connect to MongoDB from node.js</h3>

<p>To connect to MongoDB from node.js we will use <code class="language-plaintext highlighter-rouge">mongoose</code>.</p>

<p>It has to be installed from Apollo Server directory:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd apollo-server
npm install mongoose
</code></pre></div></div>

<p>To connect to MongoDB we can establish a connection in <code class="language-plaintext highlighter-rouge">src/index.js</code> by adding this code:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="nx">mongoose</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">mongoose</span><span class="dl">'</span><span class="p">;</span>

<span class="nx">mongoose</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">strictQuery</span><span class="dl">'</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">db</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">mongoose</span><span class="p">.</span><span class="nx">connect</span><span class="p">(</span><span class="dl">"</span><span class="s2">mongodb://localhost:27017/marn</span><span class="dl">"</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">useNewUrlParser</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">});</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">'</span><span class="s1">📚 Connected to db</span><span class="dl">'</span><span class="p">,</span> <span class="nx">db</span><span class="p">?.</span><span class="nx">connections</span><span class="p">[</span><span class="mi">0</span><span class="p">]?.</span><span class="nx">_connectionString</span><span class="p">);</span></code></pre></figure>

<p>Make sure your MongoDB uses port 27017. If not, update the URI. You can find the URI in Compass UI:</p>

<p><img src="/assets/2022/compass-localhost.png" alt="MongoDB uri in Compass" title="MongoDB uri in Compass" /></p>

<h3>Create Book model and query data from MongoDB</h3>

<p>We can map MongoDB collections to JavaScript objects by creating models with <code class="language-plaintext highlighter-rouge">mongoose</code>. Create Book model in <code class="language-plaintext highlighter-rouge">src/models/Book.js</code> file:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="nx">mongoose</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">mongoose</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">Book</span> <span class="o">=</span> <span class="nx">mongoose</span><span class="p">.</span><span class="nx">model</span><span class="p">(</span><span class="dl">'</span><span class="s1">Book</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">title</span><span class="p">:</span> <span class="nb">String</span><span class="p">,</span> <span class="na">year</span><span class="p">:</span> <span class="nb">Number</span> <span class="p">});</span></code></pre></figure>

<p>We can query all books by simply calling the <code class="language-plaintext highlighter-rouge">Book</code> model:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="nx">Book</span><span class="p">.</span><span class="nx">find</span><span class="p">({})</span></code></pre></figure>

<p>To make data accessible through GraphQL, we need to add <code class="language-plaintext highlighter-rouge">Book</code> type to GraphQL schema and <code class="language-plaintext highlighter-rouge">books</code> query that returns an array of <code class="language-plaintext highlighter-rouge">Book</code> elements in <code class="language-plaintext highlighter-rouge">src/models/typeDefs.js</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="nx">gql</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">graphql-tag</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    type Query {
        hello(name: String): String
        books: [Book]
    }
    type Book {
        id: ID,
        title: String,
        year: Int,
    }
`</span><span class="p">;</span></code></pre></figure>

<p>We also need to add the resolver that is querying MongoDB with <code class="language-plaintext highlighter-rouge">mongoose</code> in <code class="language-plaintext highlighter-rouge">src/resolvers.js</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span><span class="nx">Book</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./models/Book.js</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">Query</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">hello</span><span class="p">:</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">name</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="s2">`Hello </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
        <span class="na">books</span><span class="p">:</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">await</span> <span class="nx">Book</span><span class="p">.</span><span class="nx">find</span><span class="p">({}),</span>
    <span class="p">}</span>
<span class="p">};</span></code></pre></figure>

<p>Querying with Playground should return an empty array:</p>

<p><img src="/assets/2022/apollo-query-books-empty.png" alt="Apollo books query (empty)" title="Apollo books query (empty)" /></p>

<p>Let’s add a book to MongoDB with Compass by clicking <code class="language-plaintext highlighter-rouge">ADD DATA -&gt; Insert document</code>:</p>

<p><img src="/assets/2022/insert-mongodb-document.png" alt="Insert document with Compass" title="Insert document with Compass" /></p>

<p>Once the book is added it should be returned in <code class="language-plaintext highlighter-rouge">books</code> query:
<img src="/assets/2022/apollo-query-books-one-book.png" alt="Apollo books query (one book)" title="Apollo books query (one book)" /></p>

<h3>Display books in React UI</h3>

<p>To make styling easier, let’s use <a href="https://getbootstrap.com">Twitter Bootstrap</a>.</p>

<p>Add bootstrap CSS to <code class="language-plaintext highlighter-rouge">web-ui/public/index.html</code> in <code class="language-plaintext highlighter-rouge">&lt;head&gt;</code> section:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css"</span> <span class="na">integrity=</span><span class="s">"sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"</span> <span class="na">crossorigin=</span><span class="s">"anonymous"</span><span class="nt">&gt;</span></code></pre></figure>

<p>To display books, we will create two components:</p>
<ol>
  <li><code class="language-plaintext highlighter-rouge">Books</code> - for querying GraphQL API and displaying all books</li>
  <li><code class="language-plaintext highlighter-rouge">Book</code> - for displaying a single book (data will be passed from <code class="language-plaintext highlighter-rouge">Books</code> component)</li>
</ol>

<p>Let’s start with <code class="language-plaintext highlighter-rouge">Book</code> component. Create new file <code class="language-plaintext highlighter-rouge">src/components/Book.js</code>:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Book</span><span class="p">({</span><span class="nx">book</span><span class="p">})</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">book</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">book</span><span class="p">.</span><span class="nx">year</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
    <span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">Books</code> component will use the <code class="language-plaintext highlighter-rouge">Book</code> component to display all books from MongoDB. It will also fetch data from GraphQL API. We need to define GraphQL query (which we can copy from Playground), and query Apollo Server with <code class="language-plaintext highlighter-rouge">useQuery</code>. Create new file <code class="language-plaintext highlighter-rouge">src\components\Books.js</code>:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">gql</span><span class="p">,</span> <span class="nx">useQuery</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/client</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Book</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./Book</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">BOOKS_QUERY</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    query Books {
        books {
            title
            year
            id
        }
    }
`</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Books</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="p">{</span><span class="nx">data</span><span class="p">,</span> <span class="nx">error</span><span class="p">,</span> <span class="nx">loading</span><span class="p">}</span> <span class="o">=</span> <span class="nx">useQuery</span><span class="p">(</span><span class="nx">BOOKS_QUERY</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">BOOKS_QUERY error</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">div</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">table</span> <span class="na">className</span><span class="p">=</span><span class="s">'table'</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">thead</span> <span class="na">className</span><span class="p">=</span><span class="s">'thead-dark'</span><span class="p">&gt;</span>
                <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
                    <span class="p">&lt;</span><span class="nt">th</span><span class="p">&gt;</span>Title<span class="p">&lt;/</span><span class="nt">th</span><span class="p">&gt;</span>
                    <span class="p">&lt;</span><span class="nt">th</span><span class="p">&gt;</span>Year<span class="p">&lt;/</span><span class="nt">th</span><span class="p">&gt;</span>
                <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
            <span class="p">&lt;/</span><span class="nt">thead</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">tbody</span><span class="p">&gt;</span>
                <span class="si">{</span><span class="nx">loading</span> <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;&lt;</span><span class="nt">td</span><span class="p">&gt;</span>Loading...<span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span><span class="si">}</span>
                <span class="si">{</span><span class="nx">error</span> <span class="o">&amp;&amp;</span> <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;&lt;</span><span class="nt">td</span><span class="p">&gt;</span>Check console for error logs<span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span><span class="si">}</span>
                <span class="si">{</span><span class="o">!</span><span class="nx">loading</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="nx">error</span> <span class="o">&amp;&amp;</span> <span class="nx">data</span><span class="p">?.</span><span class="nx">books</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">book</span> <span class="o">=&gt;</span> <span class="p">&lt;</span><span class="nc">Book</span> <span class="na">book</span><span class="p">=</span><span class="si">{</span><span class="nx">book</span><span class="si">}</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">book</span><span class="p">.</span><span class="nx">id</span><span class="si">}</span> <span class="p">/&gt;)</span><span class="si">}</span>
            <span class="p">&lt;/</span><span class="nt">tbody</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">table</span><span class="p">&gt;</span>
    <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;;</span>
<span class="p">}</span></code></pre></figure>

<p>Import <code class="language-plaintext highlighter-rouge">Books</code> component in <code class="language-plaintext highlighter-rouge">App</code> component and display it on the main page:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloClient</span><span class="p">,</span> <span class="nx">InMemoryCache</span><span class="p">,</span> <span class="nx">ApolloProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/client</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Hello</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/Hello</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Books</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/Books</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span>
  <span class="na">uri</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:4000</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">(),</span>
<span class="p">});</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">ApolloProvider</span> <span class="na">client</span><span class="p">=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">Hello</span> <span class="p">/&gt;</span>
      <span class="p">&lt;</span><span class="nc">Books</span> <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nc">ApolloProvider</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>When you go back to the website you should see the book, which we added to the database displayed in a table:</p>

<p><img src="/assets/2022/books-table-one-book.png" alt="Books table displayed with React" title="Books table displayed with React" /></p>

<!-- https://getbootstrap.com/docs/4.0/getting-started/introduction/ 
https://getbootstrap.com/docs/4.0/content/tables/  -->

<h3>React Router</h3>

<p>Before we start working on creating, deleting and updating books, let’s add navigation to our UI with <a href="https://reactrouter.com">React Router</a>. This will allow us to separate different views instead of cluttering them on the same page.</p>

<p>First, we need to install <code class="language-plaintext highlighter-rouge">react-router-dom</code> in our React app:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd web-ui
npm install react-router-dom
</code></pre></div></div>

<p>We need to wrap all components in <code class="language-plaintext highlighter-rouge">BrowserRouter</code>, map routes to components, and add links allowing us to navigate between routes.</p>

<p>All changes that need to be done take place in <code class="language-plaintext highlighter-rouge">src/App.js</code>:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloClient</span><span class="p">,</span> <span class="nx">InMemoryCache</span><span class="p">,</span> <span class="nx">ApolloProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/client</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Hello</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/Hello</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Books</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/Books</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Link</span><span class="p">,</span> <span class="nx">BrowserRouter</span><span class="p">,</span> <span class="nx">Route</span><span class="p">,</span> <span class="nx">Routes</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-router-dom</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span>
  <span class="na">uri</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:4000</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">(),</span>
<span class="p">});</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">ApolloProvider</span> <span class="na">client</span><span class="p">=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">BrowserRouter</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">nav</span> <span class="na">className</span><span class="p">=</span><span class="s">"navbar navbar-dark bg-dark"</span><span class="p">&gt;</span>
          <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"navbar-nav mr-auto flex-row"</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="s">"/"</span> <span class="na">className</span><span class="p">=</span><span class="s">"nav-link mr-2"</span><span class="p">&gt;</span>Home<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="s">"/books"</span> <span class="na">className</span><span class="p">=</span><span class="s">"nav-link mr-2"</span><span class="p">&gt;</span>Books<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span>
          <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">nav</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"container mt-5"</span><span class="p">&gt;</span>
          <span class="p">&lt;</span><span class="nc">Routes</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">path</span><span class="p">=</span><span class="s">"/"</span> <span class="na">element</span><span class="p">=</span><span class="si">{</span><span class="p">&lt;</span><span class="nc">Hello</span> <span class="p">/&gt;</span><span class="si">}</span> <span class="p">/&gt;</span>
            <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">path</span><span class="p">=</span><span class="s">"/books"</span> <span class="na">element</span><span class="p">=</span><span class="si">{</span><span class="p">&lt;</span><span class="nc">Books</span> <span class="p">/&gt;</span><span class="si">}</span> <span class="p">/&gt;</span>
          <span class="p">&lt;/</span><span class="nc">Routes</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
      <span class="p">&lt;/</span><span class="nc">BrowserRouter</span><span class="p">&gt;</span>      
    <span class="p">&lt;/</span><span class="nc">ApolloProvider</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>After that, you should see the main page with navigation and links to home that displays <code class="language-plaintext highlighter-rouge">Hello</code> component, and a link to books that display the list of books from MongoDB in a table:</p>

<p><img src="/assets/2022/react-router.gif" alt="React Router navigation" title="React Router navigation" /></p>

<h3>Create book mutation</h3>

<p>To insert a new book into MongoDB, we need to create a <code class="language-plaintext highlighter-rouge">Mutation</code>.</p>

<p>In GraphQL we can fetch data with <code class="language-plaintext highlighter-rouge">Query</code>, but to create or modify data, we need to create a <code class="language-plaintext highlighter-rouge">Mutation</code>.</p>

<p>Let’s start with updating the schema. Mutations are in a separate block in the schema. We will add <code class="language-plaintext highlighter-rouge">create</code> a mutation that takes <code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">year</code> parameters, and returns <code class="language-plaintext highlighter-rouge">Book</code> object to <code class="language-plaintext highlighter-rouge">src/models/typeDefs.js</code> file:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="nx">gql</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">graphql-tag</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    type Query {
        hello(name: String): String
        books: [Book]
    }
    type Book {
        id: ID,
        title: String,
        year: Int,
    }
    type Mutation {
        create(title: String, year: Int): Book
    }
`</span><span class="p">;</span></code></pre></figure>

<p>Now, we need to implement a resolver for the <code class="language-plaintext highlighter-rouge">create</code> mutation. Similarly to the schema, we need to add a new block <code class="language-plaintext highlighter-rouge">Mutation</code> to our resolvers and <code class="language-plaintext highlighter-rouge">create</code> a function. In the <code class="language-plaintext highlighter-rouge">create</code> function we need to add logic to create a new book and save it to MongoDB. This is <code class="language-plaintext highlighter-rouge">src/resolvers.js</code> after updates:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span><span class="nx">Book</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./models/Book.js</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">Query</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">hello</span><span class="p">:</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">name</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="s2">`Hello </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
        <span class="na">books</span><span class="p">:</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">await</span> <span class="nx">Book</span><span class="p">.</span><span class="nx">find</span><span class="p">({}),</span>
    <span class="p">},</span>
    <span class="na">Mutation</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">create</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">title</span><span class="p">,</span> <span class="nx">year</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">newBook</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Book</span><span class="p">({</span>
                <span class="nx">title</span><span class="p">,</span> <span class="nx">year</span>
            <span class="p">});</span>
            <span class="k">await</span> <span class="nx">newBook</span><span class="p">.</span><span class="nx">save</span><span class="p">();</span>
            <span class="k">return</span> <span class="nx">newBook</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">};</span></code></pre></figure>

<p>We can test creating books with GraphQL Playground:</p>

<p><img src="/assets/2022/apollo-graphql-playground-create-mutation.png" alt="Apollo GraphQL Playground - create mutation" title="Apollo GraphQL Playground - create mutation" /></p>

<p>If everything went well you should see a new book in MongoDB. Notice <code class="language-plaintext highlighter-rouge">__v0</code> field in the book inserted with Mongoose. This is <code class="language-plaintext highlighter-rouge">versionKey</code> property, which is set on each document when first created by Mongoose.</p>

<p><img src="/assets/2022/mongodb-insert-mongoose.png" alt="MongoDB - document created with mongoose" title="MongoDB - document created with mongoose" /></p>

<h3>UI for creating books</h3>

<p>Let’s start with a new <code class="language-plaintext highlighter-rouge">CreateBook</code> component. We need to create a new form to collect <code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">year</code>, and call the <code class="language-plaintext highlighter-rouge">create</code> mutation we created in the previous section.</p>

<p>We will utilize <code class="language-plaintext highlighter-rouge">useState</code> hook to access <code class="language-plaintext highlighter-rouge">input</code> field data (<code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">year</code>).</p>

<p>To call mutation, we will use <code class="language-plaintext highlighter-rouge">useMutation</code> hook from <code class="language-plaintext highlighter-rouge">@apollo/client</code>.</p>

<p>We can get <code class="language-plaintext highlighter-rouge">CREATE_BOOK_MUTATION</code> from GraphQL playground.</p>

<p>Make sure to convert year from string to integer when passing it as a variable to mutation. Otherwise, it will return an error. You can simply add <code class="language-plaintext highlighter-rouge">+</code> before the variable to convert a string to an integer.</p>

<p>Add <code class="language-plaintext highlighter-rouge">src/components/CreateBook.js</code> file to <code class="language-plaintext highlighter-rouge">web-ui</code> folder:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">gql</span><span class="p">,</span> <span class="nx">useMutation</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/client</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">CREATE_BOOK_MUTATION</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    mutation Mutation($title: String, $year: Int) {
        create(title: $title, year: $year) {
            id
            title
            year
        }
    }
`</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">CreateBook</span><span class="p">()</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">title</span><span class="p">,</span> <span class="nx">setTitle</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">year</span><span class="p">,</span> <span class="nx">setYear</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">createMutation</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useMutation</span><span class="p">(</span><span class="nx">CREATE_BOOK_MUTATION</span><span class="p">);</span>

    <span class="kd">const</span> <span class="nx">handleSubmit</span> <span class="o">=</span> <span class="nx">evt</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">evt</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
        <span class="nx">console</span><span class="p">.</span><span class="nx">info</span><span class="p">(</span><span class="dl">'</span><span class="s1">Creating Book...</span><span class="dl">'</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">year</span><span class="p">);</span>
        <span class="nx">createMutation</span><span class="p">({</span>
            <span class="na">variables</span><span class="p">:</span> <span class="p">{</span>
                <span class="nx">title</span><span class="p">,</span>
                <span class="na">year</span><span class="p">:</span> <span class="o">+</span><span class="nx">year</span><span class="p">,</span>
            <span class="p">}</span>
        <span class="p">});</span>
        <span class="nx">alert</span><span class="p">(</span><span class="s2">`Book </span><span class="p">${</span><span class="nx">title</span><span class="p">}</span><span class="s2"> (</span><span class="p">${</span><span class="nx">year</span><span class="p">}</span><span class="s2">) created!`</span><span class="p">);</span>
        <span class="nx">setTitle</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
        <span class="nx">setYear</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
    <span class="p">};</span>

    <span class="k">return</span> <span class="p">&lt;</span><span class="nt">form</span> <span class="na">onSubmit</span><span class="p">=</span><span class="si">{</span><span class="nx">evt</span> <span class="o">=&gt;</span> <span class="nx">handleSubmit</span><span class="p">(</span><span class="nx">evt</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"form-group"</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"title"</span><span class="p">&gt;</span>Title:<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">input</span> 
                <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> 
                <span class="na">name</span><span class="p">=</span><span class="s">"title"</span> 
                <span class="na">className</span><span class="p">=</span><span class="s">"form-control"</span>
                <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">title</span><span class="si">}</span>
                <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">setTitle</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span>
                <span class="p">/&gt;</span>
        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"form-group"</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">label</span> <span class="na">htmlFor</span><span class="p">=</span><span class="s">"year"</span><span class="p">&gt;</span>Year:<span class="p">&lt;/</span><span class="nt">label</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">input</span> 
                <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> 
                <span class="na">name</span><span class="p">=</span><span class="s">"year"</span> 
                <span class="na">className</span><span class="p">=</span><span class="s">"form-control"</span>
                <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">year</span><span class="si">}</span>
                <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="nx">e</span> <span class="o">=&gt;</span> <span class="nx">setYear</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span>
                <span class="p">/&gt;</span>
        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"submit"</span> <span class="na">value</span><span class="p">=</span><span class="s">"Create"</span> <span class="na">className</span><span class="p">=</span><span class="s">"btn btn-primary"</span> <span class="p">/&gt;</span>
    <span class="p">&lt;/</span><span class="nt">form</span><span class="p">&gt;;</span>
<span class="p">}</span></code></pre></figure>

<p>We will also create a separate route <code class="language-plaintext highlighter-rouge">/create</code> to display the <code class="language-plaintext highlighter-rouge">CreateBook</code> component, and a link to that route. Update <code class="language-plaintext highlighter-rouge">src/App.js</code>:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">ApolloClient</span><span class="p">,</span> <span class="nx">InMemoryCache</span><span class="p">,</span> <span class="nx">ApolloProvider</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/client</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Hello</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/Hello</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Books</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/Books</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">CreateBook</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/CreateBook</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Link</span><span class="p">,</span> <span class="nx">BrowserRouter</span><span class="p">,</span> <span class="nx">Route</span><span class="p">,</span> <span class="nx">Routes</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react-router-dom</span><span class="dl">'</span><span class="p">;</span>

<span class="kd">const</span> <span class="nx">client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ApolloClient</span><span class="p">({</span>
  <span class="na">uri</span><span class="p">:</span> <span class="dl">'</span><span class="s1">http://localhost:4000</span><span class="dl">'</span><span class="p">,</span>
  <span class="na">cache</span><span class="p">:</span> <span class="k">new</span> <span class="nx">InMemoryCache</span><span class="p">(),</span>
<span class="p">});</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">App</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">return</span> <span class="p">(</span>
    <span class="p">&lt;</span><span class="nc">ApolloProvider</span> <span class="na">client</span><span class="p">=</span><span class="si">{</span><span class="nx">client</span><span class="si">}</span><span class="p">&gt;</span>
      <span class="p">&lt;</span><span class="nc">BrowserRouter</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">nav</span> <span class="na">className</span><span class="p">=</span><span class="s">"navbar navbar-dark bg-dark"</span><span class="p">&gt;</span>
          <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"navbar-nav mr-auto flex-row"</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="s">"/"</span> <span class="na">className</span><span class="p">=</span><span class="s">"nav-link mr-2"</span><span class="p">&gt;</span>Home<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="s">"/books"</span> <span class="na">className</span><span class="p">=</span><span class="s">"nav-link mr-2"</span><span class="p">&gt;</span>Books<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nc">Link</span> <span class="na">to</span><span class="p">=</span><span class="s">"/create"</span> <span class="na">className</span><span class="p">=</span><span class="s">"nav-link mr-2"</span><span class="p">&gt;</span>Create Book<span class="p">&lt;/</span><span class="nc">Link</span><span class="p">&gt;</span>
          <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">nav</span><span class="p">&gt;</span>
        <span class="p">&lt;</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="s">"container mt-5"</span><span class="p">&gt;</span>
          <span class="p">&lt;</span><span class="nc">Routes</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">path</span><span class="p">=</span><span class="s">"/"</span> <span class="na">element</span><span class="p">=</span><span class="si">{</span><span class="p">&lt;</span><span class="nc">Hello</span> <span class="p">/&gt;</span><span class="si">}</span> <span class="p">/&gt;</span>
            <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">path</span><span class="p">=</span><span class="s">"/books"</span> <span class="na">element</span><span class="p">=</span><span class="si">{</span><span class="p">&lt;</span><span class="nc">Books</span> <span class="p">/&gt;</span><span class="si">}</span> <span class="p">/&gt;</span>
            <span class="p">&lt;</span><span class="nc">Route</span> <span class="na">path</span><span class="p">=</span><span class="s">"/create"</span> <span class="na">element</span><span class="p">=</span><span class="si">{</span><span class="p">&lt;</span><span class="nc">CreateBook</span> <span class="p">/&gt;</span><span class="si">}</span> <span class="p">/&gt;</span>
          <span class="p">&lt;/</span><span class="nc">Routes</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">div</span><span class="p">&gt;</span>
      <span class="p">&lt;/</span><span class="nc">BrowserRouter</span><span class="p">&gt;</span>      
    <span class="p">&lt;/</span><span class="nc">ApolloProvider</span><span class="p">&gt;</span>
  <span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p><img src="/assets/2022/react-create-book.gif" alt="Create book UI in React" title="Create book UI in React" /></p>

<p>You may notice that a new book does not appear on the books list without a refresh. There are two ways to fix it:</p>
<ol>
  <li><a href="https://www.apollographql.com/docs/react/data/mutations/#updating-the-cache-directly">Update local GraphQL cache</a> - use this approach when performance is a priority over correctness</li>
  <li><a href="https://www.apollographql.com/docs/react/data/mutations/#refetching-queries">Refetch query</a> - use this approach when correctness is more important than performance</li>
</ol>

<p>We will use the refetch query approach, which is usually the best default answer unless you really care about high performance.</p>

<p>This requires passing a list of queries to refetch to <code class="language-plaintext highlighter-rouge">useMutation</code> hook:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="kd">const</span> <span class="p">[</span><span class="nx">createMutation</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useMutation</span><span class="p">(</span><span class="nx">CREATE_BOOK_MUTATION</span><span class="p">,</span> <span class="p">{</span>
    <span class="na">refetchQueries</span><span class="p">:</span> <span class="p">[</span>
        <span class="p">{</span><span class="na">query</span><span class="p">:</span> <span class="nx">BOOKS_QUERY</span><span class="p">}</span>
    <span class="p">]</span>
<span class="p">});</span></code></pre></figure>

<p>As you can see, we need <code class="language-plaintext highlighter-rouge">BOOKS_QUERY</code>, which we already defined in <code class="language-plaintext highlighter-rouge">Books</code> component. To do not copy/pasta, let’s extract all GraphQL queries and mutations to <code class="language-plaintext highlighter-rouge">src/graphql.js</code> file:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span> <span class="nx">gql</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@apollo/client</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">CREATE_BOOK_MUTATION</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    mutation Mutation($title: String, $year: Int) {
        create(title: $title, year: $year) {
            id
            title
            year
        }
    }
`</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">BOOKS_QUERY</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    query Books {
        books {
            title
            year
            id
        }
    }
`</span><span class="p">;</span></code></pre></figure>

<p>Now you can remove <code class="language-plaintext highlighter-rouge">BOOKS_QUERY</code> variable from <code class="language-plaintext highlighter-rouge">Books</code> component and just import it from <code class="language-plaintext highlighter-rouge">graphql.js</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span> <span class="nx">BOOKS_QUERY</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../graphql</span><span class="dl">'</span><span class="p">;</span></code></pre></figure>

<p>Similarly in, <code class="language-plaintext highlighter-rouge">CreateBook</code> component you can remove <code class="language-plaintext highlighter-rouge">CREATE_BOOK_MUTATION</code>, and import both <code class="language-plaintext highlighter-rouge">CREATE_BOOK_MUTATION</code> and <code class="language-plaintext highlighter-rouge">BOOKS_QUERY</code> from <code class="language-plaintext highlighter-rouge">graphql.js</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span> <span class="nx">BOOKS_QUERY</span><span class="p">,</span> <span class="nx">CREATE_BOOK_MUTATION</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../graphql</span><span class="dl">'</span><span class="p">;</span></code></pre></figure>

<h3>Deleting books</h3>

<p>Delete mutation will be very similar to <code class="language-plaintext highlighter-rouge">create</code> mutation from the previous section. We just need a mutation that takes <code class="language-plaintext highlighter-rouge">id</code> of a book we want to delete.</p>

<p>Let’s update schema in <code class="language-plaintext highlighter-rouge">src/models/typeDefs.js</code> by adding <code class="language-plaintext highlighter-rouge">delete</code> mutation that takes <code class="language-plaintext highlighter-rouge">id</code> parameter and returns the same id if book is successfully deleted:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="nx">gql</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">graphql-tag</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    type Query {
        hello(name: String): String
        books: [Book]
    }
    type Book {
        id: ID,
        title: String,
        year: Int,
    }
    type Mutation {
        create(title: String, year: Int): Book
        delete(id: ID): ID
    }
`</span><span class="p">;</span></code></pre></figure>

<p>And implement the resolver function in <code class="language-plaintext highlighter-rouge">src/resolvers.js</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span><span class="nx">Book</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./models/Book.js</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">Query</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">hello</span><span class="p">:</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">name</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="s2">`Hello </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
        <span class="na">books</span><span class="p">:</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">await</span> <span class="nx">Book</span><span class="p">.</span><span class="nx">find</span><span class="p">({}),</span>
    <span class="p">},</span>
    <span class="na">Mutation</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">create</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">title</span><span class="p">,</span> <span class="nx">year</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">newBook</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Book</span><span class="p">({</span>
                <span class="nx">title</span><span class="p">,</span> <span class="nx">year</span>
            <span class="p">});</span>
            <span class="k">await</span> <span class="nx">newBook</span><span class="p">.</span><span class="nx">save</span><span class="p">();</span>
            <span class="k">return</span> <span class="nx">newBook</span><span class="p">;</span>
        <span class="p">},</span>
        <span class="na">delete</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">id</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Book</span><span class="p">.</span><span class="nx">deleteOne</span><span class="p">({</span><span class="na">_id</span><span class="p">:</span> <span class="nx">id</span><span class="p">});</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">acknowledged</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span><span class="p">.</span><span class="nx">deletedCount</span> <span class="o">===</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">return</span> <span class="nx">id</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
        <span class="p">},</span>
    <span class="p">}</span>
<span class="p">};</span></code></pre></figure>

<p>We can test deleting book in the GraphQL playground by grabbing an id from MongoDB Compass:</p>

<p><img src="/assets/2022/apollo-graphql-playground-delete.png" alt="Apollo GraphQL Playground - delete" title="Apollo GraphQL Playground - delete" /></p>

<p>If we try to delete a book that doesn’t exist we should get <code class="language-plaintext highlighter-rouge">null</code> in response:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
  </span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"delete"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>We have our backend working. Let’s add delete functionality to UI.</p>

<p>Add delete mutation to <code class="language-plaintext highlighter-rouge">graphql.js</code> (you can copy/paste from GraphQL Playground):</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">export</span> <span class="kd">const</span> <span class="nx">DELETE_BOOK_MUTATION</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    mutation Mutation($id: ID) {
        delete(id: $id)
    }
`</span><span class="p">;</span></code></pre></figure>

<p>We will add a delete button to <code class="language-plaintext highlighter-rouge">Book</code> component, and call <code class="language-plaintext highlighter-rouge">delete</code> mutation from there:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">useMutation</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DELETE_BOOK_MUTATION</span><span class="p">,</span> <span class="nx">BOOKS_QUERY</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../graphql</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Book</span><span class="p">({</span><span class="nx">book</span><span class="p">})</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">deleteBookMutation</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useMutation</span><span class="p">(</span><span class="nx">DELETE_BOOK_MUTATION</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">refetchQueries</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span><span class="na">query</span><span class="p">:</span> <span class="nx">BOOKS_QUERY</span><span class="p">},</span>
        <span class="p">],</span>
    <span class="p">});</span>
    <span class="kd">const</span> <span class="nx">deleteBook</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">deleteBookMutation</span><span class="p">({</span>
            <span class="na">variables</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">id</span><span class="p">:</span> <span class="nx">book</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">});</span>
    <span class="p">};</span>
    <span class="k">return</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">book</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span><span class="si">{</span><span class="nx">book</span><span class="p">.</span><span class="nx">year</span><span class="si">}</span><span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span>
                <span class="p">&lt;</span><span class="nt">button</span> <span class="na">className</span><span class="p">=</span><span class="s">"btn btn-danger"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">deleteBook</span><span class="si">}</span><span class="p">&gt;</span>
                    Delete
                <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
            <span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
    <span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>We also need to add a new column to the table header in <code class="language-plaintext highlighter-rouge">Books</code> component:</p>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;thead</span> <span class="na">className=</span><span class="s">'thead-dark'</span><span class="nt">&gt;</span>
    <span class="nt">&lt;tr&gt;</span>
        <span class="nt">&lt;th&gt;</span>Title<span class="nt">&lt;/th&gt;</span>
        <span class="nt">&lt;th&gt;</span>Year<span class="nt">&lt;/th&gt;</span>
        <span class="nt">&lt;th&gt;&lt;/th&gt;</span>
    <span class="nt">&lt;/tr&gt;</span>
<span class="nt">&lt;/thead&gt;</span></code></pre></figure>

<p>Clicking a delete button should delete a book from MongoDB and delete a row from the books list table:</p>

<p><img src="/assets/2022/react-delete-book.gif" alt="Delete book from React UI" title="Delete book from React UI" /></p>

<h3>Editing books</h3>

<p>Editing is almost like creating. You need to pass <code class="language-plaintext highlighter-rouge">id</code> of an existing book in addition to <code class="language-plaintext highlighter-rouge">title</code> and <code class="language-plaintext highlighter-rouge">year</code>.</p>

<p>Let’s update the schema by adding <code class="language-plaintext highlighter-rouge">edit</code> mutation:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="nx">gql</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">graphql-tag</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    type Query {
        hello(name: String): String
        books: [Book]
    }
    type Book {
        id: ID,
        title: String,
        year: Int,
    }
    type Mutation {
        create(title: String, year: Int): Book
        delete(id: ID): ID
        edit(id: ID, title: String, year: Int): Book
    }
`</span><span class="p">;</span></code></pre></figure>

<p>And resolvers, by implementing <code class="language-plaintext highlighter-rouge">edit</code> mutation:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">import</span> <span class="p">{</span><span class="nx">Book</span><span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./models/Book.js</span><span class="dl">'</span><span class="p">;</span>

<span class="k">export</span> <span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
    <span class="na">Query</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">hello</span><span class="p">:</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">name</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="s2">`Hello </span><span class="p">${</span><span class="nx">name</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
        <span class="na">books</span><span class="p">:</span> <span class="k">async</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="k">await</span> <span class="nx">Book</span><span class="p">.</span><span class="nx">find</span><span class="p">({}),</span>
    <span class="p">},</span>
    <span class="na">Mutation</span><span class="p">:</span> <span class="p">{</span>
        <span class="na">create</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">title</span><span class="p">,</span> <span class="nx">year</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">newBook</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Book</span><span class="p">({</span>
                <span class="nx">title</span><span class="p">,</span> <span class="nx">year</span>
            <span class="p">});</span>
            <span class="k">await</span> <span class="nx">newBook</span><span class="p">.</span><span class="nx">save</span><span class="p">();</span>
            <span class="k">return</span> <span class="nx">newBook</span><span class="p">;</span>
        <span class="p">},</span>
        <span class="na">delete</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">id</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Book</span><span class="p">.</span><span class="nx">deleteOne</span><span class="p">({</span><span class="na">_id</span><span class="p">:</span> <span class="nx">id</span><span class="p">});</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">acknowledged</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span><span class="p">.</span><span class="nx">deletedCount</span> <span class="o">===</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">return</span> <span class="nx">id</span><span class="p">;</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
        <span class="p">},</span>
        <span class="na">edit</span><span class="p">:</span> <span class="k">async</span> <span class="p">(</span><span class="nx">_</span><span class="p">,</span> <span class="p">{</span><span class="nx">id</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">year</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span>
            <span class="kd">const</span> <span class="nx">result</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Book</span><span class="p">.</span><span class="nx">updateOne</span><span class="p">(</span>
                <span class="p">{</span>
                    <span class="na">_id</span><span class="p">:</span> <span class="nx">id</span><span class="p">,</span>
                <span class="p">},</span>
                <span class="p">{</span>
                    <span class="na">$set</span><span class="p">:</span> <span class="p">{</span>
                        <span class="nx">title</span><span class="p">,</span>
                        <span class="nx">year</span>
                    <span class="p">},</span>
                <span class="p">});</span>
            <span class="k">if</span> <span class="p">(</span><span class="nx">result</span><span class="p">.</span><span class="nx">acknowledged</span> <span class="o">&amp;&amp;</span> <span class="nx">result</span><span class="p">.</span><span class="nx">modifiedCount</span> <span class="o">===</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">return</span> <span class="k">await</span> <span class="nx">Book</span><span class="p">.</span><span class="nx">findOne</span><span class="p">({</span><span class="na">_id</span><span class="p">:</span> <span class="nx">id</span><span class="p">});</span>
            <span class="p">}</span>
            <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">};</span></code></pre></figure>

<p>Test in GraphQL Playground:</p>

<p><img src="/assets/2022/apollo-graphql-playground-edit.png" alt="Apollo GraphQL Playground - edit" title="Apollo GraphQL Playground - edit" /></p>

<p>To enable editing from UI, we will add an edit button to <code class="language-plaintext highlighter-rouge">Book</code> component. It will change text to <code class="language-plaintext highlighter-rouge">input</code> fields when in editing mode, and display save and cancel buttons to commit or discard changes.</p>

<p>Let’s start with adding <code class="language-plaintext highlighter-rouge">EDIT_BOOK_MUTATION</code> to <code class="language-plaintext highlighter-rouge">src/graphql.js</code>:</p>

<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="k">export</span> <span class="kd">const</span> <span class="nx">EDIT_BOOK_MUTATION</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`
    mutation Mutation($id: ID, $title: String, $year: Int) {
        edit(id: $id, title: $title, year: $year) {
            id
            title
            year
        }
    }
`</span><span class="p">;</span></code></pre></figure>

<p>Update <code class="language-plaintext highlighter-rouge">src/components/Book.js</code> by adding editing state, buttons and call to update book:</p>

<figure class="highlight"><pre><code class="language-react" data-lang="react"><span class="k">import</span> <span class="p">{</span> <span class="nx">useMutation</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@apollo/client</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DELETE_BOOK_MUTATION</span><span class="p">,</span> <span class="nx">BOOKS_QUERY</span><span class="p">,</span> <span class="nx">EDIT_BOOK_MUTATION</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../graphql</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react</span><span class="dl">"</span><span class="p">;</span>

<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nx">Book</span><span class="p">({</span><span class="nx">book</span><span class="p">})</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">deleteBookMutation</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useMutation</span><span class="p">(</span><span class="nx">DELETE_BOOK_MUTATION</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">refetchQueries</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span><span class="na">query</span><span class="p">:</span> <span class="nx">BOOKS_QUERY</span><span class="p">},</span>
        <span class="p">],</span>
    <span class="p">});</span>
    <span class="kd">const</span> <span class="nx">deleteBook</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">deleteBookMutation</span><span class="p">({</span>
            <span class="na">variables</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">id</span><span class="p">:</span> <span class="nx">book</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">});</span>
    <span class="p">};</span>

    <span class="kd">const</span> <span class="p">[</span><span class="nx">isEditing</span><span class="p">,</span> <span class="nx">setIsEditing</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">title</span><span class="p">,</span> <span class="nx">setTitle</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="nx">book</span><span class="p">.</span><span class="nx">title</span><span class="p">);</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">year</span><span class="p">,</span> <span class="nx">setYear</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useState</span><span class="p">(</span><span class="nx">book</span><span class="p">.</span><span class="nx">year</span><span class="p">);</span>
    <span class="kd">const</span> <span class="p">[</span><span class="nx">editBookMutation</span><span class="p">]</span> <span class="o">=</span> <span class="nx">useMutation</span><span class="p">(</span><span class="nx">EDIT_BOOK_MUTATION</span><span class="p">,</span> <span class="p">{</span>
        <span class="na">refetchQueries</span><span class="p">:</span> <span class="p">[</span>
            <span class="p">{</span><span class="na">query</span><span class="p">:</span> <span class="nx">BOOKS_QUERY</span><span class="p">},</span>
        <span class="p">],</span>
    <span class="p">});</span>

    <span class="kd">const</span> <span class="nx">saveChanges</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">editBookMutation</span><span class="p">({</span>
            <span class="na">variables</span><span class="p">:</span> <span class="p">{</span>
                <span class="na">id</span><span class="p">:</span> <span class="nx">book</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
                <span class="na">title</span><span class="p">:</span> <span class="nx">title</span><span class="p">,</span>
                <span class="na">year</span><span class="p">:</span> <span class="o">+</span><span class="nx">year</span><span class="p">,</span>
            <span class="p">},</span>
        <span class="p">});</span>
        <span class="nx">setIsEditing</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>        
    <span class="p">};</span>

    <span class="kd">const</span> <span class="nx">discardChanges</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
        <span class="nx">setIsEditing</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
        <span class="nx">setTitle</span><span class="p">(</span><span class="nx">book</span><span class="p">.</span><span class="nx">title</span><span class="p">);</span>
        <span class="nx">setYear</span><span class="p">(</span><span class="nx">book</span><span class="p">.</span><span class="nx">year</span><span class="p">);</span>
    <span class="p">};</span>

    <span class="k">return</span> <span class="p">(</span>
        <span class="p">&lt;</span><span class="nt">tr</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span>
                <span class="si">{</span><span class="nx">isEditing</span> 
                 <span class="p">?</span> <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> 
                    <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">title</span><span class="si">}</span> 
                    <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">setTitle</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span> 
                    <span class="na">className</span><span class="p">=</span><span class="s">"form-control"</span> <span class="p">/&gt;</span>
                 <span class="p">:</span> <span class="nx">book</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span>
            <span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span>
                <span class="si">{</span><span class="nx">isEditing</span> 
                 <span class="p">?</span> <span class="p">&lt;</span><span class="nt">input</span> <span class="na">type</span><span class="p">=</span><span class="s">"text"</span> 
                    <span class="na">value</span><span class="p">=</span><span class="si">{</span><span class="nx">year</span><span class="si">}</span> 
                    <span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">setYear</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span>
                    <span class="na">className</span><span class="p">=</span><span class="s">"form-control"</span> <span class="p">/&gt;</span>
                 <span class="p">:</span> <span class="nx">book</span><span class="p">.</span><span class="nx">year</span><span class="si">}</span>
            <span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
            <span class="p">&lt;</span><span class="nt">td</span><span class="p">&gt;</span>
                <span class="si">{</span><span class="nx">isEditing</span> 
                <span class="p">?</span> <span class="p">&lt;&gt;</span>
                    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">className</span><span class="p">=</span><span class="s">"btn btn-success mr-2"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">saveChanges</span><span class="si">}</span><span class="p">&gt;</span>
                        Save
                    <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
                    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">className</span><span class="p">=</span><span class="s">"btn btn-danger"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">discardChanges</span><span class="si">}</span><span class="p">&gt;</span>
                        Cancel
                    <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
                <span class="p">&lt;/&gt;</span>
                <span class="p">:</span> <span class="p">&lt;&gt;</span>
                    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">className</span><span class="p">=</span><span class="s">"btn btn-info mr-2"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=&gt;</span> <span class="nx">setIsEditing</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="si">}</span><span class="p">&gt;</span>
                        Edit
                    <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
                    <span class="p">&lt;</span><span class="nt">button</span> <span class="na">className</span><span class="p">=</span><span class="s">"btn btn-danger"</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">deleteBook</span><span class="si">}</span><span class="p">&gt;</span>
                        Delete
                    <span class="p">&lt;/</span><span class="nt">button</span><span class="p">&gt;</span>
                <span class="p">&lt;/&gt;</span>
                <span class="si">}</span>
                
            <span class="p">&lt;/</span><span class="nt">td</span><span class="p">&gt;</span>
        <span class="p">&lt;/</span><span class="nt">tr</span><span class="p">&gt;</span>
    <span class="p">);</span>
<span class="p">}</span></code></pre></figure>

<p>Now, you should be able to edit books inline:</p>

<p><img src="/assets/2022/react-edit-book.gif" alt="Edit book from React UI" title="Edit book from React UI" /></p>

<p>To make sure that everything works as expected, you can double-check if books are being properly created, deleted and updated in Mongo with MongoDB Compass.</p>

<h2>Summary</h2>

<p>Congratulations! Now, you know how to build web apps with MARN stack!</p>

<p>You can find the entire code in this GitHub repo: <a href="https://github.com/jj09/marn">https://github.com/jj09/marn</a>.</p>

<p>I also recorded a video version of this guide:</p>

<iframe width="640" height="360" src="https://www.youtube.com/embed/6YAKvGlFcwo" title="Complete Guide to Web Apps Development with MARN Stack: MongoDB, Apollo Server, React, and Node.js" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<p>To learn more about Apollo Server, checkout <a href="https://www.apollographql.com/docs/">Apollo docs</a>. It’s pretty good!</p>

<p>To dive into MongoDB check out <a href="https://www.mongodb.com/docs/manual/crud/">MongoDB CRUD Operations</a> and <a href="https://mongoosejs.com/docs/api.html">mongoose API</a>. Once you learn how to write Mongo queries with <a href="https://www.mongodb.com/docs/mongodb-shell/">MongoDB Shell</a>, you don’t really need to go over <a href="https://mongoosejs.com/docs/api.html">mongoose API</a> as it’s almost identical.</p>

<p>If you are new to React, I recommend <a href="https://beta.reactjs.org/">React Docs</a> and <a href="https://www.youtube.com/watch?v=w7ejDZ8SWv8">ReactJS Crash Course</a>.</p>

<p>You can very easily swap different components for MARN stack. E.g., swap React with Vue.js as I did for <a href="https://github.com/jj09/vue-apollo">vue-apollo</a> demo project.</p>]]></content><author><name></name></author><category term="programming" /><category term="MongoDB" /><category term="React" /><category term="Node.js" /><category term="Apollo Server" /><category term="GraphQL" /><category term="web apps" /><summary type="html"><![CDATA[This is a step-by-step overview for creating the front-end, backend, and persistence layer with MARN Stack: MongoDB, Apollo Server, React and Node.js. I also created a video version of this tutorial, which you can find on YouTube.]]></summary></entry><entry><title type="html">I am an Ironman!</title><link href="https://jj09.net/i-am-an-ironman/" rel="alternate" type="text/html" title="I am an Ironman!" /><published>2022-11-15T00:00:00+00:00</published><updated>2022-11-15T00:00:00+00:00</updated><id>https://jj09.net/i-am-an-ironman</id><content type="html" xml:base="https://jj09.net/i-am-an-ironman/"><![CDATA[<p><img src="/assets/2022/Ironman-finish.png" alt="Ironman California finish line" title="Ironman California finish line" /></p>

<p>The Ironman triathlon is 2.4 mile swim, 112 mile bike and 26.2 mile run (marathon). You have 17 hours to finish in order to become an Ironman.</p>

<h2>How I got into triathlons</h2>

<p>I’ve been doing triathlons for over 5 years now. Mostly Sprint and Olympic distances. I also did a few half Ironman distance races (AKA Ironman 70.3).</p>

<p>This is what each distance contains:</p>
<ul>
  <li>Sprint: 750m swim + 20k bike + 5k run</li>
  <li>Olympic: 1.5k swim + 40k bike + 10k run</li>
  <li>Half Ironman: 1.2 mile swim + 56 mile bike + 13.1 mile run (half marathon) - the total distance is 70.3 miles, therefore it’s also called Ironman 70.3.</li>
</ul>

<p>I love biking since I was a kid. I decided to try triathlons when I was in college. Unfortunately, it took me a while to do my first triathlon, because of knee injury. Coming back to sports after that took over 1 year.</p>

<p>The first triathlon, I’ve ever done was indoor triathlon i55: 10 mins of swimming + 30 mins of biking + 15 mins of running. The final result was based on the total distance covered. As most beginners, I pushed too hard on bike, and was barely able to run for 15 minutes. Despite that, I was hooked, and soon after that I signed up for my very first sprint triathlon.</p>

<p>The first sprint triathlon I signed up for (Kirkland Triathlon) didn’t happen, because of the water quality at Juanita Beach in Kirkland. A few days before the race, they changed triathlon to run-bike-run, which was ultimately a duathlon. I still got triathlon finisher medal as they had them made before finding out about the water quality issue.</p>

<p>Despite all the obstacles, I decided to keep going. I kinda did sprint, so I went ahead and signed up for an Olympic Triathlon next year. It was Troika Triathlon in Spokane, WA. It was almost my first, legit triathlon. Almost, because the swim was cut short due to the weather issues, and bike distance was extended to compensate for shorter swim. I really enjoyed the challenge, and decided to take it to the next level. I did one more Olympic tri and entered the serious triathlon World by doing half-Ironman distance race. It was actually much easier than I thought, and I was thinking that maybe I would’ve been able to pull a full distance that day!</p>

<p><img src="/assets/2022/triathlon-bike.jpeg" alt="Triathlon biking" /></p>

<p>While training for half-Ironman, I learned that you cannot live life like you use to and just throw trainings in the spare time. You need to add time for rest, and take care of the nutrition. I learned that hard way. One week before the half-Ironman I was soo tired that I spent entire weekend in bed. I wasn’t sick. No cold, or sore throat. I was just exhausted.</p>

<p>Over years I did a few more sprints, olympics and one more Ironman 70.3 triathlon. While olympic distance races went well, the half-Ironman was a challenge. I was dealing with foot injury, and decided to just run less during the training for the race. This, didn’t ended up well. After the race, I messed up my foot so bad that I was barely able to walk. Fortunately, after a week or so I was fine. At that time I didn’t even stretch before and after workouts! Still had a lot to learn.</p>

<p>After the Ironman 70.3, which ended up with injury I concluded that races above Olympic distance are not healthy. They require more time to train that you need to stay healthy. There is too much strain on your body. Often resulting in injuries. There is also need to spend more time that I would like to in training. You want to go hiking this weekend? No, I need to do my planned swim. If I won’t my training plan will get screw up, and I’ll have to readjust it…and I won’t be prepared for the race as good as if I’ve done the planned session. So I sticked with Sprints and Olympics for a while.</p>

<p><img src="/assets/2022/triathlon-run.jpeg" alt="Triathlon running" /></p>

<h2>Ironman journey</h2>

<p>I knew that at some point in my life I wanted to finish the full distance Ironman triathlon. I was delaying it, because I knew that it would require significant time commitment. Not only for the time to train, but also for time to rest. When COViD happened, and put a pause to my favorite sport, I realized (again) that life is short and I shouldn’t postpone things. In 2020, when all races were cancelled, I did Sprint and Olympic triathlon with friends. We just swam, biked and run on our own. It was fun, but not as fun as real races.</p>

<p>When, in 2021 triathlons started happening again, I decided to do Ironman 70.3 Oregon. I was hungry. As running was my weakest discipline, because of the knee injuries, I decided to do half Marathon earlier that year. My thinking was: if I finish half marathon, I can do half Ironman. Swimming and biking wasn’t a problem for me. I did half marathon, followed by Olympic triathlon, and finished half Ironman without problems. Injury free! After the race a thought came to my mind: why not keep training and do a full Ironman this year? There was Ironman California, just 3 months away. This would give me enough time to build up from half to full Ironman. After discussing it with my wife, I signed up!</p>

<h3>Training</h3>

<p>I followed <a href="https://s3-eu-west-1.amazonaws.com/kpdocshare/Training_Plans/TRI38.First_Ironman.pdf">My First Ironman Training Plan</a>. I made one adjustment: I was doing only 1 swim per week instead of 2. Why? Swimming takes a lot of time! Not swimming itself, but before and after. Driving to the pool (or lake), changing, driving back, etc. It’s much more time consuming than biking and running. Additionally, the Ironman California has down river swim, which makes it much easier. I decided to prioritize biking and running.</p>

<p>I maintained a Google Spreadsheet where I tracked all my workouts. I used green for workouts I did as planned (at least 80% or more of planned distance), yellow for workouts that were cut short (below 80%), and red for workouts I skipped. Marking workouts as done (green) was one of the best parts of my day.</p>

<p><img src="/assets/2022/Ironman-training-spreadsheet.png" alt="Ironman training spreadsheet" /></p>

<p>Half way through my training I decided to do a quick consult with a coach. This made me to realize how much I didn’t know, and how much more I could’ve learn. I recommend anyone who want to attempt an Ironman to get a coach FROM THE BEGINNING!!! It will make your training more efficient, more enjoyable and less stressful. Is that enough? Am I doing it right? Isn’t that too much if I just want to finish? Coach, who has already done Ironman and trained others, can help to answer these questions. Thanks to my coach I realized that I won’t be able to run the marathon during Ironman. Together with other 80% of athleets. Only pros and top age-groupers run entire marathon. Most of people run/walk or just walk! I developed run/walk strategy that made my running more efficient: 0.3mi run / 0.1mi walk.</p>

<h3>Life</h3>

<p>Training for Ironman is not only about making time for training, but also about making more time for resting! I underestimated how much more time I would need for rest. Especially, during the weekends after long workouts. I just wanted to lay down in bed and watch TV…</p>

<p>Sometimes, easy thing like attending friend’s baby shower at 2PM on Saturday or joining friends to watch the new James Bond movie in the cinema that my friend rented was impossible. I couldn’t do baby shower because that day I had to ride 100 miles. For me that was ~8h. I would have to start at ~5AM, and shower/stretch/drive to baby shower in 1h. Not ideal. Especially taking into account that sunrise at the time was after 7AM. When planning to watch the new Bond movie with friends at 1PM, I had only 90mins run planned for that day. However, I had to change that plan, and I had to run 16 miles instead, which took me almost 4 hours. I planned to start at 8AM, but my body said no to 6AM wake up to eat ~2h before the run. I ended up starting after 9AM, and finished around 1PM. I skipped the movie. The Ironman dream was more important.</p>

<p>When reading this you probably think: couldn’t you just move that workout to different day or do it after the movie? Well, it’s not that easy. Doing it after the movie would require me to start around 5PM at best. Finishing long run at 9PM is not great. Moving to another day? It’s not easy to ensure that you do enough of swimming, biking and running even when everything goes according to the plan. Trying to move things around when you have full-time job, especially closer to race, is hard. Of course you can do it, but…you would be less prepared. You don’t want to DNF (Did Not Finish), because you decided to watch a movie instead of train, right?</p>

<p>The weather is another component that comes to play. The rain and cold can mess up your plans for good. I was actually pretty fortunate, taking into account I live in Seattle. Weather here, in September/October (peak training months), can be not the best. I had to shuffle workouts a bit, do some Zwift biking instead of running outside, and replace Open Water Swims with pool. The most important long runs and long bike rides I was able to do outdoor! Yay! I’ve seen other athleetes who had to do long 4h+ rides with trainer…I would’ve hate that.</p>

<h3>Challenges</h3>

<p>On top of already mentioned time-management issues and weather, you may also run into another very common challenge: injuries. This happened to me ~4-5 weeks before the race. My knee didn’t handle training load well. I also messed up by forgetting a knee brace that I usually wear for one of my long runs.</p>

<p>Many people quit at that point. I decided to do whatever it takes. I wanted to become Ironman. I didn’t want to postpone the dream. Especially, because I already put a lot of time and effort into this. Countless PT sessions, stretching, messages and hours of exercises at home on top of my workouts helped to alleviate the issue. I was still not very confident that I will be able to finish the race.</p>

<p>I did whatever I could, overcame all the obstacles: time management, sacrificing social life, and managing injury. Unfortunately, I couldn’t overcome the final obstacle. The race was cancelled because of <a href="https://www.tri247.com/triathlon-news/elite/ironman-california-cancelled-weather">Bomb Cyclone</a>! They announced it at the race morning when I was putting my wetsuit on. It was the right decision. That day winds were 30-40mph, and it was raining. The day after, there were fallen trees on the bike course, and run course was flooded.</p>

<p>I was devastated. All sacrifices and hard work for nothing. I tried to look at the bright side though. I decided to properly recover from injuries, and attempt it again next year!</p>

<h3>Recovery</h3>

<p>When Ironman got cancelled I jumped straight into Physical Therapy. Dan Benson from ForeFront PT is the best specialist I’ve ever worked with. Strongly recommend! He not only helped me to recover, but also gave me some tips that helped to improve my running. It took over 4 months from the time I started regular PT exercises until I could finally run. I was doing sets of exercises, which take 30 mins or more every other day. I hated it! I was doing it only because of my dream: becoming an Ironman. There is nothing fun about PT exercises like hip rotations, penguin walks with band, side squats or speed skitters. I was very happy that I was able to go back to running. I strengthened my hip muscles, and actually started running properly. Per Dan’s advice I also tried to keep 175 steps per minute cadence, which is not easy! I was usually below 150 spm! Everytime when I was hurting, increasing cadence seemed to fix it. That was a silver bullet. After 4 months I could run, and I did 5k race that went smooth.</p>

<h3>Smarter Training</h3>

<p>Thanks to the experience from the previous year, I knew better how to manage my time during the training. No social life for 3 months, with 1 exception for a party in 1 weekend. No international travel during that time neither. I really spent 3 months focused on the race. My life was: work, training, rest. Not much room for anything else. I sometimes work 50-60h/week, and it was hard not only for me but also for my wife. We were both commited though, as we both knew it would be just one and done. I am very grateful for her support. Without that becoming Ironman wouldn’t be possible.</p>

<p>In my second attempt, I prepared better in all disciplines. I followed the same <a href="https://s3-eu-west-1.amazonaws.com/kpdocshare/Training_Plans/TRI38.First_Ironman.pdf">My First Ironman Training Plan</a>. I still swam only once per week. This time I modified long runs to be distance based building up to 16 miles as longest run per my coach recommendation.</p>

<p>I vastly improved nutrition, both during and <a href="https://twitter.com/realJacobJed/status/1569351545950699521?s=20&amp;t=rFJyPdDq2T-jATA2b38L2A">after training</a>. 60g+ of carbs per hour during, and <a href="https://www.target.com/p/premier-protein-shake-chocolate-4pk-11-fl-oz/-/A-50835667">protein shake</a> after workouts longer than 1h.</p>

<p>I showed up on the start line stronger than year before and injury free.</p>

<h3>Successful race</h3>

<p>My goal for the race was to finish. I estimated that in the best case scenario I can do it in about 14 hours, but realistically closer to 16. Again, the time limit is 17 hours. If you finish after 17 hours and 1 second you are <strong>NOT Ironman</strong>.</p>

<p>My pacing plan:</p>

<ul>
  <li>swim: treat it as a warm up; swim at good pace, but don’t push</li>
  <li>bike: 80-90 rpm, HR below 144 bpm</li>
  <li>run: walk first 10 minutes, then run 0.3 mile / walk 0.1 mile, walk through every aid station</li>
</ul>

<p>My nutrition plan:</p>

<ul>
  <li>bike: during hours 1,3,5, and 6/7 - drink tailwind+gatorade mix (40-80g carbs per hour), in hours 2,4,6 - eat 1x honey stinger, 1x gel, 1x cliff block</li>
  <li>run: 1 gel every 15 mins, tailwind+gatorade on the half way point (special needs bag)</li>
</ul>

<p>There were some challenges during, and before, the race that I didn’t anticipated. The transition was setup at the Stadium in Sacramento, CA. Getting in and out required a lot of walking. Before getting to water I walked 5000 steps! I also spent 1 hour in line to the shuttle, which was taking athleetes from the transition zone to swim start. However, after that the <a href="https://www.youtube.com/watch?v=_Yhyp-_hX2s">Lose yourself</a> at the swim start, when I was entering the water, gave me good kick, and swim went great. It was very enjoyable experience. After it, another challenge: I had to <strong>run barefoot on concrete for 1 mile</strong> from swim exit to transition. I was worried that I could get injuried, and the race will be over. I took it very easy, walked parts of it, and made it to bike.</p>

<p><img src="/assets/2022/ironman-swim.JPG" alt="Ironman California swim" /></p>

<p>The 112 miles bike ride was not easy. I experienced the strongest winds I’ve ever biked with: 20-30mph! It was worse than biking in Kona, which is famous for brutal winds. I biked there year before. Bike course had two out and back loops (56 miles each). In some parts of the course I was getting tail wind, but other parts had head wind, and the worst possible: cross winds. I had to hold on to my aero bars and be focused every single second to don’t crash. It was very energy and mentaly draining. A few athleetes ended their race by crashing on the bike due to the wind. The last few miles were especially tough. It was against the wind, and wind was getting stronger later in the day. It was windy utill the very last turn into the transition!</p>

<p><img src="/assets/2022/ironman-bike.JPG" alt="Ironman California bike" /></p>

<p>When I got off the bike it was hard to walk. I was asking myself: “How am I gonna run a marathon now?”. I thought it was impossible, but I was trying not to think too much about it. Instead, I was focusing on executing my race plan. I took my time during the transition, changed to running gear, and walked for the first 10 minutes. After that, I started my run/walk strategy 0.3mi run / 0.1mi walk. I was also walking through aid stations, sometimes stopping for bathroom breaks.</p>

<p>First 9 miles went pretty well. After that, I experienced what Ironman athleetes call the dark moment. It was literally dark. We entered the park after sunset, and there was no lights there. Sometimes, total darkness. Thankfully to Ironman race organizers, there were providing head lamps for athleetes. However, even with the head lamp it was dark…</p>

<p>The miles were passing slowly and I was getting weaker. I had my rocket fuel mix (tailwind+gatorade) at run special needs bag, which was at mile 10.5. I drank almost entire bottle. After that I felt like puking for next 30-45 mins. I knew however, that I would be better off not feeling great than bonking.</p>

<p>Sometimes, I was wondering where is the next mile marker. A few times I thought that maybe I missed it, and the next one would be the following mile. Only to find a few minutes later that I just got to the one that I thought I passed…</p>

<p>The park section was out and back. After making the u-turn at mile 14.5 I started seeing the path to victory (AKA finish). It was more and more painful though. I started calculating how fast I need to run/walk, and it looked good. I even decided to walk 1 mile to see how much faster I am running than walking. It wasn’t much. Less than 5 minutes per mile! However, at that point it was more painful to walk than run!</p>

<p>In the last 10 miles I switched from eating gels to pretzels, chips and coca cola. It was great! Change from sugar to savory did magic. It was actually getting down easier than gels! That gave me a boost.</p>

<p>When I finally got to mile 20, one volounteer said that it’s only 10k to finish. 6.2 miles is 10k! This gave me another power boost. From that point I threw my strategy out of the window and just went on. I walked some parts of last 10k, but last 5k was my fastest split, and last mile was my fastest mile!</p>

<p><img src="/assets/2022/ironman-finish.jpg" alt="Ironman California 2022 Finish Line" /></p>

<p>I finished in 15 hours and 17 minutes. A little below 16h I was targeting, but way above assummed best case scenario of 14h. The winds and long transitions prevented that. None of this matters now. I am an Ironman!</p>

<p>Crossing the finish line was awesome! I wish it lasted longer than a few seconds.</p>

<iframe width="720" height="420" src="https://www.youtube.com/embed/qBMtwPUi4pY" title="Ironman California 2022 - Jacob Jedryszek finish" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>

<h2>Reflection and Lessons Learned</h2>

<p>If I was about to do it again, there are a few things I would’ve done differently.</p>

<ol>
  <li>I would probably cap my runs by time as recommended in <a href="https://s3-eu-west-1.amazonaws.com/kpdocshare/Training_Plans/TRI38.First_Ironman.pdf">My First Ironman Training Plan</a>, and for the longest run just do 16 miles instead of recommended 20.</li>
  <li>I would still do 1 swim per week. Even if it wasn’t down river swim, I would’ve swam 2.4 miles without issues.</li>
  <li>I would cut my dose of carbs from tailwind by half. Instead of 80g from tailwind I would get 40g, and try to get another 20-30g from gels or gatorade.</li>
  <li>I would put gatorade flavor I like in the bike special needs bag. The orange gatorade endurance provided on the course was awful. I couldn’t drink it, which forced me to switch to water. This resulted in less electrolytes.</li>
  <li>I would start eating chips/pretzels with coca cola earlier during the run. Probably after 5-6 miles.</li>
  <li>I would definitely get chicken broth whenever it was available. They were offering it when I was on the way out (about mile 11) and I was planning to get it on my way back…but when I was coming back they ran out.</li>
</ol>

<p>Things besides training that helped me a lot:</p>

<ol>
  <li>Stretching after every training.</li>
  <li>PT combined with strength exercises 1-2 times per week</li>
  <li>Making time for rest. Not planning anything after long bike rides and long runs on the weekends. It’s hard…but it helps a lot!</li>
  <li>Support from your partner, and understanding that 3 months before the race (or maybe even more) would be dedicated for Ironman. Not much time for travel or social life.</li>
</ol>

<p>As I mentioned before, training for Ironman is much more than just putting hours of training. You need to be smart at handling unexpected life events, managing your life well to don’t get sick and most importantly knowing when to stop pushing to don’t end up with injury. Especially during the run. This is where majority (if not all) of athleetes get injuries.</p>

<p>Proper diet and enough amount of sleep is crucial. Otherwise training won’t be effective. In <a href="https://amzn.to/3DX8MrK">Fast-Track Triathlete</a> Matt Dixon recommends to do not bother with training if you are not well rested as it will have the opposite effect.</p>

<p>Although Ironman challenge was a great journey, I am looking forward to go back to Sprint and Olympic distance races.</p>

<p>Maybe I’ll do Ironman in Kona after I retire ;)</p>

<p><img src="/assets/2022/ironman-wall.JPG" alt="Ironman California wall" /></p>

<h2>Call to action!</h2>

<p>It’s recommended that all of us do 30 mins of exercise per day. This is actually enough to train for both Sprint and Olympic distance triathlon!</p>

<p>If you do 2x30 mins run + 1x30 mins swim + 2x1h bike it would be more than enough to finish sprint triathlon! That’s total of 3.5h per week, which is 30 minutes per day.</p>

<p>For Olympic: 2x30 mins run + 1x30 mins swim + 1x brick (1h45m bike + 15m run) + optional 1x30-60mins run</p>

<p>That’s it! 4 times per week. 30mins/day average!</p>

<p>If you are interested in triathlon I recommend to join local triathlon club. For Sprint and Olympic distance races I recommend to use <a href="https://www.myprocoach.net/free-training-plans/">free training plans from Phil Mosley</a>. To learn more about triathlon: <a href="https://amzn.to/3A8o9fD">Your First Triathlon book</a> is a great resource to get started. For more advanced I recommend <a href="https://amzn.to/3EpTTzG">The Triathlete’s Training Bible</a>. If you have a full-time job and want to participate in Ironman 70.3 or Ironman distance races check out Matt Dixon’s <a href="https://amzn.to/3DX8MrK">Fast-Track Triathlete</a>.</p>

<p>Triathlon is fun, and it’s a great mix of different disciplines to keep your body in shape and healthy. I love this sport, and I recommend it to everyone! Hope to see you on the race course!</p>]]></content><author><name></name></author><category term="triathlon" /><summary type="html"><![CDATA[]]></summary></entry></feed>