<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Stack to Basics]]></title><description><![CDATA[Going back to basics to make development accessible for everyone.]]></description><link>https://stacktobasics.com</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1683124428979/Nh5xASFdG.png</url><title>Stack to Basics</title><link>https://stacktobasics.com</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 16 Apr 2026 23:47:35 GMT</lastBuildDate><atom:link href="https://stacktobasics.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Build For Yourself]]></title><description><![CDATA[Like most developers, I have a million ideas but only so much time to build them. I keep a document in Notion that lists these ideas — and it grows way faster than I could ever implement.
That’s doubly true for me thanks to my ADHD: I’ll start five p...]]></description><link>https://stacktobasics.com/build-for-yourself</link><guid isPermaLink="true">https://stacktobasics.com/build-for-yourself</guid><category><![CDATA[Micro Apps]]></category><category><![CDATA[AI]]></category><category><![CDATA[claude-code]]></category><category><![CDATA[claude.ai]]></category><category><![CDATA[engineering]]></category><category><![CDATA[Developer]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Wed, 04 Feb 2026 20:07:26 GMT</pubDate><content:encoded><![CDATA[<p>Like most developers, I have a million ideas but only so much time to build them. I keep a document in Notion that lists these ideas — and it grows way faster than I could ever implement.</p>
<p>That’s doubly true for me thanks to my ADHD: I’ll start five projects at once, wildly underestimate how long things will take (“It’ll take a month, tops!”), and before I know it, six months have passed and interest has evaporated.</p>
<p>In this post, I’ll share how I experimented with using AI not just for repetitive tasks, but to practically build personalised apps tailored to my problems. I’ll walk through how I built <a target="_blank" href="http://overpayyourmortgage.co.uk"><strong>overpayyourmortgage.co.uk</strong></a> after getting frustrated that no tool existed that actually solved my problem.</p>
<h1 id="heading-what-is-a-micro-app">What is a “Micro App”?</h1>
<p>I’ve used AI as a developer assistant since the start of the AI hype cycle, but only recently began experimenting with using AI to almost entirely build applications.</p>
<p>I’ve been skeptical of this “vibe coding” movement, as a lot of people have. I dread how much code is now running in “production” without being tested, or even read. You often see reports of non-technical people using AI to vibe code enitre applications, sometimes with <a target="_blank" href="https://netlas.io/blog/vibe-coding-security-risks/">disastrous results</a>.</p>
<p>But as software engineers, I think we’re in a unique position to use vibe coding for good — <strong>as long as we treat AI like a junior developer</strong>: useful for low-risk areas, but always under review.</p>
<p>One interesting trend is people building <em>micro apps</em> — lightweight tools that solve niche problems, often for a tiny audience (in some cases, just one person: the builder).</p>
<p>They’re useful because we’ve all hit situations where:</p>
<ul>
<li><p>an app almost does what we want, but not quite,</p>
</li>
<li><p>a manual process is annoying but not worth the time to automate,</p>
</li>
<li><p>or we <em>know</em> exactly what a tool should do — it’s just tedious to build.</p>
</li>
</ul>
<p>An interesting trend is people building “micro apps” - these are applications that solve niche targeted problems, with the target audience being small, sometimes just one user - the builder themselves. We’ve all faced issues with apps that don’t quite do what we want them to do, e.g. a hobby tracker that doesn’t track negative habits, or we’ve had problems which are arduous to solve manually but probably aren’t worth our time to solve programmatically.</p>
<p>This is where I believe vibe coding can be extraordinarily useful - outsourcing the initial build to AI can make the time investment worthwhile.</p>
<p>Whilst there isn’t a set definition of micro apps, in my view they generally have:</p>
<ul>
<li><p>A specific niche problem, often for a small audience</p>
</li>
<li><p>A short build time — a few days to a week</p>
</li>
<li><p>Potentially temporal use — useful for a time, not forever</p>
</li>
<li><p>Possibly private — maybe it lives only on your machine</p>
</li>
</ul>
<h1 id="heading-what-niche-problems-do-you-have">What Niche Problems Do <em>You</em> Have?</h1>
<p>Before building, think about problems you care about solving but haven’t gotten around to because of time or friction.</p>
<p>For example, here are some of mine:</p>
<ul>
<li><p>Keeping track of every Toby Carvery that my wife and I visit - we’re trying to visit all of them in the UK</p>
</li>
<li><p>Knowing which Pokémon games give the best catch rates for my living dex.</p>
</li>
</ul>
<p>These are projects that I have started in the past but never finished. I know exactly <em>what</em> they should do, but they became chores because the scope ballooned and interest faded.</p>
<p>Your ideas might be useful to other people, tracking pokémon catch rates could be useful to others, tracking Toby Carvery’s… probably not so much. Either way, design them as if you are the only user.</p>
<p>I say this because:</p>
<ul>
<li><p>It eliminates feature creep.</p>
</li>
<li><p>You won’t waste time building stuff nobody needs.</p>
</li>
<li><p>You don’t have to wrestle with broad use-cases like multi-language UI, accessibility for strangers, or authentication.</p>
</li>
</ul>
<p>These can end up killing your passion for a project, causing you to abandon it. Build what you would use. You can always extend it later.</p>
<p>And yes — if you’re the only user, you might not even need authentication, fancy UI libraries, or all that extra scaffolding. Be brutal about chopping out features. This isn’t your day job — chances are nobody else will read this code!</p>
<h1 id="heading-do-you-need-to-build-it">Do You <em>Need</em> To Build It?</h1>
<p>Before you jump in, it’s worth checking whether what you need already exists. There are a number of ways you can do this:</p>
<ul>
<li><p>Google around</p>
</li>
<li><p>Use dedicated tool finders, e.g. <a target="_blank" href="https://www.microapp.io/">https://www.microapp.io/</a></p>
</li>
<li><p>Ask ChatGPT</p>
</li>
</ul>
<p>I went with a combination of googling around and asking chatGPT.</p>
<p>Here is an example of a prompt you can use for this:</p>
<pre><code class="lang-plaintext">Look for existing software applications (web, mobile, or desktop) that match the following product description, use case, or problem to solve. For each app you find:
• Provide the app name
• A short description of what it does
• Key features relevant to the description
• Platform(s) (web, iOS, Android, Windows, macOS, etc.)
• A note on the closest match and why

Here is the description:
[PASTE YOUR DESCRIPTION HERE]
</code></pre>
<p>For my mortgage overpayment calculator, I pasted:</p>
<blockquote>
<p>An application that shows you how much money you could save by overpaying your mortgage, and how much it would cost each year. The app should respect the common 10% of the remaining balance rule and allow you to see results for a percentage based overpayment.</p>
</blockquote>
<p>ChatGPT gave me a number of results, but looking closer at them none of them actually did what I wanted them to do.</p>
<p>Whilst I could have gone more into depth, or used a more sophisticated tool, such as <a target="_blank" href="https://aicofounder.com/">aicofounder.com</a>, this was good enough for me and I felt vindicated enough to build my own.</p>
<h1 id="heading-building-a-micro-app">Building a Micro App</h1>
<p>Before we get started on building a micro app, we’ll first need to set up our own AI ecosystem and decide on our own boundaries.</p>
<h2 id="heading-pick-your-tools">Pick Your Tools</h2>
<p>There are numerous tools that you can use for building micro apps, depending on your appetite for control. You could use Claude Code or OpenAI Codex, or lean more into low-code solutions with Loveable or Replit.</p>
<p>Personally I use Claude Code as I still like to feel in control of the project and delegate tasks to Claude Code.</p>
<h2 id="heading-decide-how-much-you-want-to-do-yourself">Decide how much you want to do yourself</h2>
<p>In theory, AI can write the entire app without you touching a line of code. In reality, this often gets frustrating — the agent can mess up, loop on itself, hit token limits, or just go off track.</p>
<p>What feels “right” will be different for all of us. For example, I’m perfectly happy letting Claude Code write unit tests and I review them afterwards. I’m also happy letting Claude Code add in UI elements.</p>
<p>No matter what you decide, what is important is that you review everything that the AI agent generates. Incidious bugs can be generated by AI that we can easily miss, that we likely wouldn’t have introduced ourselves. For micro apps this is less important as the audience is small, but I believe it’s a bad habit to fall into, plus it’s a good learning experience!</p>
<h2 id="heading-know-your-strengths-and-your-weaknesses">Know your strengths and your weaknesses</h2>
<p>We all have areas of software engineering that we’re not great at. In my case, I’m terrible at UX. I don’t have a good sense of what looks “good”, no matter how many design books I read. However what I am good at is architecture and writing readible and efficient code.</p>
<p>UX has put me off many personal projects so I’m more than happy to outsource it and focus on the bits that bring me joy and keep me interested in the project. I’d much rather write code at a slower pace than have to do extensive PRs for generated code. Of course we can flip this and I can use Claude Code to critic my implementation and essentially do the PR for me, which has helped me find gaps in my implementation in the past.</p>
<h2 id="heading-specialised-prompts-instructions-skills-and-agents">Specialised Prompts, Instructions, Skills, and Agents</h2>
<p>A big part of using AI agents is knowing how to prompt and instruct them to get the result you want. Depending on what you use these can have different names, but there is movement to try to homogenise these, such as <a target="_blank" href="https://agentskills.io/home">Agent Skills</a> and <a target="_blank" href="https://agents.md/">Agents.md</a>.</p>
<p>Whilst you’ll most likely build up your own catalogue as time goes by (I have my own project engineer subagent for example), a good place to start is <a target="_blank" href="https://github.com/hesreallyhim/awesome-claude-code">Awesome Claude Code</a> if you’re using Claude Code, or look for an equivalent “Awesome &lt;Tool&gt;” repository depending on your choice of tool.</p>
<p>Lets spend some time here going over the different types.</p>
<h3 id="heading-specialised-prompts">Specialised Prompts</h3>
<p>These are carefully crafted, one-off or reusable prompts designed to get a <em>specific kind of output</em> from The AI agent.</p>
<p><strong>Example</strong></p>
<pre><code class="lang-plaintext">You are a senior Java engineer.
Review the following Spring Boot service for:
- concurrency issues
- transaction boundaries
- performance problems

Return findings as a table with severity and fix.
</code></pre>
<h3 id="heading-instructions">Instructions</h3>
<p>Instructions are persistent or semi-persistent rules that shape how the AI Agent behaves across messages. We set these once, and they are applied repeatedly. They do not execute actions, but rather act as guardrails.</p>
<p><strong>Example</strong></p>
<pre><code class="lang-plaintext">Instructions:
- Always assume production-grade code
- Prefer explicit types over inference
- Never suggest libraries unless asked
</code></pre>
<h3 id="heading-skills">Skills</h3>
<p>Skills are defined <strong>capabilities or procedures</strong> which the AI agent can repeatedly perform when invoked. These are good for repeatable small tasks.</p>
<p><strong>Example</strong></p>
<pre><code class="lang-plaintext">Skill: Backend Code Review

When invoked:
1. Identify architectural smells
2. Check concurrency and threading
3. Review error handling
4. Suggest refactors with examples
</code></pre>
<p>You might invoke it like:</p>
<pre><code class="lang-plaintext">Use the Backend Code Review skill on this service:
</code></pre>
<h3 id="heading-subagents">Subagents</h3>
<p>Subagents are specialised AI assistants that handle speciifc types of tasks. Each subagent runs in its own context window with a custom system prompt, specific tool access, and independent permissions. They are good for large open-ended problems and tasks, such as refactors or migrations.</p>
<p><strong>Example</strong></p>
<pre><code class="lang-plaintext">Agent: Spring Boot Migration Agent

Goal:
Migrate this service from blocking MVC to virtual threads.

Capabilities:
- Analyze current code
- Identify blocking calls
- Apply refactors
- Validate correctness
</code></pre>
<p>You might invoke it like:</p>
<pre><code class="lang-plaintext">Use the Spring Boot Migration Agent to migrate to virtual threads in this project
</code></pre>
<h2 id="heading-the-return-of-tdd">The Return of TDD</h2>
<p>Most developers will have at least heard of TDD - Test-Driven Development. Whilst most developers will agree in theory that it’s a good idea, in practice not many people actually do it. However in my experience it’s essential when you’re instructing an AI agent to implement a feature for you.</p>
<p>One problem I would often have is that I would ask Claude Code to implement something specific. It would do the work, and then come back to me to say it was done. I would then look and it had either not implemented what I asked, had missed edge cases, or it had broken something else. This would then be followed with a series of prompts by me to get it to implement what I wanted without breaking existing functionality or ignoring edge cases - which is time-consuming and frustrating to say the least.</p>
<p>I instead switched to asking Claude Code to implement features using TDD. I would explain the feature, ask it first to write tests regarding the scenarios I outlined, implement the feature, and <strong>not to return until the new tests and existing tests passed</strong>.</p>
<p>I found that this could burn through quite a few tokens, especially if it was a complex feature, but it meant I didn’t need to intervene as much and it could use the tests to automate checking it’s correctness.</p>
<p>For example here is a prompt I used:</p>
<pre><code class="lang-plaintext">The net position is not £0 when the savings rate and the mortgage rate are the same percentage.
Add tests to mortgage-calculator.test.ts to verify that it is £0 when the savings and mortgage rates
are the same for different percentage values.
Do not return until this is fixed and all tests pass.
</code></pre>
<p>I’ve found this gives a better experience overall.</p>
<h1 id="heading-my-micro-app-an-overpayment-mortgage-calculator-based-on-remaining-balance">My Micro App - An Overpayment Mortgage Calculator Based On Remaining Balance</h1>
<p>One problem I was facing was that I was not able to find a mortgage overpayment calculator that let me calculate overpayments based on my overpayment allowance.</p>
<p>In the UK, many lenders let you overpay up to <strong>10% of the remaining balance each year</strong> without Early Repayment Charges (ERCs), which can dramatically reduce total interest without incurring extra fees.</p>
<p>Here is an example of the overpayment allowance each year:</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Year</td><td>Mortgage Balance</td><td>10% Overpayment</td></tr>
</thead>
<tbody>
<tr>
<td>1</td><td>£200,000</td><td>£20,000</td></tr>
<tr>
<td>2</td><td>£175,000</td><td>£17,500</td></tr>
</tbody>
</table>
</div><p>However the calculators I found only let you:</p>
<ul>
<li><p>Overpay a fixed amount per month, or</p>
</li>
<li><p>Use percentage of <em>initial</em> balance</p>
</li>
</ul>
<p>Neither respected the yearly allowance on the remaining balance.</p>
<p>Additionally, my fixed deal is due to expire this October and I would most likely be able to secure a cheaper rate, as well as potentially add a lump sum overpayment when my mortgage switched to the Standard Variable Rate (SVR) and there were no ERCs. I couldn’t find any calculator that let me do this.</p>
<p>So I built my own micro app using AI!</p>
<p>You can see it live at <a target="_blank" href="http://overpayyourmortgage.co.uk"><strong>overpayyourmortgage.co.uk</strong></a>.</p>
<h2 id="heading-how-i-automated-it">How I Automated It</h2>
<p>My workflow:</p>
<ol>
<li><p>Brain dump into ChatGPT will all app details, architecture, features, tools, etc. and ask for a PRD.md (Product Requirements Document).</p>
</li>
<li><p>Feed the PRD to my “AI Project Engineer” user agent, which generates a workplan, architecture documents, and readmes and iterates over the workplan until everything is implemented.</p>
</li>
<li><p>Review this first iteration of code, create a V2 of the PRD.md based on things that have been missed either by myself or Claude, and keep doing this until I’m mostly happy.</p>
</li>
<li><p>For smaller points I ask Claude Code directly to implement specific features.</p>
</li>
</ol>
<p>For me, this got me about 80% of the way there.</p>
<h2 id="heading-what-i-did-manually">What I Did Manually</h2>
<p>There are certain steps that I’m sure if I tried hard enough I could set up with an agent, but for now at least I did manually:</p>
<ul>
<li><p>Created the initial project in GitHub</p>
</li>
<li><p>Commit changes and add meaningful messages</p>
</li>
<li><p>Configure deployment to Netlify</p>
</li>
<li><p>Bought a domain via Cloudflare and attached it to my Netlify project</p>
</li>
</ul>
<h2 id="heading-what-i-ended-up-with">What I Ended Up With</h2>
<p>At <a target="_blank" href="http://overpayyourmortgage.co.uk"><strong>overpayyourmortgage.co.uk</strong></a> you get:</p>
<ul>
<li><ul>
<li><p>A landing page explaining the calculator</p>
<ul>
<li><p>Guides on overpayments, interest savings, and ERCs</p>
</li>
<li><p>A calculator that lets you:</p>
<ul>
<li><p>Compare overpaying vs not overpaying</p>
</li>
<li><p>Use percentage- or fixed-amount overpayments that respect and cap at the annual overpayment allowance</p>
</li>
<li><p>Compare mortgage vs savings</p>
</li>
<li><p>Add remortgage scenarios with new rates, terms, and lump sums</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770212072034/7227e596-cb80-4cd6-9800-7d2c44b45415.png" alt="The calculator" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770212089213/dbe90e8e-e3f2-4de5-9b18-0df93919e82b.png" alt="Interest results" class="image--center mx-auto" /></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1770212107637/1c347473-99fb-4997-ae65-53e75aee164a.png" alt="Graph and detailed breakdown of year by year" class="image--center mx-auto" /></p>
<p>I’m pretty happy with it! And importantly it does exactly what I needed from a mortgage calculator.</p>
<p>I made the choice to host it and buy a domain as I imagine this isn’t a problem isn’t unique to me. I believe others could benefit from this, so perhaps it will graduate from a micro app in the future!</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Using AI to build micro apps allow us to build tools that are tailored to exactly what we need with a much lower time investment cost.</p>
<p>Whilst I have gone over what I consider the advantages of this approach - reducing boredom, lack of momentum, and time investment, which are the biggest project killers - there are a number of disadvantages that I didn’t expect.</p>
<p>When I’m waiting for Claude Code to respond, I often drift off and start looking at something else (most likely Reddit) which is concerning for my overall attention span but it often means I don’t get “into the zone”, meaning I’m more likely to make mistakes on the project. In future, I’m going to try tools like OpenClaw to experiment with letting Claude generate features overnight to cut down on “waiting” downtime and keep my focus where it matters most during the day.</p>
<p>Additionally, despite reviewing the code that Claude generates, I still have significantly less understanding than if I had built this myself. As an engineer this makes me nervous, even for a micro app, as there could be subtle but dangerous bugs that I’ve glossed over.</p>
<p>Finally, I worry that if I rely on it too much my skills will atrophy or I’ll become so dependent on it that I will struggle to solve problems without the the use of AI.</p>
<p>All of these are real problems that we as software engineers face - and it is up to each of us to find a balance that works.</p>
]]></content:encoded></item><item><title><![CDATA[Fixing Catastrophic Failures, "Failed to translate", and 0x80071772 errors in WSL]]></title><description><![CDATA[One day I turned on my computer, logged in, and was met with a Command Prompt window asking me to update WSL. I pressed the key to start the update, and then a few seconds later saw the dreaded "Catastrophic failure" message.
The update was being dri...]]></description><link>https://stacktobasics.com/fixing-catastrophic-failures-failed-to-translate-and-0x80071772-errors-in-wsl</link><guid isPermaLink="true">https://stacktobasics.com/fixing-catastrophic-failures-failed-to-translate-and-0x80071772-errors-in-wsl</guid><category><![CDATA[WSL]]></category><category><![CDATA[Docker]]></category><category><![CDATA[error]]></category><category><![CDATA[Windows]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Thu, 15 Feb 2024 10:09:28 GMT</pubDate><content:encoded><![CDATA[<p>One day I turned on my computer, logged in, and was met with a Command Prompt window asking me to update WSL. I pressed the key to start the update, and then a few seconds later saw the dreaded "Catastrophic failure" message.</p>
<p>The update was being driven by Docker Desktop, which I had configured to use WSL rather than Hyper-V, as is now recommended by Docker.</p>
<p>I tried multiple things to get WSL to work, but I wasn't able to perform any other commands with WSL as anything I tried, e.g. <code>wsl --unregister ubuntu</code> would be met with the "You need to upgrade" message.</p>
<p>I tried various methods to uninstall WSL completely so I could do a fresh install but nothing would fix it.</p>
<p>Here I'll explain how I fixed the issues on my machine.</p>
<h1 id="heading-tldr-version">Tl;dr Version</h1>
<ol>
<li><p>Follow the steps in <a target="_blank" href="https://www.minitool.com/news/uninstall-wsl.html">this guide</a> to uninstall WSL</p>
</li>
<li><p>Installed WSL from the Microsoft Store <a target="_blank" href="https://www.microsoft.com/store/productId/9P9TQF7MRM4R?ocid=pdpshare">here</a></p>
</li>
<li><p>Uninstall Ubuntu</p>
</li>
<li><p>Ensure your <code>New apps will save to:</code> setting in Windows is set to your main drive</p>
</li>
<li><p>Run <code>wsl --install -d Ubuntu</code></p>
</li>
<li><p>Run <code>wsl --setdefault Ubuntu</code></p>
</li>
</ol>
<h1 id="heading-long-version">Long Version</h1>
<p>Firstly I did the following</p>
<ol>
<li><p>I followed the steps in <a target="_blank" href="https://www.minitool.com/news/uninstall-wsl.html">this guide</a> to uninstall WSL</p>
</li>
<li><p>I installed WSL from the Microsoft Store <a target="_blank" href="https://www.microsoft.com/store/productId/9P9TQF7MRM4R?ocid=pdpshare">here</a></p>
</li>
</ol>
<p>I was then able to at least run <code>wsl</code> without it asking to update, but I was then faced with another error: <code>&lt;3&gt;WSL (206) ERROR: CreateProcessParseCommon:731: Failed to translate</code>.</p>
<p>After a lot of reading it turned out this error was because:</p>
<ol>
<li><p>Docker Desktop had set itself as the default distro for WSL, which was what was returning those errors when I tried to run <code>wsl</code> directly</p>
</li>
<li><p>Ubuntu had not been successfully installed as a distro when installing WSL through the Microsoft Store, which is the distro I wanted to use when I ran <code>wsl</code> via the command line</p>
</li>
</ol>
<p>I found now that my Docker containers were working fine, but I couldn't use WSL to run Ubuntu.</p>
<p>Running <code>wsl --list</code> showed the following:</p>
<pre><code class="lang-bash">Windows Subsystem <span class="hljs-keyword">for</span> Linux Distributions:
docker-desktop-data
docker-desktop (Default)
</code></pre>
<p>Here we can see that Ubuntu isn't listed, and Docker is set as the default.</p>
<p>I noticed that Windows had attempted to install Ubuntu (it was listed in my Windows applications):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707990392930/a68a9ac8-c762-40dd-8c34-6af8e8696cc6.png" alt class="image--center mx-auto" /></p>
<p>I uninstalled this and then ran <code>wsl --install -d Ubuntu</code>.</p>
<p>I was then met with another error:</p>
<pre><code class="lang-bash">WslRegisterDistribution failed with error: 0x80071772
Error: 0x80071772 The specified file is encrypted and the user does not have the ability to decrypt it.
</code></pre>
<p>Now this one happened because in the past I had set my Windows apps to be installed on a different drive from C:. WSL isn't able to force Ubuntu to be installed on C, and thus gives this cryptic error.</p>
<p>To fix this, I went to <code>Settings &gt; System &gt; Advanced storage settings &gt; Where new content is saved</code>. I changed the <code>New apps will save to:</code> option to be your main drive:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1707990696241/4d0b4cd5-dfbb-42a3-a1ba-80c9d3615ff1.png" alt class="image--center mx-auto" /></p>
<p>Once this has been done, I uninstalled <code>Ubuntu</code> again via the Windows start menu, and then ran <code>wsl --install -d Ubuntu</code> which ran successfully.</p>
<p>Lastly, I set Ubuntu as my default distro by running <code>wsl --setdefault Ubuntu</code> .</p>
<p>Finally, I was able to use Ubuntu via WSL by running <code>wsl</code> on the command line.</p>
<p>I hope this helps anyone else who is struggling with the same issue!</p>
]]></content:encoded></item><item><title><![CDATA[Adding Correlation IDs to Easily Track Down Errors - Spring Boot 3 Edition]]></title><description><![CDATA[You may have read my previous post, Adding Correlation IDs to Easily Track Down Errors, which used Spring Cloud Sleuth and Spring Boot 2.X to add correlation IDs to our logs and error responses.
In this post, I'll go over how we can do the same in Sp...]]></description><link>https://stacktobasics.com/adding-correlation-ids-to-easily-track-down-errors-spring-boot-3-edition</link><guid isPermaLink="true">https://stacktobasics.com/adding-correlation-ids-to-easily-track-down-errors-spring-boot-3-edition</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><category><![CDATA[tracing]]></category><category><![CDATA[error handling]]></category><category><![CDATA[SpringBoot3]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Tue, 09 May 2023 20:10:28 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683662936268/69a30362-7d90-4051-8eca-3b6c5566248c.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>You may have read my previous post, <a target="_blank" href="https://stacktobasics.com/correlation-ids">Adding Correlation IDs to Easily Track Down Errors</a>, which used Spring Cloud Sleuth and Spring Boot 2.X to add correlation IDs to our logs and error responses.</p>
<p>In this post, I'll go over how we can do the same in Spring Boot 3. If you'd like more background on why correlation IDs are useful and why you should use them, please read my previous post <a target="_blank" href="https://stacktobasics.com/correlation-ids">here</a>.</p>
<p>You can find all of the code referenced in this post <a target="_blank" href="https://github.com/Zinbo/blog-examples/tree/master/correlation-ids-spring-boot-3">here</a>.</p>
<h1 id="heading-whats-different-in-spring-boot-3">What's Different In Spring Boot 3?</h1>
<p>One of the big changes in Spring Boot 3 was the switch over to using <a target="_blank" href="https://micrometer.io/docs/tracing">Micrometer</a> for tracing. With that change, Spring Cloud Sleuth has been discontinued and is incompatible with Spring Boot 3.</p>
<p>Thankfully the switch over to Micrometer is easy!</p>
<p>In the rest of this post we will go over the examples given in the previous blog <a target="_blank" href="https://stacktobasics.com/correlation-ids">here</a>, but using Micrometer.</p>
<h1 id="heading-setting-up-a-spring-boot-app-with-micrometer"><strong>Setting Up A Spring Boot App with Micrometer</strong></h1>
<h2 id="heading-adding-a-simple-controller-and-service"><strong>Adding a Simple Controller and Service</strong></h2>
<p><strong>pom.xml</strong></p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.0.6<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span> <span class="hljs-comment">&lt;!-- lookup parent from repository --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.stacktobasics<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>correlation-ids-spring-boot-3<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>correlation-ids-spring-boot-3<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Showcase correlation ids in Spring Boot 3<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>17<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>io.micrometer<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>micrometer-tracing<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>io.micrometer<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>micrometer-tracing-bridge-brave<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.projectlombok<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>lombok<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">optional</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">optional</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<p><strong>StackToBasicsApplication</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3;

<span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;
<span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;

<span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StackToBasicsApplication</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        SpringApplication.run(StackToBasicsApplication.class, args);
    }

}
</code></pre>
<p><strong>HelloWorldController</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3.api;

<span class="hljs-keyword">import</span> com.stacktobasics.correlationidsspringboot3.service.HelloWorldService;
<span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;
<span class="hljs-keyword">import</span> org.springframework.http.ResponseEntity;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;
<span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> HelloWorldService helloWorldService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HelloWorldController</span><span class="hljs-params">(HelloWorldService helloWorldService)</span> </span>{
        <span class="hljs-keyword">this</span>.helloWorldService = helloWorldService;
    }

    <span class="hljs-meta">@GetMapping("/hello")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;String&gt; <span class="hljs-title">sayHello</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"Someone called the /hello endpoint"</span>);
        <span class="hljs-keyword">return</span> ResponseEntity.ok(helloWorldService.sayHello());
    }

    <span class="hljs-meta">@GetMapping("/bad-call")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">badCall</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"Someone called the /bad-call endpoint"</span>);
        helloWorldService.fakeBadCall();
    }
}
</code></pre>
<p><strong>HelloWorldService</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3.service;

<span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Service;

<span class="hljs-meta">@Service</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldService</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">sayHello</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"Returning hello from service"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-string">"hello"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">fakeBadCall</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"About to throw IllegalArgumentException..."</span>);
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"Exception from Hello World Service"</span>);
    }
}
</code></pre>
<p><strong>application.yml</strong></p>
<pre><code class="lang-yaml"><span class="hljs-attr">spring:</span>
  <span class="hljs-attr">application:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">correlation-ids-spring-boot-3</span>

<span class="hljs-attr">logging:</span>
  <span class="hljs-attr">pattern:</span>
    <span class="hljs-attr">level:</span> <span class="hljs-string">"%5p [${spring.application.name:},%X{traceId:-},%X{spanId:-}]"</span>
</code></pre>
<p>Note that, unlike with Sleuth, we need to manually set out logging pattern to include our traceId and spanId.</p>
<p>When we launch this application and call <code>GET /hello</code> we see the following:</p>
<pre><code class="lang-bash">2023-05-09T19:30:49.423+01:00  INFO [correlation-ids-spring-boot-3,645a91593eebd66457eb88b96d17bcc9,57eb88b96d17bcc9] 41420 --- [nio-8080-exec-2] c.s.c.api.HelloWorldController           : Someone called the /hello endpoint
2023-05-09T19:30:49.423+01:00  INFO [correlation-ids-spring-boot-3,645a91593eebd66457eb88b96d17bcc9,57eb88b96d17bcc9] 41420 --- [nio-8080-exec-2] c.s.c.service.HelloWorldService          : Returning hello from service
</code></pre>
<p>We can see our traceId and spanId being printed, which is the same as what we saw for our Spring Boot 2.X application with Spring Sleuth.</p>
<h2 id="heading-adding-the-correlation-id-to-exception-responses"><strong>Adding the Correlation ID to Exception Responses</strong></h2>
<p><strong>HttpExceptionHandler</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3.infra.exceptionhandler;

<span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;
<span class="hljs-keyword">import</span> org.springframework.http.HttpStatus;
<span class="hljs-keyword">import</span> org.springframework.http.ResponseEntity;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.ExceptionHandler;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.ResponseStatus;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestControllerAdvice;

<span class="hljs-meta">@RestControllerAdvice</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HttpExceptionHandler</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CorrelationIDHandler correlationIDHandler;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HttpExceptionHandler</span><span class="hljs-params">(CorrelationIDHandler correlationIDHandler)</span> </span>{
        <span class="hljs-keyword">this</span>.correlationIDHandler = correlationIDHandler;
    }

    <span class="hljs-meta">@ExceptionHandler(IllegalArgumentException.class)</span>
    <span class="hljs-meta">@ResponseStatus(HttpStatus.BAD_REQUEST)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;ExceptionResponse&gt; <span class="hljs-title">handleIllegalArgumentException</span><span class="hljs-params">(IllegalArgumentException exception)</span> </span>{
        log.warn(exception.getMessage(), exception);
        <span class="hljs-keyword">var</span> exceptionResponse = <span class="hljs-keyword">new</span> ExceptionResponse(HttpStatus.BAD_REQUEST, exception, correlationIDHandler.getCorrelationId());
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ResponseEntity&lt;&gt;(exceptionResponse, exceptionResponse.getStatus());
    }
}
</code></pre>
<p><strong>CorrelationIDHandler</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3.infra.exceptionhandler;

<span class="hljs-keyword">import</span> io.micrometer.tracing.CurrentTraceContext;
<span class="hljs-keyword">import</span> io.micrometer.tracing.TraceContext;
<span class="hljs-keyword">import</span> io.micrometer.tracing.Tracer;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Component;

<span class="hljs-keyword">import</span> java.util.Optional;

<span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CorrelationIDHandler</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Tracer tracer;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CorrelationIDHandler</span><span class="hljs-params">(Tracer tracer)</span> </span>{
        <span class="hljs-keyword">this</span>.tracer = tracer;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getCorrelationId</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> Optional.of(tracer).map(Tracer::currentTraceContext).map(CurrentTraceContext::context).map(TraceContext::traceId).orElse(<span class="hljs-string">""</span>);
    }
}
</code></pre>
<p>Note here that we now use io.micrometer.tracing, rather than org.springframework.cloud.sleuth.</p>
<p><strong>ExceptionResponse</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.wownamechecker.infra.exceptionhandler;

<span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonFormat;
<span class="hljs-keyword">import</span> lombok.Getter;
<span class="hljs-keyword">import</span> lombok.NonNull;
<span class="hljs-keyword">import</span> org.springframework.http.HttpStatus;

<span class="hljs-keyword">import</span> java.time.LocalDateTime;

<span class="hljs-meta">@Getter</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExceptionResponse</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String correlationId;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> HttpStatus status;
    <span class="hljs-meta">@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> LocalDateTime datetime;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String error;

    ExceptionResponse(<span class="hljs-meta">@NonNull</span> HttpStatus status, <span class="hljs-meta">@NonNull</span> Exception ex, String correlationId) {
        datetime = LocalDateTime.now();
        <span class="hljs-keyword">this</span>.status = status;
        <span class="hljs-keyword">this</span>.error = ex.getMessage();
        <span class="hljs-keyword">this</span>.correlationId = correlationId;
    }
}
</code></pre>
<p>When we call <code>GET /bad-call</code> we see the following in our logs:</p>
<pre><code class="lang-bash">2023-05-09T19:35:00.844+01:00  INFO [correlation-ids-spring-boot-3,645a925467299fb619c6ac98ab7f704b,19c6ac98ab7f704b] 41420 --- [nio-8080-exec-7] c.s.c.api.HelloWorldController           : Someone called the /bad-call endpoint
2023-05-09T19:35:00.844+01:00  INFO [correlation-ids-spring-boot-3,645a925467299fb619c6ac98ab7f704b,19c6ac98ab7f704b] 41420 --- [nio-8080-exec-7] c.s.c.service.HelloWorldService          : About to throw IllegalArgumentException...
2023-05-09T19:35:00.845+01:00  WARN [correlation-ids-spring-boot-3,645a925467299fb619c6ac98ab7f704b,19c6ac98ab7f704b] 41420 --- [nio-8080-exec-7] c.s.c.i.e.HttpExceptionHandler           : Exception from Hello World Service

java.lang.IllegalArgumentException: Exception from Hello World Service
    at com.stacktobasics.correlationidsspringboot3.service.HelloWorldService.fakeBadCall(HelloWorldService.java:17)
</code></pre>
<p>We get the following response:</p>
<pre><code class="lang-json">{
    <span class="hljs-attr">"correlationId"</span>: <span class="hljs-string">"645a925467299fb619c6ac98ab7f704b"</span>,
    <span class="hljs-attr">"status"</span>: <span class="hljs-string">"BAD_REQUEST"</span>,
    <span class="hljs-attr">"datetime"</span>: <span class="hljs-string">"2023-05-09 19:35:00"</span>,
    <span class="hljs-attr">"error"</span>: <span class="hljs-string">"Exception from Hello World Service"</span>
}
</code></pre>
<p>As expected, the <code>correlationId</code> field in the response matches the <code>traceId</code> in the logs.</p>
<h1 id="heading-testing-trace-propagation">Testing Trace Propagation</h1>
<p>Before we go any further, let's test that our trace IDs are being propagated to downstream requests. In case you're new to tracing, this is a key aspect as to how tracing works. The trace ID gets propagated to any requests that you make, meaning that you can trace the whole request across multiple services.</p>
<p>Trace IDs are propagated by using an HTTP header. By default, in Spring Sleuth this was the <code>X-B3-TraceId</code> header. Spring Boot 3 with Micrometer uses the <code>traceparent</code> header by default.</p>
<p>Let's add a new endpoint to our application and call this endpoint from <code>HelloWorldService</code> to check that the trace ID is being propagated properly.</p>
<p><strong>OtherController</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3.api;

<span class="hljs-keyword">import</span> jakarta.servlet.http.HttpServletRequest;
<span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;
<span class="hljs-keyword">import</span> org.springframework.http.ResponseEntity;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;

<span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OtherController</span> </span>{
    <span class="hljs-meta">@GetMapping("/other")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;String&gt; <span class="hljs-title">sayHello</span><span class="hljs-params">(HttpServletRequest request)</span> </span>{
        log.info(<span class="hljs-string">"traceparent: {}"</span>, request.getHeader(<span class="hljs-string">"traceparent"</span>));
        log.info(<span class="hljs-string">"Someone called the /other endpoint"</span>);
        <span class="hljs-keyword">return</span> ResponseEntity.ok(<span class="hljs-string">"other"</span>);
    }
}
</code></pre>
<p>HelloWorldService</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3.service;

<span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Service;
<span class="hljs-keyword">import</span> org.springframework.web.client.RestTemplate;

<span class="hljs-meta">@Service</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldService</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> RestTemplate restTemplate;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HelloWorldService</span><span class="hljs-params">(RestTemplate restTemplate)</span> </span>{
        <span class="hljs-keyword">this</span>.restTemplate = restTemplate;
    }


    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">sayHello</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"Returning hello from service"</span>);
        restTemplate.getForEntity(<span class="hljs-string">"http://localhost:8080/other"</span>, String.class);
        <span class="hljs-keyword">return</span> <span class="hljs-string">"hello"</span>;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">fakeBadCall</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"About to throw IllegalArgumentException..."</span>);
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"Exception from Hello World Service"</span>);
    }
}
</code></pre>
<p>We also need to add a <code>RestTemplate</code> bean to ensure that the trace ID gets propagated automatically:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3.infra.web;

<span class="hljs-keyword">import</span> org.springframework.boot.web.client.RestTemplateBuilder;
<span class="hljs-keyword">import</span> org.springframework.context.annotation.Bean;
<span class="hljs-keyword">import</span> org.springframework.context.annotation.Configuration;
<span class="hljs-keyword">import</span> org.springframework.web.client.RestTemplate;

<span class="hljs-meta">@Configuration</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RestTemplateConfiguration</span> </span>{

    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function">RestTemplate <span class="hljs-title">restTemplate</span><span class="hljs-params">(RestTemplateBuilder builder)</span> </span>{
        <span class="hljs-keyword">return</span> builder.build();
    }
}
</code></pre>
<p>When we call <code>GET /hello</code> we see the following in our logs:</p>
<pre><code class="lang-bash">2023-05-09T19:44:17.569+01:00  INFO [correlation-ids-spring-boot-3,645a94813f591c8b1e2d81517af62961,1e2d81517af62961] 12636 --- [nio-8080-exec-2] c.s.c.api.HelloWorldController           : Someone called the /hello endpoint
2023-05-09T19:44:17.569+01:00  INFO [correlation-ids-spring-boot-3,645a94813f591c8b1e2d81517af62961,1e2d81517af62961] 12636 --- [nio-8080-exec-2] c.s.c.service.HelloWorldService          : Returning hello from service
2023-05-09T19:44:17.591+01:00  INFO [correlation-ids-spring-boot-3,645a94813f591c8b1e2d81517af62961,c85d5409fb9a2748] 12636 --- [nio-8080-exec-5] c.s.c.api.OtherController                : traceparent: 00-645a94813f591c8b1e2d81517af62961-5225ae36a413cb58-00
2023-05-09T19:44:17.593+01:00  INFO [correlation-ids-spring-boot-3,645a94813f591c8b1e2d81517af62961,c85d5409fb9a2748] 12636 --- [nio-8080-exec-5] c.s.c.api.OtherController                : Someone called the /other endpoint
</code></pre>
<p>We can see that <code>traceparent</code> contains 4 values, delimited by <code>-</code>. These values are:</p>
<ul>
<li><p><code>version</code></p>
</li>
<li><p><code>trace-id</code></p>
</li>
<li><p><code>parent-id</code></p>
</li>
<li><p><code>trace-flags</code></p>
</li>
</ul>
<p>We can see that the second value, which is the trace ID, matches the traceID that we see in our logs.</p>
<p>You can read more about the <code>traceparent</code> header <a target="_blank" href="https://www.w3.org/TR/trace-context/#traceparent-header">here</a>.</p>
<p>Note that this is different from our previous implementation using Spring Boot 2.X and Sleuth, which used the <code>X-B3-TraceId</code> header, which only contained the trace ID.</p>
<h3 id="heading-using-old-headers">Using Old Headers</h3>
<p>If you would prefer to use the older <code>X-B3-*</code> headers, you can do this by simply adding the following bean:</p>
<pre><code class="lang-java">    <span class="hljs-meta">@Bean</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> Tracing <span class="hljs-title">braveTracing</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> Tracing.newBuilder()
                .propagationFactory(B3Propagation.newFactoryBuilder().injectFormat(B3Propagation.Format.MULTI).build())
                .build();
    }
</code></pre>
<p>Your requests will now contain the headers <code>x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled</code>, rather than <code>traceparent</code>.</p>
<h2 id="heading-adding-the-trace-id-to-the-response"><strong>Adding the Trace ID to the Response</strong></h2>
<p><strong>TraceFilter</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.correlationidsspringboot3.infra.web;

<span class="hljs-keyword">import</span> io.micrometer.tracing.CurrentTraceContext;
<span class="hljs-keyword">import</span> io.micrometer.tracing.Span;
<span class="hljs-keyword">import</span> io.micrometer.tracing.Tracer;
<span class="hljs-keyword">import</span> jakarta.servlet.*;
<span class="hljs-keyword">import</span> jakarta.servlet.http.HttpServletResponse;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Component;

<span class="hljs-keyword">import</span> java.io.IOException;
<span class="hljs-keyword">import</span> java.util.Optional;

<span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TraceFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Filter</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String TRACE_ID_HEADER_NAME = <span class="hljs-string">"traceparent"</span>;
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String DEFAULT = <span class="hljs-string">"00"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Tracer tracer;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TraceFilter</span><span class="hljs-params">(Tracer tracer)</span> </span>{
        <span class="hljs-keyword">this</span>.tracer = tracer;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doFilter</span><span class="hljs-params">(ServletRequest req, ServletResponse res,
                         FilterChain chain)</span> <span class="hljs-keyword">throws</span> IOException, ServletException </span>{

        HttpServletResponse response = (HttpServletResponse) res;
        <span class="hljs-keyword">if</span> (!response.getHeaderNames().contains(TRACE_ID_HEADER_NAME)) {
            <span class="hljs-keyword">if</span>(Optional.of(tracer).map(Tracer::currentTraceContext).map(CurrentTraceContext::context).isEmpty()) {
                chain.doFilter(req, res);
                <span class="hljs-keyword">return</span>;
            }
            <span class="hljs-keyword">var</span> context = tracer.currentTraceContext().context();
            <span class="hljs-keyword">var</span> traceId = context.traceId();
            <span class="hljs-keyword">var</span> parentId = context.spanId();
            <span class="hljs-keyword">var</span> traceparent = DEFAULT + <span class="hljs-string">"-"</span> + traceId + <span class="hljs-string">"-"</span> + parentId + <span class="hljs-string">"-"</span> + DEFAULT;
            response.setHeader(TRACE_ID_HEADER_NAME, traceparent);
        }
        chain.doFilter(req, res);
    }
}
</code></pre>
<p>Note that this is a bit different from our <code>TraceFilter</code> implementation using Spring Sleuth. Here we're building the whole <code>traceparent</code> header rather than just the trace ID.</p>
<p>When we now call <code>GET /hello</code> we'll see that the <code>traceparent</code> header is returned in the response:</p>
<pre><code class="lang-bash">traceparent: 00-645aa0cb147f7334b00a46bd38797e6c-b00a46bd38797e6c-00
</code></pre>
<h1 id="heading-conclusion"><strong>Conclusion</strong></h1>
<p>In this post, we've learned how to use Spring Boot 3 with Micrometer, which has superseded Spring Cloud Sleuth, to trace calls through our application. We've also learned how to include those trace IDs in error responses sent back to the client, as well as including the trace ID as an HTTP header to propagate it through other clients.</p>
<p>You can find all of the code referenced in this post <a target="_blank" href="https://github.com/Zinbo/blog-examples/tree/master/correlation-ids-spring-boot-3">here</a>.</p>
<p>Till next time!</p>
]]></content:encoded></item><item><title><![CDATA[Creating a Project Using Public Data for Fun and Profit: Part 4]]></title><description><![CDATA[This blog is part 4 of "Creating a Project Using Public Data for Fun and Profit".Previous posts:

part 1

part 2

part 3


In this part, we will cover:

Adding support for Twitter and Facebook cards

Measuring website performance using Google Analyti...]]></description><link>https://stacktobasics.com/web-app-public-data-part-4</link><guid isPermaLink="true">https://stacktobasics.com/web-app-public-data-part-4</guid><category><![CDATA[Next.js]]></category><category><![CDATA[Vercel]]></category><category><![CDATA[social media]]></category><category><![CDATA[Google Analytics]]></category><category><![CDATA[SEO]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Fri, 05 May 2023 17:30:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301943380/79bfa641-10ee-44b2-8bd8-361a7929efe1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This blog is part 4 of "Creating a Project Using Public Data for Fun and Profit".<br />Previous posts:</p>
<ul>
<li><p><a target="_blank" href="/blog/web-app-public-data-part-1">part 1</a></p>
</li>
<li><p><a target="_blank" href="/blog/web-app-public-data-part-2">part 2</a></p>
</li>
<li><p><a target="_blank" href="/blog/web-app-public-data-part-3">part 3</a></p>
</li>
</ul>
<p>In this part, we will cover:</p>
<ul>
<li><p>Adding support for Twitter and Facebook cards</p>
</li>
<li><p>Measuring website performance using Google Analytics</p>
</li>
<li><p>Measuring SEO using Google Search Console</p>
</li>
<li><p>Monitising our website using Google AdSense</p>
</li>
</ul>
<p>All the code referenced in this post can be found in my repo <a target="_blank" href="https://github.com/Zinbo/public-data-demo">here</a>. As well, you can find a live version of the application we build <a target="_blank" href="https://drivingpassrate.co.uk/">here</a>.</p>
<h1 id="heading-adding-social-media-support">Adding Social Media Support</h1>
<p>Adding social media support is a great way to improve the appearance of your website when shared on social media platforms. The tags provided in the sections below will add a card preview on Twitter and Facebook, which will contain a title, description, and image.</p>
<p>To add Twitter support, add the following tags to <code>_app.tsx</code> in <code>&lt;Head&gt;</code>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:card"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"summary"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:url"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"https://public-data-demo.vercel.app"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Best Driving Test Pass Rates Near Me"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Give yourself the best opportunity to pass your driving test. Find the driving test centre that has the best pass rate near you. Find in locations such as Manchester, London, Birmingham, Newcastle, Leeds, Wales, Scotland, anywhere in the UK."</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:image"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"https://public-data-demo.vercel.app/icon-512x512.png"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:creator"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"@shanepjennings"</span> /&gt;</span>
</code></pre>
<p>Make sure to change the URL, image, and creator tags to reflect your own!</p>
<p>Using a link to your website in a tweet will now show a card, like this:  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301359640/e129ff7d-49c5-4e2f-82fc-ee8fe39620a3.png" alt class="image--center mx-auto" /></p>
<p>To add Facebook support, add the following tags to <code>_app.tsx</code> in <code>&lt;Head&gt;</code>:</p>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:type"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"website"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Best Driving Test Pass Rates Near Me"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"Give yourself the best opportunity to pass your driving test. Find the driving test centre that has the best pass rate near you. Find in locations such as Manchester, London, Birmingham, Newcastle, Leeds, Wales, Scotland, anywhere in the UK."</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:url"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"https://public-data-demo.vercel.app"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:image"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"https://public-data-demo.vercel.app/icon-512x512.png"</span> /&gt;</span>
</code></pre>
<p>Again, make sure to change the URL and image tags to reflect your own!</p>
<p>Using a link to your website in a Facebook post will now show a card, like this:  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301380967/5b3ddb3c-e1f4-4afa-865a-774923dd6185.png" alt class="image--center mx-auto" /></p>
<p>To test how your website will appear on various social media platforms from various devices, you can use the <a target="_blank" href="https://en.rakko.tools/tools/9/">Open Graph Simulator</a>. Simply enter your website URL and you'll be shown how your website would appear on various platforms.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301402255/5a619af8-2518-4466-9047-085564e9e93e.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-performance">Performance</h1>
<p>Your application is now live, great! But now you might be wondering, "How can I see if anyone is even using my website?".<br />This is where Google Analytics comes in. It's a powerful tool that allows you to measure your website's performance, track user behaviour, and gain insights that can help you improve your site.</p>
<p>Here we'll walk through how to get started with Google Analytics.</p>
<h2 id="heading-step-1-create-an-account-and-property">Step 1: Create an Account and Property</h2>
<p>First, go to <a target="_blank" href="http://analytics.google.com">analytics.google.com</a> and click the "Start measuring" button. This will prompt you to create an account and a property. If you're a single developer, you'll likely have one account to encompass all of your websites and a property per website.</p>
<p>Once you've created your property, you should be given a Measurement ID. If not, click on the Gear icon on the bottom left of the screen, click "Data Streams" under the property column, and select the "Web" tab.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301448067/2b0c6a92-75c7-4b51-a62a-6178b36ad8b3.png" alt class="image--center mx-auto" /></p>
<p>Here, you'll find the Measurement ID.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301456040/79385b37-385f-493f-bf66-92c39aef2a27.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-step-2-add-the-google-analytics-script-to-your-website">Step 2: Add the Google Analytics Script to Your Website</h2>
<p>To enable analytics collections for your website, you'll need to add the Google Analytics script. Go to <code>_app.tsx</code> and add the following code before the <code>&lt;Head&gt;</code> element:</p>
<pre><code class="lang-typescript">&lt;Script id=<span class="hljs-string">"google-tag-manager"</span> strategy=<span class="hljs-string">"lazyOnload"</span>
        src=<span class="hljs-string">"https://www.googletagmanager.com/gtag/js?id=G-XXXXXX"</span>/&gt;
&lt;Script id=<span class="hljs-string">"google-analytics"</span> strategy=<span class="hljs-string">"lazyOnload"</span>&gt;
  {<span class="hljs-string">`
                window.dataLayer = window.dataLayer || [];
                function gtag(){dataLayer.push(arguments);}
                gtag('js', new Date());

                gtag('config', 'G-XXXXXX', {
                    page_path: window.location.pathname,
                    }); 
            `</span>}

&lt;/Script&gt;
</code></pre>
<p>Make sure to replace the <code>G-XXXXXX</code> with your own Measurement ID.</p>
<h2 id="heading-step-3-start-tracking-your-websites-performance">Step 3: Start Tracking Your Website's Performance</h2>
<p>Once you've added the Google Analytics script to your website, you can start tracking its performance. Visit your website a few times and click around on various pages. Eventually, you'll start seeing data in the Google Analytics console.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301471679/181fe939-530f-4996-a5f1-a4130acc12af.png" alt class="image--center mx-auto" /></p>
<p>Google Analytics tracks a lot of useful information, such as the number of users who have visited your website, where they're from, which pages are the most popular, and much more. I recommend taking some time to explore the different reports and analytics to gain valuable insights that can help you improve your website.<br />And the best part? Google Analytics is free to use!</p>
<h1 id="heading-seo">SEO</h1>
<p>I've mentioned SEO a few times in this post, however, if you haven't heard of it before it stands for Search Engine Optimisation, and it's all about getting your website to show up in search engine results, like Google. The higher your website ranks in search results, the more likely people are to click on it. And if you're running a monetized website, good SEO can mean more income.</p>
<p>Next.js has an excellent introduction to SEO, which you can find <a target="_blank" href="https://nextjs.org/learn/seo/introduction-to-seo">here</a>.</p>
<p>There are plenty of SEO tools out there, some free and some not. Some of these tools can help with keyword research, which can help you figure out which words to use on your website to improve your chances of showing up in search results. But in this section, we'll be focusing on Google Search Console, a free tool that I find particularly useful for SEO.</p>
<p>Google Search Console has two features that I find particularly helpful:</p>
<ol>
<li><p><strong>Indexing</strong>: You can see if Google has indexed pages from your website. Pages need to be indexed to show up in a Google search.</p>
</li>
<li><p><strong>Performance</strong>: You can see how many times your website has appeared in Google searches, how many clicks it's received, what people searched for to find it, and more.</p>
</li>
</ol>
<h2 id="heading-step-1-sign-up-with-google-search-console">Step 1: Sign Up With Google Search Console</h2>
<p>To get started with Google Search Console, go to <a target="_blank" href="http://search.google.com/search-console">search.google.com/search-console</a> and enter your website's domain under the "Domain" box on the left. Then click "Continue."</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301499210/c88a6da2-08bd-490a-982e-e39d7ead8b54.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-step-2-verify-domain-ownership">Step 2: Verify Domain Ownership</h2>
<p>You'll see a screen asking you to verify domain ownership, like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301510774/f583ad99-46a0-4225-a838-836c548936a5.png" alt class="image--center mx-auto" /></p>
<p>We need to complete this step before we can use Google Search Console. Unfortunately, we can't do this with the basic Vercel domain that we're using for free.<br />This step requires you to have purchased a domain in the previous step. The steps to verify domain ownership will vary depending on your domain provider.</p>
<p>If you purchased your domain through Google Domains, you can verify ownership by:</p>
<ul>
<li><p>Opening your domain in Google Domains</p>
</li>
<li><p>Select "DNS" from the left-hand sidebar</p>
</li>
<li><p>Clicking "Manage custom records"</p>
</li>
<li><p>Clicking "Create new record"</p>
<ul>
<li><p>Host name: leave blank</p>
</li>
<li><p>Type: A</p>
</li>
<li><p>TTL: Leave as default</p>
</li>
<li><p>Data: The text provided by Google Search console, starting with "google-site-verification="</p>
<p>  <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301535871/ae402e2c-c460-47f8-a808-7aaaccc07bde.png" alt class="image--center mx-auto" /></p>
</li>
</ul>
</li>
</ul>
<p>Once you've created the custom record, click the "Verify" button in Google Search Console. It may take some time for the change to take effect, so you may need to check back later.</p>
<h2 id="heading-indexing">Indexing</h2>
<p>Now, you might be wondering why you're not showing up on Google's search results yet. Well, the answer is simple: Google hasn't indexed your website yet.</p>
<p>First, let's talk about what indexing is. Google uses a "crawler" to find your website's <code>robots.txt</code> file. This file tells the crawler which URLs it can access to crawl. From there, the <code>sitemap.xml</code> file, which contains the locations of all of your static pages, is found. The crawler will then start indexing each of these pages.</p>
<p>It is important to note that this process can take <strong>months</strong>, especially if the website isn't appearing in a lot of Google search results. But there are ways to speed it up.</p>
<p>One way is to check if Google has found your <code>sitemap.xml</code> file. You can see this by clicking on "Sitemaps" on the left-hand sidebar. If your sitemap has been found then it will be listed there.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301579056/29dfb1a2-0795-47dd-8285-47930b39a8b6.png" alt class="image--center mx-auto" /></p>
<p>If your sitemap hasn't been found, you can speed up the process by submitting a link to your sitemap using the "Add a new sitemap" box. This will add the sitemap to the crawler's queue, increasing the likelihood that your website will be indexed sooner.</p>
<p>Once your sitemap has been discovered, Google will automatically start discovering pages on your website. In the screenshot above you'll see under the "Discovered pages" heading that 67 pages have been discovered on my website, <a target="_blank" href="http://drivingpassrate.co.uk">drivingpassrate.co.uk</a>.</p>
<p>Once you have some pages appearing you can dig into this deeper by clicking on "Pages" on the left-hand sidebar. This will show you which pages have been indexed and which have yet to be indexed.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301592207/7ca85e9e-94a3-47f9-9936-2c4f0883ad4b.png" alt class="image--center mx-auto" /></p>
<p>In the image above you can see how pages on my website have been indexed over time.<br />At the bottom of this page, we can see more details on why some pages have not been indexed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301603755/8aa3d8f3-946a-46d6-821f-b6e6825b96d7.png" alt class="image--center mx-auto" /></p>
<p>Here we can see that Google has:</p>
<ul>
<li><p>discovered 15 pages (by using the sitemap) but has not yet crawled or indexed them</p>
</li>
<li><p>has crawled 4 pages but has not yet indexed them.</p>
</li>
</ul>
<p>We can drill deeper again to see which pages haven't been indexed.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301628907/d1841a56-9186-4f3a-88aa-7a9b12547aa2.png" alt class="image--center mx-auto" /></p>
<p>I mentioned that this process can take months, and you can see proof of that in the image above, where <a target="_blank" href="http://drivingpassrate.co.uk/pass-rates/derby">drivingpassrate.co.uk/pass-rates/derby</a> was crawled in August last year but still hasn't been indexed.</p>
<p>We can speed up this process by forcing our pages into Google's indexing queue. To do this, search for your page in the search bar at the top that says "Inspect any URL". Note that you need to search for the <strong>whole</strong> URL, including HTTPS. The search is also case-sensitive so make sure that is also correct.<br />Once you've searched for a page you can check that that page exists by clicking the "Test Live URL" button. If you've entered the wrong URL then you'll see "Page cannot be indexed: Not found (404)", like this:  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301649398/bb777aa0-3de2-46b1-bd46-860268d363a3.png" alt class="image--center mx-auto" /></p>
<p>If you've entered the correct URL then you should see "Page can be indexed", like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301659163/2eca9158-5282-425a-a025-d4b9ad773a1e.png" alt class="image--center mx-auto" /></p>
<p>To force the page into the indexing queue click the "Request Indexing" button.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301677648/b02ceb24-3e73-4e9c-861c-f7a80a176ffd.png" alt class="image--center mx-auto" /></p>
<p>You should then see a popup stating that the URL was added to the queue. Note that it also says that submitting the same page multiple times will not change the queue position, so don't bother spamming the "Request Indexing" button!</p>
<p>Keep in mind that even though your page has been added to the queue, it can still take a long time to be indexed. However, it will be faster than waiting for the crawler to automatically crawl it. Also, note that you can only request a small number of pages to be indexed at one time before your quota is exceeded (around 10 pages per day). So, be strategic in which pages you request to be indexed.</p>
<p>If you exceed this quota, you'll see this popup when trying to request indexing:  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301694295/f1cad3bc-49c4-4600-9307-b9f61eeadc96.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-performance-1">Performance</h2>
<p>With this feature, you can see how many people have seen your results when searching in Google, how many people have clicked on your results, and what people were searching for when they saw your results.</p>
<p>To access this feature, simply click on "Performance" on the left-hand sidebar of the Search Console dashboard. You'll be presented with a graph that displays several metrics, including total clicks, total impressions, average click-through rate (CTR), and average position.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301707003/cb6431a1-d9b0-451e-bf50-d4116836e620.png" alt class="image--center mx-auto" /></p>
<p>Scrolling down, you'll find a table that displays more detailed information, such as the queries people searched for, the pages that appeared in the search results, the countries people were searching from, the devices they used to search, and the dates your pages appeared in search results. Each result set displays the number of impressions and the position of your website in the search results.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301717644/2fe6f1d2-e2a0-41e3-baa5-43ad15c582a3.png" alt class="image--center mx-auto" /></p>
<p>To dive even deeper, you can click on any of these results to see the page that appeared in the search results, as well as other metrics such as country and device. With this information, you can better understand how your website is performing in search results and make improvements as needed.</p>
<p>For example, by clicking the top result "winchester pass rate" we can see the page that appeared in the search results, as well as all the other metrics such as country and device.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301736564/d0269fe6-4d4d-4d2e-ae68-0e4b7ed4ae25.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-keyword-research">Keyword Research</h2>
<p>When it comes to SEO, it's also important to consider keyword research. While Search Console can show you what keywords people searched for when your website appeared in search results, it can't tell you what similar keywords people searched for when your website did not appear in the results. This is where keyword research comes in.</p>
<p>There are free tools available, such as <a target="_blank" href="https://ahrefs.com/keyword-generator">Ahrefs Keyword Generator</a>, that can help you conduct keyword research. By typing in a number of keywords related to your website, you can find out what people are searching for related to those keywords. For example, if you search for the term "driving test centre pass rate," you might find that people are searching specifically for "Mill Hill driving test centre pass rate," with around 100 searches per month.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301757846/298bdf32-e204-4c09-b3ac-dceaca20353b.png" alt class="image--center mx-auto" /></p>
<p>Armed with this information, you can optimize your website's content to target these keywords and improve your SEO.</p>
<h1 id="heading-monitise-your-website">Monitise your Website</h1>
<p>There are numerous ways that you can earn income from a website, but one popular method is by adding ads. And Google AdSense is an easy and effective way to start earning some revenue.</p>
<h2 id="heading-step-1-sign-up-with-google-adsense">Step 1: Sign Up with Google AdSense</h2>
<p>To get started, simply head over to <a target="_blank" href="http://adsense.google.com/start/">adsense.google.com/start/</a> and sign up. Once you have created an account, you can add your website to start displaying ads.</p>
<h2 id="heading-step-2-add-the-script">Step 2: Add the Script</h2>
<p>After signing in, navigate to the "Sites" tab on the left sidebar, and click on "New site" to add your website. Once you've done that, you will see the code you need to add to your website to start showing ads.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301777854/a25ecf1e-afe2-4135-8772-fea4b31f7e37.png" alt class="image--center mx-auto" /></p>
<p>To add the code to your website, simply go to <code>_app.tsx</code> and add the following code just before the <code>&lt;Head&gt;</code> element:</p>
<pre><code class="lang-typescript">&lt;Script
src=<span class="hljs-string">"https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXX"</span>
crossOrigin=<span class="hljs-string">"anonymous"</span>/&gt;
</code></pre>
<p>Make sure to replace "ca-pub-XXXXX" with your own unique AdSense publisher ID.</p>
<h2 id="heading-step-3-enable-ads">Step 3: Enable Ads</h2>
<p>After adding the code to your website, go back to the Google AdSense window and tick the "I've placed the code" option and click "Next." Next, click "Request review", so that your website will be reviewed for eligibility.</p>
<p>While waiting for the review process, you can decide on your ad placement. With Google AdSense, you have the option to let the system automatically place ads on your site or choose ad placements manually. In this post, we'll focus on the former option.</p>
<p>Navigate to the "Ads" tab on the left sidebar and click the pencil icon next to your website to edit its settings.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301800744/69b8f568-ac1e-4f01-aed0-e32c78e3f88e.png" alt class="image--center mx-auto" /></p>
<p>From there, select the "Auto ads" option to turn on automatic ad placement. Sometimes, the ads might not show up immediately, so try clicking "Apply to site" and coming back later to see if the ads have appeared. You should see some ad placeholders added to your site.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683301816521/e575f1d1-867e-44fe-b90b-6e7fb15023fd.png" alt class="image--center mx-auto" /></p>
<p>If you're not satisfied with the ad placement, you can always choose to manually place them on your site. Check out <a target="_blank" href="https://support.google.com/adsense/answer/9274025?sjid=1729009541156227832-EU">this guide</a> to learn how.</p>
<p>It's important to note that ads won't appear immediately after adding the code to your website. Google AdSense will review your website to ensure that it meets its policies, which can take up to two weeks.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>And there we have it! Firstly, well done for getting through this whole process. Building an entire application and ensuring it's production-ready is no easy feat, so congratulations on getting through this entire guide! Let's cover what we've done:</p>
<ul>
<li><p>Built a website using public data</p>
</li>
<li><p>Added a sitemap and robots.txt for SEO</p>
</li>
<li><p>Added PWA support</p>
</li>
<li><p>Deployed the website using Vercel</p>
</li>
<li><p>Added our own domain name</p>
</li>
<li><p>Added our own email address for our domain</p>
</li>
<li><p>Added support for Twitter and Facebook cards</p>
</li>
<li><p>Measured website performance using Google Analytics</p>
</li>
<li><p>Measured SEO using Google Search Console</p>
</li>
<li><p>Monitised the website using Google AdSense.</p>
</li>
</ul>
<p>That is a lot!</p>
<p>You can find all the code used in this post in my repo <a target="_blank" href="https://github.com/Zinbo/public-data-demo">here</a>. As well, you can find a live version of the application we built <a target="_blank" href="https://drivingpassrate.co.uk/">here</a>.</p>
<p>Till next time!</p>
]]></content:encoded></item><item><title><![CDATA[Creating a Project Using Public Data for Fun and Profit: Part 3]]></title><description><![CDATA[This blog is part 3 of the "Creating a Project Using Public Data for Fun and Profit" series.Previous posts:

part 1

part 2


In this part, we will cover:

Adding a favicon

Adding a sitemap and robots.txt

Adding PWA support

Deploying our website

...]]></description><link>https://stacktobasics.com/web-app-public-data-part-3</link><guid isPermaLink="true">https://stacktobasics.com/web-app-public-data-part-3</guid><category><![CDATA[Next.js]]></category><category><![CDATA[Vercel]]></category><category><![CDATA[SEO]]></category><category><![CDATA[Google Analytics]]></category><category><![CDATA[PWA]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Tue, 02 May 2023 15:37:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300559417/c42590de-8606-45f7-ac2b-2fc4b4cb1c29.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This blog is part 3 of the "Creating a Project Using Public Data for Fun and Profit" series.<br />Previous posts:</p>
<ul>
<li><p><a target="_blank" href="/blog/web-app-public-data-part-1">part 1</a></p>
</li>
<li><p><a target="_blank" href="/blog/web-app-public-data-part-2">part 2</a></p>
</li>
</ul>
<p>In this part, we will cover:</p>
<ul>
<li><p>Adding a favicon</p>
</li>
<li><p>Adding a sitemap and robots.txt</p>
</li>
<li><p>Adding PWA support</p>
</li>
<li><p>Deploying our website</p>
</li>
<li><p>Setting up a domain name</p>
</li>
<li><p>Setting up email forwarding</p>
</li>
</ul>
<p>All the code referenced in this post can be found in my repo <a target="_blank" href="https://github.com/Zinbo/public-data-demo">here</a>. As well, you can find a live version of the application we build <a target="_blank" href="https://drivingpassrate.co.uk/">here</a>.</p>
<h1 id="heading-adding-the-finishing-touches">Adding the Finishing Touches</h1>
<p>Congratulations on building your application! If you're planning on deploying it to the public, there are a few finishing touches that can take it to the next level of professionalism. In this section, we'll go over some easy but important tweaks that will make your app look even better.</p>
<h2 id="heading-adding-an-icon">Adding an Icon</h2>
<p>When you open your application at <a target="_blank" href="http://localhost:3000"><code>http://localhost:3000</code></a>, you may notice that the favicon (the small icon that appears on the tab in your browser) is set to the Next.js logo. That's not very personalized, is it? We will change this to be the icon for our website.</p>
<p>There are plenty of places online where you can get free assets. One of my favorites is <a target="_blank" href="http://flaticon.com">flaticon.com</a>. Let's use the icon <a target="_blank" href="https://www.flaticon.com/free-icon/pass_1633103?term=exam+pass&amp;page=1&amp;position=4&amp;origin=search&amp;related_id=1633103">here</a> for our website.</p>
<p>Before we get started, remember that favicons have a specific size: 32x32 pixels. So make sure to download the icon as a 32x32 PNG file. Once you have it, you'll need to convert it to a .ico file, which is what we need for our favicon. There are plenty of online tools for doing this, but one option is <a target="_blank" href="https://image.online-convert.com/convert-to-ico">here</a>. Just upload the PNG file and convert it to .ico.</p>
<p>Now that you have your <code>favicon.ico</code> file, it's time to replace the existing one in the <code>public</code> directory. This will ensure that your new icon is used when your app is deployed.</p>
<p>Once you've completed this step, head back to <a target="_blank" href="http://localhost:3000">http://localhost:3000</a>. You should see that the icon has changed on the tab.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300737558/a80afc6e-a643-4e33-a268-9cd8749b0f20.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-adding-a-sitemap-and-robotstxt">Adding a Sitemap and robots.txt</h2>
<p>Your website is almost ready to go live! However, before we deploy it, let's add a sitemap and a robots.txt file to improve its search engine optimization (SEO).<br />Sitemaps help search engines understand the structure of your website and find all the relevant pages.<br />The robots.txt tells search engines how to crawl your website.</p>
<p>To add these files to your website, we can use a package called <code>next-sitemap</code>. This package will automatically generate a sitemap and robots.txt for all of our static pages.</p>
<h3 id="heading-step-1-add-next-sitemapconfigjs">Step 1: Add next-sitemap.config.js</h3>
<p>First, we need to create a <code>next-sitemap.config.js</code> file in the root directory of our project. Copy the following code into the file:</p>
<pre><code class="lang-js"><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('next-sitemap').IConfig}</span> </span>*/</span>
<span class="hljs-keyword">const</span> config = {
    <span class="hljs-attr">siteUrl</span>: process.env.SITE_URL || <span class="hljs-string">'https://yourdomainname.com'</span>,
    <span class="hljs-attr">generateRobotsTxt</span>: <span class="hljs-literal">true</span>, <span class="hljs-comment">// (optional)</span>
    <span class="hljs-comment">// ...other options</span>
}
<span class="hljs-built_in">module</span>.exports = config
</code></pre>
<p>This sets up the configuration for <code>next-sitemap</code>. Notice that <code>siteUrl</code> is set to <a target="_blank" href="https://yourdomainname.com"><code>https://yourdomainname.com</code></a>. Don't worry, we will update this later.</p>
<h3 id="heading-step-2-install-next-sitemap">Step 2: Install next-sitemap</h3>
<p>Next, let's add the <code>next-sitemap</code> package to our project by running the following command in our terminal:</p>
<pre><code class="lang-shell">npm install next-sitemap
</code></pre>
<h3 id="heading-step-3-add-postbuild-step">Step 3: Add postbuild Step</h3>
<p>Once the package is installed, we need to add a <code>postbuild</code> step to our <code>package.json</code> file to generate the sitemap. Open the <code>package.json</code> file and add the following line after the <code>build</code> command:</p>
<pre><code class="lang-json">    <span class="hljs-string">"build"</span>: <span class="hljs-string">"next build"</span>,
    <span class="hljs-string">"postbuild"</span>: <span class="hljs-string">"next-sitemap"</span>,
</code></pre>
<p>Now, we can generate the sitemap by running the following command in our terminal:</p>
<pre><code class="lang-shell">npm run build
</code></pre>
<p>Make sure to have stopped your instance of <code>npm run dev</code> first.</p>
<p>Once the build is complete, you should see that 3 files have been generated in your <code>public</code> folder.</p>
<p>robots.txt:</p>
<pre><code class="lang-typescript"># *
User-agent: *
Allow: /

# Host
Host: https:<span class="hljs-comment">//yourdomainname.com</span>

# Sitemaps
Sitemap: https:<span class="hljs-comment">//yourdomainname.com/sitemap.xml</span>
</code></pre>
<p>sitemap.xml:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">sitemapindex</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.sitemaps.org/schemas/sitemap/0.9"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">sitemap</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://yourdomainname.com/sitemap-0.xml<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">sitemap</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">sitemapindex</span>&gt;</span>
</code></pre>
<p>sitemap-0.xml:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">urlset</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.sitemaps.org/schemas/sitemap/0.9"</span> <span class="hljs-attr">xmlns:news</span>=<span class="hljs-string">"http://www.google.com/schemas/sitemap-news/0.9"</span> <span class="hljs-attr">xmlns:xhtml</span>=<span class="hljs-string">"http://www.w3.org/1999/xhtml"</span> <span class="hljs-attr">xmlns:mobile</span>=<span class="hljs-string">"http://www.google.com/schemas/sitemap-mobile/1.0"</span> <span class="hljs-attr">xmlns:image</span>=<span class="hljs-string">"http://www.google.com/schemas/sitemap-image/1.1"</span> <span class="hljs-attr">xmlns:video</span>=<span class="hljs-string">"http://www.google.com/schemas/sitemap-video/1.1"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://yourdomainname.com<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2023-04-06T12:36:31.039Z<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.7<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://yourdomainname.com/cities<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2023-04-06T12:36:31.039Z<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.7<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://yourdomainname.com/pass-rates<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2023-04-06T12:36:31.039Z<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.7<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://yourdomainname.com/pass-rates/aberdeen<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2023-04-06T12:36:31.039Z<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.7<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://yourdomainname.com/pass-rates/bangor<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2023-04-06T12:36:31.039Z<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.7<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://yourdomainname.com/pass-rates/bath<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2023-04-06T12:36:31.039Z<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.7<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">url</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">loc</span>&gt;</span>https://yourdomainname.com/pass-rates/birmingham<span class="hljs-tag">&lt;/<span class="hljs-name">loc</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">lastmod</span>&gt;</span>2023-04-06T12:36:31.039Z<span class="hljs-tag">&lt;/<span class="hljs-name">lastmod</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">changefreq</span>&gt;</span>daily<span class="hljs-tag">&lt;/<span class="hljs-name">changefreq</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">priority</span>&gt;</span>0.7<span class="hljs-tag">&lt;/<span class="hljs-name">priority</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">url</span>&gt;</span>
...
</code></pre>
<h2 id="heading-how-to-make-your-app-a-progressive-web-app-pwa">How to Make Your App a Progressive Web App (PWA)</h2>
<p>PWAs are a game-changer for providing a consistent experience across a variety of devices, and they allow users to install your web app on their device as if it were a native Android or iOS app. And the good news is that with Next.js, setting up your application as a PWA is quick and easy!<br />You can read more about them <a target="_blank" href="https://web.dev/learn/pwa/">here</a>.</p>
<h3 id="heading-step-1-install-next-pwa">Step 1: Install next-pwa</h3>
<p>The first thing we'll do is install <code>next-pwa</code> with the following command:</p>
<pre><code class="lang-shell">npm install next-pwa
</code></pre>
<h3 id="heading-step-2-update-nextconfigjs">Step 2: Update next.config.js</h3>
<p>Next, we need to update our <code>next.config.js</code> file to use the <code>next-pwa</code> plugin. Here's the code you'll need:</p>
<pre><code class="lang-js"><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('next').NextConfig}</span> </span>*/</span>
<span class="hljs-keyword">const</span> withPWA = <span class="hljs-built_in">require</span>(<span class="hljs-string">'next-pwa'</span>)({
  <span class="hljs-attr">dest</span>: <span class="hljs-string">'public'</span>
})

<span class="hljs-keyword">const</span> nextConfig = withPWA({
  <span class="hljs-attr">reactStrictMode</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">swcMinify</span>: <span class="hljs-literal">true</span>,
})

<span class="hljs-built_in">module</span>.exports = nextConfig
</code></pre>
<h3 id="heading-step-3-create-manifestjson">Step 3: Create manifest.json</h3>
<p>To make our PWA work, we need to create a <code>manifest.json</code> file. The easiest way to do this is to use a generator like <a target="_blank" href="https://www.simicart.com/manifest-generator.html/">simicart</a>. Make sure you download the icon at size 512 <a target="_blank" href="https://www.flaticon.com/free-icon/pass_1633103?term=exam+pass&amp;page=1&amp;position=4&amp;origin=search&amp;related_id=1633103">here</a> as you'll need that for the generator.</p>
<p>Add the following properties to the Simicart manifest generator:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300757447/9a268dd6-ef24-4e3e-aabe-9976e536d3f7.png" alt class="image--center mx-auto" /></p>
<p>Click "Generate Manifest" and you'll download a zip. Extract this zip to your <code>public</code> directory and rename <code>manifest.webmanifest</code> to <code>manifest.json</code>.<br />Your <code>manifest.json</code> should look like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#008000"</span>,
  <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#FFFFFF"</span>,
  <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
  <span class="hljs-attr">"scope"</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Best Driving Test Pass Rates Near Me"</span>,
  <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"Driving Test Pass Rates"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Give yourself the best opportunity to pass your driving test. Find the driving test centre that has the best pass rate near you. Find in locations such as Manchester, London, Birmingham, Newcastle, Leeds, Wales, Scotland, anywhere in the UK."</span>,
  <span class="hljs-attr">"icons"</span>: [
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/icon-192x192.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/icon-256x256.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"256x256"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/icon-384x384.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"384x384"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/icon-512x512.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"512x512"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    }
  ]
}
</code></pre>
<h3 id="heading-step-4-add-pwa-tags-to-the-head-element">Step 4: Add PWA tags to the Head element</h3>
<p>Next, we need to add more tags to our <code>&lt;Head&gt;</code> element in the <code>_app.ts</code> file to provide more information on our PWA for different devices. Replace the <code>&lt;Head&gt;</code> element with the following:</p>
<pre><code class="lang-typescript">&lt;Head&gt;
  &lt;title&gt;Best Driving Test Pass Rates Near Me&lt;/title&gt;
  &lt;meta name=<span class="hljs-string">"description"</span>
        content=<span class="hljs-string">"Give yourself the best opportunity to pass your driving test. Find the driving test centre that has the best pass rate near you. Find in locations such as Manchester, London, Birmingham, Newcastle, Leeds, Wales, Scotland, anywhere in the UK."</span>/&gt;
  &lt;meta name=<span class="hljs-string">'application-name'</span> content=<span class="hljs-string">'Best Driving Test Pass Rates Near Me'</span> /&gt;
  &lt;link rel=<span class="hljs-string">"icon"</span> href=<span class="hljs-string">"/favicon.ico"</span>/&gt;

  {<span class="hljs-comment">/* iOS */</span>}
  &lt;meta name=<span class="hljs-string">"apple-mobile-web-app-capable"</span> content=<span class="hljs-string">"yes"</span> /&gt;
  &lt;meta name=<span class="hljs-string">"apple-mobile-web-app-status-bar-style"</span> content=<span class="hljs-string">"default"</span> /&gt;
  &lt;meta name=<span class="hljs-string">"apple-mobile-web-app-title"</span> content=<span class="hljs-string">"PWA App"</span> /&gt;
  &lt;meta name=<span class="hljs-string">"format-detection"</span> content=<span class="hljs-string">"telephone=no"</span> /&gt;

  {<span class="hljs-comment">/* Android */</span>}
  &lt;meta name=<span class="hljs-string">"mobile-web-app-capable"</span> content=<span class="hljs-string">"yes"</span> /&gt;
  &lt;meta name=<span class="hljs-string">"theme-color"</span> content=<span class="hljs-string">"#008000"</span> /&gt;

  &lt;link rel=<span class="hljs-string">"manifest"</span> href=<span class="hljs-string">"/manifest.json"</span> /&gt;
  &lt;link rel=<span class="hljs-string">"shortcut icon"</span> href=<span class="hljs-string">"/favicon.ico"</span> /&gt;
  &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"https://fonts.googleapis.com/css?family=Roboto:300,400,500"</span> /&gt;

&lt;/Head&gt;
</code></pre>
<p>Now, we can generate the PWA config by running the following command in our terminal:</p>
<pre><code class="lang-bash">npm run build
</code></pre>
<p>You'll see 2 new files in the <code>public</code> directory, <code>sw.js</code> and <code>workbox-&lt;guid&gt;.js</code>.<br />These files are crucial for PWAs and you can learn more about them <a target="_blank" href="https://developer.chrome.com/docs/workbox/">here</a>.</p>
<p>Once the build process is complete, start up the app by running <code>npm run start</code>. Then, navigate to <a target="_blank" href="http://localhost:3000/"><code>http://localhost:3000/</code></a>. You should now see an option to install the application.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300782825/3cad3312-f90f-4a85-9500-1e05605fbb2b.png" alt class="image--center mx-auto" /></p>
<p>Just a quick heads up: I had to change the <code>display</code> property to <code>standalone</code> in manifest.json before Chrome allowed me to install the app as a PWA. You might need to do the same.</p>
<h1 id="heading-deploy-the-application">Deploy the Application</h1>
<p>We're now ready to deploy our application! We'll be using Vercel to deploy our Next.js application, as it's free for personal projects and is made by the creators of Next.js themselves.</p>
<h2 id="heading-deploying-with-vercel">Deploying with Vercel</h2>
<p>To deploy your application on <a target="_blank" href="https://vercel.com/">Vercel</a>, start by signing up on their website using your GitHub account. Once you're logged in, head to the <a target="_blank" href="https://vercel.com/dashboard">Dashboard</a> and click the "Add New..." button followed by "Project". From there, click the "Import" button and select your project.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300799174/dfdb9a0e-81c5-4635-8f5f-d960c4501e24.png" alt class="image--center mx-auto" /></p>
<p>After that, leave all the fields on the next screen as they are and click "Deploy". Vercel will now begin deploying your application, and you'll be able to see the progress of the deployment on your screen.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300807725/9999aac9-51d1-4215-a950-723dcc59d9c8.png" alt class="image--center mx-auto" /></p>
<p>Once the deployment is finished, you can select your project and preview the landing page of your app. Click the "Visit" button to access your website, which now has a domain associated with it (e.g., <a target="_blank" href="http://public-data-demo.vercel.app"><code>public-data-demo.vercel.app</code></a>).  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300813958/64621adb-007b-424c-a6f7-67367f0cd976.png" alt class="image--center mx-auto" /></p>
<p>At this point, you now have a domain. For example, mine is <a target="_blank" href="http://public-data-demo.vercel.app"><code>public-data-demo.vercel.app</code></a>. You can now go and change your <code>next-sitemap.config.js</code> file to point to this domain if you wish. Make sure to re-generate the robots.txt and the sitemap by running <code>npm run build</code> and check this into your repo.</p>
<p>If you would rather get your own domain without the <a target="_blank" href="http://vercel.app"><code>vercel.app</code></a> suffix then hang on till the next section!</p>
<h3 id="heading-vercel-analytics">Vercel Analytics</h3>
<p>One great feature of Vercel is its analytics. You can enable these for free by clicking the heartbeat button and then clicking "Enable".  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300861196/229cbd36-cd55-46ab-b843-54472e2e02a1.png" alt class="image--center mx-auto" /></p>
<p>This will give you valuable insights into your site's performance and an overall experience score based on multiple factors.  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300869735/c7f1d74a-838d-4303-b06f-a718309bf4f2.png" alt class="image--center mx-auto" /></p>
<p>A low score can negatively impact SEO with Google, so it's important to know how your site is performing and make any necessary improvements.</p>
<h2 id="heading-buy-a-domain-name">Buy a Domain Name</h2>
<p>You may want to have your own domain name that isn't associated with vercel. Domains can vary wildly in price depending on how popular the domain is.</p>
<p>I usually get my domains from <a target="_blank" href="https://domains.google.com/">Google Domains</a> which is quick and easy to use. Say for example we wanted a domain related to the phrase <code>public-data-demo</code>. We can search for this in Google Domains and buy one for £10 a year:  </p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300890033/481a16ea-6390-44b2-a251-4d5d3c4ce46b.png" alt class="image--center mx-auto" /></p>
<p>We won't be walking through how to add a custom domain here, however I can assure you that is it very easy to do with Google Domains and Vercel. You can find a guide on how to do this <a target="_blank" href="https://vercel.com/docs/concepts/projects/domains/add-a-domain">here</a>.</p>
<p>You can now go and change your file to point to this domain if you wish.<br /><strong>Once your domain has been added make sure to update your</strong> <code>next-sitemap.config.js</code> file to reflect your new domain name! Also, make sure to re-generate the robots.txt and the sitemap by running <code>npm run build</code> and push this into your repo. We'll get into why this is important in a later section.</p>
<h3 id="heading-email-forwarding">Email Forwarding</h3>
<p>If you want to use a custom email address with your domain, but don't want to pay for Google Workspace, you can set up email forwarding for free.</p>
<p>To do this, go to your domain in Google Domains and navigate to the "Email" section. Click "Add email alias" and enter your desired email address at your domain, along with your personal email address that you want emails forwarded to. This will allow you to create an email address with your domain name (e.g., <a target="_blank" href="mailto:hello@drivingpassrate.co.uk">hello@drivingpassrate.co.uk</a>) and forward it to your personal email.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300907189/3a482a9d-4b0a-4eae-9567-5ee17974e5df.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This completes part 3!<br />Let's review what we've done here:</p>
<ul>
<li><p>Added a favicon</p>
</li>
<li><p>Added a sitemap and robots.txt</p>
</li>
<li><p>Made it a PWA</p>
</li>
<li><p>Deployed with Vercel</p>
</li>
<li><p>Got a domain name</p>
</li>
<li><p>Set up email forwarding</p>
</li>
</ul>
<p>You can find all the code used in this post in my repo <a target="_blank" href="https://github.com/Zinbo/public-data-demo">here</a>. As well, you can find a live version of the application we built <a target="_blank" href="https://drivingpassrate.co.uk/">here</a>.</p>
<p>Look out for the final part, where we'll be adding social media support, finding out how we can track website performance and SEO, and monetising our website!</p>
<p>Till next time!</p>
]]></content:encoded></item><item><title><![CDATA[Creating a Project Using Public Data for Fun and Profit: Part 2]]></title><description><![CDATA[This blog is part 2 of "Creating a Project Using Public Data for Fun and Profit" series.Previous posts:

part 1

In this part, we will cover:

Adding the ability to search by postcode and radius on the landing page and the pass rates page

Adding aut...]]></description><link>https://stacktobasics.com/web-app-public-data-part-2</link><guid isPermaLink="true">https://stacktobasics.com/web-app-public-data-part-2</guid><category><![CDATA[React]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[material ui]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Wed, 19 Apr 2023 15:22:12 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300022343/d3576423-71b0-4d55-8cac-5372be114584.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This blog is part 2 of "Creating a Project Using Public Data for Fun and Profit" series.<br />Previous posts:</p>
<ul>
<li><a target="_blank" href="/blog/web-app-public-data-part-1">part 1</a></li>
</ul>
<p>In this part, we will cover:</p>
<ul>
<li><p>Adding the ability to search by postcode and radius on the landing page and the pass rates page</p>
</li>
<li><p>Adding autocomplete to the postcodes search field</p>
</li>
<li><p>Creating a page for each city, showing the nearest test centres</p>
</li>
<li><p>Creating a page listing all cities, and adding a link to this page from the landing page</p>
</li>
</ul>
<p>All the code referenced in this post can be found in my repo <a target="_blank" href="https://github.com/Zinbo/public-data-demo">here</a>. As well, you can find a live version of the application we build <a target="_blank" href="https://drivingpassrate.co.uk/">here</a>.</p>
<h1 id="heading-adding-search-functionality">Adding Search Functionality</h1>
<p>Now that we have a page that shows the nearest and best test centres based on a postcode, we need to add a user-friendly way to search by postcode.<br />To do this we'll add a form, with a text field for the postcode, and a dropdown for the radius.</p>
<h2 id="heading-step-1-create-the-search-component">Step 1: Create the Search Component</h2>
<p>We'll start by adding a new component under our <code>component</code> directory called <code>Search.tsx</code>. This will contain the elements we just described and a submit button that will navigate the user to our <code>/pass-rates</code> page. <code>Search.tsx</code> should contain the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {Box} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/system"</span>;
<span class="hljs-keyword">import</span> {Button} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material"</span>;
<span class="hljs-keyword">import</span> Paper <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material/Paper"</span>;
<span class="hljs-keyword">import</span> React, {useEffect, useState} <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {useRouter} <span class="hljs-keyword">from</span> <span class="hljs-string">"next/router"</span>;


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Search</span>(<span class="hljs-params">{initialPostcode, initialRadius}:<span class="hljs-built_in">any</span></span>) </span>{
    <span class="hljs-keyword">const</span> router = useRouter()
    <span class="hljs-keyword">const</span> [postcode, setPostcode] = useState&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> [radius, setRadius] = useState&lt;<span class="hljs-built_in">number</span>&gt;(<span class="hljs-number">10</span>);

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

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleSubmit</span>(<span class="hljs-params"></span>) </span>{
        router.push(<span class="hljs-string">`/pass-rates?postcode=<span class="hljs-subst">${postcode}</span>&amp;radius=<span class="hljs-subst">${radius}</span>`</span>);
    }

    <span class="hljs-keyword">return</span> (
        &lt;Paper variant=<span class="hljs-string">"outlined"</span> sx={{p: <span class="hljs-number">1</span>}}&gt;
            &lt;div style={{display: <span class="hljs-string">'flex'</span>, flexFlow: <span class="hljs-string">'row wrap'</span>}}&gt;
                &lt;Box sx={{m: <span class="hljs-number">1</span>, flex: <span class="hljs-number">1</span>}}&gt;
                    &lt;TextField label=<span class="hljs-string">"Postcode"</span> value={postcode} onChange={<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> setPostcode(e.target.value)}/&gt;
                &lt;/Box&gt;
                &lt;FormControl sx={{m: <span class="hljs-number">1</span>, minWidth: <span class="hljs-number">120</span>, flex: <span class="hljs-number">1</span>}}&gt;
                    &lt;InputLabel id=<span class="hljs-string">"demo-simple-select-label"</span>&gt;Radius&lt;/InputLabel&gt;
                    &lt;Select
                        labelId=<span class="hljs-string">"demo-simple-select-label"</span>
                        id=<span class="hljs-string">"demo-simple-select"</span>
                        value={radius}
                        label=<span class="hljs-string">"Age"</span>
                        onChange={<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> setRadius(e.target.value <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>)}
                    &gt;
                        &lt;MenuItem value={<span class="hljs-number">5</span>}&gt;<span class="hljs-number">5</span> miles&lt;/MenuItem&gt;
                        &lt;MenuItem value={<span class="hljs-number">10</span>}&gt;<span class="hljs-number">10</span> miles&lt;/MenuItem&gt;
                        &lt;MenuItem value={<span class="hljs-number">20</span>}&gt;<span class="hljs-number">20</span> miles&lt;/MenuItem&gt;
                        &lt;MenuItem value={<span class="hljs-number">30</span>}&gt;<span class="hljs-number">30</span> miles&lt;/MenuItem&gt;
                    &lt;/Select&gt;
                &lt;/FormControl&gt;

                &lt;Box sx={{m: <span class="hljs-number">1</span>, mt: <span class="hljs-number">1.8</span>, flex: <span class="hljs-number">1</span>, display: <span class="hljs-string">'flex'</span>, flexDirection: <span class="hljs-string">'column'</span>}}&gt;
                    &lt;Button sx={{alignSelf: <span class="hljs-string">'center'</span>}} variant=<span class="hljs-string">"outlined"</span> onClick={handleSubmit} size=<span class="hljs-string">"large"</span>&gt;Search&lt;/Button&gt;
                &lt;/Box&gt;

            &lt;/div&gt;


        &lt;/Paper&gt;
    );
}
</code></pre>
<p>Here we can see that we have a <code>TextField</code> element for the postcode and a <code>Select</code> element for the radius. Our submit <code>Button</code> element calls the <code>handleSubmit</code> function when pressed, which navigates the user to the <code>/pass-rates</code> page, passing in the postcode and radius as query parameters.</p>
<h2 id="heading-step-2-add-search-component-to-landing-page">Step 2: Add Search Component to Landing Page</h2>
<p>Next we need to add the <code>&lt;Search&gt;</code> element to our landing page. Open <code>index.tsx</code> and add the following after the <code>&lt;div&gt;</code> containing the <code>&lt;Typography&gt;</code> element:</p>
<pre><code class="lang-typescript">&lt;Search initialPostcode={<span class="hljs-string">''</span>} initialRadius={<span class="hljs-number">10</span>}/&gt;
</code></pre>
<p>Go to <a target="_blank" href="http://localhost:3000">http://localhost:3000</a>. You should now see the new search fields.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300307585/af4341e7-0b36-4332-a191-ac5b304c0b05.png" alt class="image--center mx-auto" /></p>
<p>Try entering a postcode and clicking search, it will bring you to the <code>/pass-rates</code> page.</p>
<h2 id="heading-step-3-add-autocomplete-to-postcode-search">Step 3: Add Autocomplete to Postcode Search</h2>
<p>Currently, we have no validation for postcodes in our search component. One way that we can validate postcodes is to use <a target="_blank" href="http://postcodes.io">postcodes.io</a> again.<br />We can call <a target="_blank" href="http://postcodes.io">postcodes.io</a> to check if the entered postcode is a real postcode. Even better, <a target="_blank" href="http://postcodes.io">postcodes.io</a> offers an autocomplete endpoint which we can use to show an autocomplete dropdown.</p>
<p>We'll add the following functions to our <code>PostcodesAPI.ts</code> file:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPostcodeSuggestions</span>(<span class="hljs-params">partial: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">return</span> (<span class="hljs-keyword">await</span> getPostcodeAutocompleteResponse(partial))?.result;
}

<span class="hljs-keyword">const</span> getPostcodeAutocompleteResponse = <span class="hljs-keyword">async</span> (partial: <span class="hljs-built_in">string</span>) : <span class="hljs-built_in">Promise</span>&lt;PostcodeAutocompleteResponseDTO | <span class="hljs-literal">null</span>&gt; =&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span><span class="hljs-subst">${partial}</span>/autocomplete`</span>, {
    headers: {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
      <span class="hljs-string">'Accept'</span>: <span class="hljs-string">'application/json'</span>
    }
  }).then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> response.json();
  }).catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.error(error);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  })
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> isValidPostcode = <span class="hljs-keyword">async</span> (postcode: <span class="hljs-built_in">string</span>) : <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">boolean</span>&gt; =&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span><span class="hljs-subst">${postcode}</span>`</span>, {
    headers: {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
      <span class="hljs-string">'Accept'</span>: <span class="hljs-string">'application/json'</span>
    }
  }).then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> response.ok;
  }).catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.error(error);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
  })
}

<span class="hljs-keyword">interface</span> PostcodeAutocompleteResponseDTO {
  result: <span class="hljs-built_in">string</span>[]
}
</code></pre>
<p>We have added:</p>
<ol>
<li><p><code>getPostcodeSuggestions</code>, which calls <a target="_blank" href="http://Postcodes.io">Postcodes.io</a>'s autocomplete endpoint to get the autocomplete suggestions.</p>
</li>
<li><p><code>isValidPostcode</code>, which checks that the passed in postcode is a valid postcode</p>
</li>
</ol>
<p>Next, we'll create a new component with our autocomplete functionality, which will replace the postcode <code>TextField</code> element in the <code>Search</code> component.</p>
<p>Firstly, we'll need to install lodash.<br />Run the following:</p>
<pre><code class="lang-shell">npm install lodash
npm install @types/lodash
</code></pre>
<p>We then need to create the new component, <code>PostcodeAutocomplete.tsx</code> in the <code>components</code> directory, which should contain the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> {useCallback, useEffect} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> Box <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/Box'</span>;
<span class="hljs-keyword">import</span> TextField <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/TextField'</span>;
<span class="hljs-keyword">import</span> Autocomplete, {AutocompleteRenderInputParams} <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/Autocomplete'</span>;
<span class="hljs-keyword">import</span> LocationOnIcon <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/icons-material/LocationOn'</span>;
<span class="hljs-keyword">import</span> Grid <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/Grid'</span>;
<span class="hljs-keyword">import</span> debounce <span class="hljs-keyword">from</span> <span class="hljs-string">'lodash/debounce'</span>;
<span class="hljs-keyword">import</span> {getPostcodeSuggestions} <span class="hljs-keyword">from</span> <span class="hljs-string">"./PostcodesAPI"</span>;
<span class="hljs-keyword">import</span> {CircularProgress} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getOptionsAsync = (query: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">Promise</span>&lt;<span class="hljs-built_in">string</span>[]&gt; =&gt; {
    <span class="hljs-keyword">if</span>(!query) <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.resolve([]);
    <span class="hljs-keyword">return</span> getPostcodeSuggestions(query).then(<span class="hljs-function"><span class="hljs-params">suggestions</span> =&gt;</span> suggestions ? suggestions : []);
};

<span class="hljs-keyword">interface</span> PostcodeAutocompleteProps {
    setPostcode: <span class="hljs-function">(<span class="hljs-params">s: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>
    postcodeError: <span class="hljs-built_in">boolean</span>
    setPostcodeError: <span class="hljs-function">(<span class="hljs-params">b: <span class="hljs-built_in">boolean</span></span>) =&gt;</span> <span class="hljs-built_in">void</span>
    submitForm: <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">void</span>
    postcode: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">PostcodeAutocomplete</span>(<span class="hljs-params">{
                                                 setPostcode,
                                                 postcodeError,
                                                 setPostcodeError,
                                                 submitForm,
                                                 postcode
                                             }: PostcodeAutocompleteProps,</span>) </span>{
    <span class="hljs-keyword">const</span> [options, setOptions] = React.useState&lt;<span class="hljs-built_in">string</span>[]&gt;([]);
    <span class="hljs-keyword">const</span> [value, setValue] = React.useState&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>&gt;(postcode);
    <span class="hljs-keyword">const</span> [searchQuery, setSearchQuery] = React.useState&lt;<span class="hljs-built_in">string</span>&gt;(<span class="hljs-string">""</span>);
    <span class="hljs-keyword">const</span> [isLoading, setIsLoading] = React.useState&lt;<span class="hljs-built_in">boolean</span>&gt;(<span class="hljs-literal">false</span>);

    <span class="hljs-keyword">const</span> getOptionsDelayed = useCallback(
        debounce((query: <span class="hljs-built_in">string</span>, callback: <span class="hljs-function">(<span class="hljs-params">options: <span class="hljs-built_in">string</span>[]</span>) =&gt;</span> <span class="hljs-built_in">void</span>) =&gt; {
            setOptions([]);
            getOptionsAsync(query).then(callback);
        }, <span class="hljs-number">300</span>),
        []
    );

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

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

        getOptionsDelayed(searchQuery, <span class="hljs-function">(<span class="hljs-params">options: <span class="hljs-built_in">string</span>[]</span>) =&gt;</span> {
            setOptions(options);

            setIsLoading(<span class="hljs-literal">false</span>);
        });
    }, [searchQuery, getOptionsDelayed]);

    <span class="hljs-keyword">const</span> onChange = <span class="hljs-function">(<span class="hljs-params">event: unknown, value: <span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span></span>) =&gt;</span> {
        setValue(value);
        setPostcode(value);
        setPostcodeError(<span class="hljs-literal">false</span>);
    };

    <span class="hljs-keyword">const</span> onInputChange = <span class="hljs-function">(<span class="hljs-params">event: unknown, value: <span class="hljs-built_in">string</span></span>) =&gt;</span> {
        setSearchQuery(value);
        setPostcode(value);
        setPostcodeError(<span class="hljs-literal">false</span>);
    };

    <span class="hljs-keyword">const</span> keyPress = <span class="hljs-function">(<span class="hljs-params">e: React.KeyboardEvent&lt;HTMLDivElement&gt;</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (e.key == <span class="hljs-string">'Enter'</span>) {
            submitForm()
        }
    }

    <span class="hljs-keyword">const</span> renderInput = (
        params: AutocompleteRenderInputParams
    ): React.ReactNode =&gt; {
        <span class="hljs-keyword">return</span> (
            &lt;TextField {...params} label=<span class="hljs-string">"Postcode"</span> InputProps={{
                ...params.InputProps,
                endAdornment: (
                    &lt;React.Fragment&gt;
                        {isLoading ? &lt;CircularProgress color=<span class="hljs-string">"inherit"</span> size={<span class="hljs-number">20</span>}/&gt; : <span class="hljs-literal">null</span>}
                        {params.InputProps.endAdornment}
                    &lt;/React.Fragment&gt;
                ),
            }}
                       error={postcodeError}
                       helperText={postcodeError ? <span class="hljs-string">"Not a valid postcode"</span> : <span class="hljs-string">""</span>}
                       onKeyDown={keyPress}/&gt;
        );
    };

    <span class="hljs-keyword">return</span> (
        &lt;Autocomplete
            options={options}
            value={value}
            onChange={onChange}
            onInputChange={onInputChange}
            renderInput={renderInput}
            loading={isLoading}
            filterOptions={<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x}
            freeSolo
            sx={{minWidth: <span class="hljs-number">140</span>}}
            renderOption={<span class="hljs-function">(<span class="hljs-params">props, option</span>) =&gt;</span> {

                <span class="hljs-keyword">return</span> (
                    &lt;li {...props}&gt;
                        &lt;Grid container alignItems=<span class="hljs-string">"center"</span>&gt;
                            &lt;Grid item&gt;
                                &lt;Box
                                    component={LocationOnIcon}
                                    sx={{color: <span class="hljs-string">'text.secondary'</span>, mr: <span class="hljs-number">2</span>}}
                                /&gt;
                            &lt;/Grid&gt;
                            &lt;Grid item xs&gt;
                                {option}
                            &lt;/Grid&gt;
                        &lt;/Grid&gt;
                    &lt;/li&gt;
                );
            }}
        /&gt;
    );
}
</code></pre>
<p>Let's take a closer look at some of the key features in our code:</p>
<ol>
<li><p><strong>Debouncing with Lodash</strong>: We've integrated Lodash's debounce function to ensure that we only call <a target="_blank" href="http://Postcodes.io">Postcodes.io</a>'s API once the user has stopped typing for at least 300 milliseconds. This helps to minimize API calls and improve the overall user experience. We then use the data returned from the API to populate the options for our autocomplete dropdown.</p>
</li>
<li><p><strong>Handling validation</strong>: The parent component takes care of handling the logic to set the value and error state for validation purposes.</p>
</li>
<li><p><strong>Visual feedback for errors</strong>: If the error prop is set to true, we visually indicate that the user has entered an invalid postcode by highlighting the text field in red and displaying an error message.</p>
</li>
<li><p><strong>Autocomplete</strong>: We're using Material UI's Autocomplete with the results from <a target="_blank" href="http://Postcodes.io">Postcodes.io</a> to display a dropdown when the user starts typing a postcode.</p>
</li>
</ol>
<p>Next, we update the <code>Search.tsx</code> component to use our new <code>PostcodeAutocomplete</code> component. Replace the entire component with the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Search</span>(<span class="hljs-params">{initialPostcode, initialRadius}:<span class="hljs-built_in">any</span></span>) </span>{
    <span class="hljs-keyword">const</span> router = useRouter()
    <span class="hljs-keyword">const</span> [postcode, setPostcode] = useState&lt;<span class="hljs-built_in">string</span> | <span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>);
    <span class="hljs-keyword">const</span> [radius, setRadius] = useState&lt;<span class="hljs-built_in">number</span>&gt;(<span class="hljs-number">10</span>);
    <span class="hljs-keyword">const</span> [postcodeError, setPostcodeError] = useState(<span class="hljs-literal">false</span>);

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

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleSubmit</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">if</span> (!postcode) setPostcodeError(<span class="hljs-literal">true</span>)
        <span class="hljs-keyword">else</span> isValidPostcode(postcode)
            .then(<span class="hljs-function"><span class="hljs-params">isValid</span> =&gt;</span> {
                setPostcodeError(!isValid);
                <span class="hljs-keyword">if</span> (isValid) router.push(<span class="hljs-string">`/pass-rates?postcode=<span class="hljs-subst">${postcode}</span>&amp;radius=<span class="hljs-subst">${radius}</span>`</span>)
            });
    }

    <span class="hljs-keyword">return</span> (
        &lt;Paper variant=<span class="hljs-string">"outlined"</span> sx={{p: <span class="hljs-number">1</span>}}&gt;
            &lt;div style={{display: <span class="hljs-string">'flex'</span>, flexFlow: <span class="hljs-string">'row wrap'</span>}}&gt;
                &lt;Box sx={{m: <span class="hljs-number">1</span>, flex: <span class="hljs-number">1</span>}}&gt;
                    &lt;PostcodeAutocomplete postcode={postcode} setPostcode={setPostcode} postcodeError={postcodeError}
                                          setPostcodeError={setPostcodeError} submitForm={handleSubmit}/&gt;
                &lt;/Box&gt;
                &lt;FormControl sx={{m: <span class="hljs-number">1</span>, minWidth: <span class="hljs-number">120</span>, flex: <span class="hljs-number">1</span>}}&gt;
                    &lt;InputLabel id=<span class="hljs-string">"demo-simple-select-label"</span>&gt;Radius&lt;/InputLabel&gt;
                    &lt;Select
                        labelId=<span class="hljs-string">"demo-simple-select-label"</span>
                        id=<span class="hljs-string">"demo-simple-select"</span>
                        value={radius}
                        label=<span class="hljs-string">"Age"</span>
                        onChange={<span class="hljs-function"><span class="hljs-params">e</span> =&gt;</span> setRadius(e.target.value <span class="hljs-keyword">as</span> <span class="hljs-built_in">number</span>)}
                    &gt;
                        &lt;MenuItem value={<span class="hljs-number">5</span>}&gt;<span class="hljs-number">5</span> miles&lt;/MenuItem&gt;
                        &lt;MenuItem value={<span class="hljs-number">10</span>}&gt;<span class="hljs-number">10</span> miles&lt;/MenuItem&gt;
                        &lt;MenuItem value={<span class="hljs-number">20</span>}&gt;<span class="hljs-number">20</span> miles&lt;/MenuItem&gt;
                        &lt;MenuItem value={<span class="hljs-number">30</span>}&gt;<span class="hljs-number">30</span> miles&lt;/MenuItem&gt;
                    &lt;/Select&gt;
                &lt;/FormControl&gt;

                &lt;Box sx={{m: <span class="hljs-number">1</span>, mt: <span class="hljs-number">1.8</span>, flex: <span class="hljs-number">1</span>, display: <span class="hljs-string">'flex'</span>, flexDirection: <span class="hljs-string">'column'</span>}}&gt;
                    &lt;Button sx={{alignSelf: <span class="hljs-string">'center'</span>}} variant=<span class="hljs-string">"outlined"</span> onClick={handleSubmit}  size=<span class="hljs-string">"large"</span>&gt;Search&lt;/Button&gt;
                &lt;/Box&gt;

            &lt;/div&gt;


        &lt;/Paper&gt;
    );
}
</code></pre>
<p>What we've achieved with this change:</p>
<ol>
<li><p><strong>Handling Postcode Validation</strong>: To improve the accuracy of our user inputs, we've added a new state variable called postcodeError which tracks whether the entered postcode is valid or not. We pass this variable to the PostcodeAutocomplete component to provide visual feedback to the user.</p>
</li>
<li><p><strong>Validating Postcodes with PostcodesAPI</strong>: We now validate the postcode in the handleSubmit() method using the isValidPostcode() method in PostcodesAPI. This allows us to ensure that the user has entered a valid UK postcode before submitting the form.</p>
</li>
</ol>
<p>Now, let's test our changes!</p>
<p>Go to <a target="_blank" href="http://localhost:3000">http://localhost:3000</a> and try entering a postcode into the landing page. You should see something like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300325671/90088a58-187a-450f-88d9-80aaac021298.png" alt class="image--center mx-auto" /></p>
<p>Clicking on one of the postcodes will autofill the postcode for you.</p>
<h2 id="heading-step-4-add-search-functionality-to-the-pass-rates-page">Step 4: Add Search Functionality to the Pass Rates Page</h2>
<p>A nice extra feature would be to allow the user to search for a new postcode from the <code>/pass-rates</code> page without having to go back to the landing page.<br />As we've got the <code>Search</code> and <code>PostcodeAutocomplete</code> functionality in their own components we can easily add this to the <code>/pass-rates</code> page.<br />Replace the return block in <code>pass-rates.tsx</code> with the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">return</span> (
    &lt;&gt;
        &lt;Head&gt;
            &lt;meta charSet=<span class="hljs-string">"utf-8"</span>/&gt;
            &lt;title&gt;Best Driving Test Pass Rates Near Me - Pass Rates&lt;/title&gt;
            &lt;meta name=<span class="hljs-string">"description"</span> content={<span class="hljs-string">`Shows the latest pass rates for driving test centres near <span class="hljs-subst">${postcode}</span> within a <span class="hljs-subst">${radius}</span> mile radius`</span>}/&gt;
        &lt;/Head&gt;
        &lt;Box sx={{display: <span class="hljs-string">'flex'</span>, justifyContent: <span class="hljs-string">'center'</span>, mt: <span class="hljs-number">1</span>, mb: <span class="hljs-number">1</span>}}&gt;
            &lt;Search initialPostcode={postcode} initialRadius={radius}/&gt;
        &lt;/Box&gt;

        &lt;Typography variant=<span class="hljs-string">"h6"</span>&gt;Test Centres within {radius} mile radius <span class="hljs-keyword">of</span> {postcode}&lt;/Typography&gt;

        &lt;ResultsTable results={results}/&gt;
    &lt;/&gt;
);
</code></pre>
<p>The only thing we've changed here is that we've added our <code>Search</code> component.<br />The only difference between our use of <code>Search</code> here and in the landing page is that here we initially populate the postcode and radius fields with the previous search's parameters.</p>
<p>The <code>/pass-rates</code> page will now contain a search form at the top.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300346616/87d508fb-3adc-4851-95c1-4db824000ccd.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-display-nearest-test-centres-for-each-city">Display Nearest Test Centres for Each City</h1>
<p>One of the key requirements for our project is to allow users to select the best test centres near a city. We want to do this for two reasons:</p>
<ol>
<li><p>If the user wants to look for test centres around a specific city, they don't need to find a postcode for that city.</p>
</li>
<li><p>We can create static pages for each city, which will improve SEO (discussed in a later section) and help increase our chances of appearing at the top of Google's search results.</p>
</li>
</ol>
<h2 id="heading-step-1-calculating-the-nearest-test-centres-for-each-city">Step 1: Calculating the Nearest Test Centres for Each City</h2>
<p>To calculate the nearest test centres to each city, we need to know the latitude and longitude of each city. We can start by compiling a list of all the cities in the UK from <a target="_blank" href="https://en.wikipedia.org/wiki/List_of_cities_in_the_United_Kingdom">Wikipedia</a>.</p>
<p>Next, we can use Google Maps APIs to search for the latitude and longitude of each city. For instance, to obtain the location data for Birmingham, we send the following request:</p>
<pre><code class="lang-typescript">https:<span class="hljs-comment">//maps.googleapis.com/maps/api/place/findplacefromtext/json?input=Birmingham&amp;inputtype=textquery&amp;key=&lt;YOUR API KEY&gt;&amp;locationbias=ipbias&amp;fields=name,place_id</span>
</code></pre>
<p>The response contains the name of the city and its place ID:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"candidates"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham"</span>,
      <span class="hljs-attr">"place_id"</span>: <span class="hljs-string">"ChIJc3FBGy2UcEgRmHnurvD-gco"</span>
    }
  ],
  <span class="hljs-attr">"status"</span>: <span class="hljs-string">"OK"</span>
}
</code></pre>
<p>Using the place ID, we can then send a second request to retrieve the latitude and longitude of the city:</p>
<pre><code class="lang-typescript">https:<span class="hljs-comment">//maps.googleapis.com/maps/api/place/details/json?place_id=ChIJc3FBGy2UcEgRmHnurvD-gco&amp;key=&lt;YOUR API KEY&gt;&amp;fields=geometry</span>
</code></pre>
<p>The response contains the location data for the city:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"html_attributions"</span>: [],
  <span class="hljs-attr">"result"</span>: {
    <span class="hljs-attr">"geometry"</span>: {
      <span class="hljs-attr">"location"</span>: {
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.48624299999999</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.890401</span>
      },
      <span class="hljs-attr">"viewport"</span>: {
        <span class="hljs-attr">"northeast"</span>: {
          <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.60869933491674</span>,
          <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.709829372653529</span>
        },
        <span class="hljs-attr">"southwest"</span>: {
          <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.38599896742283</span>,
          <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-2.017433632448159</span>
        }
      }
    }
  },
  <span class="hljs-attr">"status"</span>: <span class="hljs-string">"OK"</span>
}
</code></pre>
<p>To calculate which test centres are near each city we first need to decide on what distance we consider "close". I considered anything within a 10-mile radius close.<br />For each city we need to go through each test centre and calculate the distance between the city and the test centre. If the distance is less than 10 miles then we add that test centre to the list of close test centres.<br />As before, once you have the data for each city you can use your preferred scripting language to extract the list of nearest test centres for each city.</p>
<p>You'll want to save each city with its closest test centres in individual JSON files. Each JSON file should be the name of the city, in a directory called <code>cities</code>.<br />E.g. you'll have a file called <code>birmingham.json</code> that looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham"</span>,
  <span class="hljs-attr">"postcode"</span>: <span class="hljs-string">"B4 7DL"</span>,
  <span class="hljs-attr">"testCentres"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham (Garretts Green)"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"43.6"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">4.767003603076919</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJldK_AICwcEgR2-SLap5P9Nw"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"Granby Ave, Garrett's Green, Birmingham B33 0SJ, United Kingdom"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Garretts Green Test Centre"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.4759864</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.7783739</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">3.7</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=15921438124472526043"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">58</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham (Kings Heath)"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"42.1"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">5.493458409608323</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJD1VCmci-cEgR77E87hMKPNg"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"955 Alcester Rd S, Birmingham B14 5JA, United Kingdom"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham Kings Heath Driving Test Centre"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.4067573</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.8873253</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">3.6</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=15581339891512685039"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">81</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham (Kingstanding)"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"36.7"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">4.000310419795066</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJHbuU-WOjcEgRtqaHZnumMto"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"205 Birdbrook Rd, Birmingham B44 9UL, United Kingdom"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham Kingstanding Driving Test Centre"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.5441402</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.890445</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">3.9</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=15722812298035177142"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">116</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham (Shirley)"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"52.2"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">6.287245676525351</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJWaNhJae5cEgRb7RLuTDHiaE"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"374 Stratford Rd, Shirley, Solihull B90 4AQ, United Kingdom"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Shirley Driving Test Centre"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.4051825</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.8225654</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">3.7</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=11640053723996861551"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">44</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham (South Yardley)"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"36.7"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">4.019107914150748</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJHUTeU0e6cEgRM9dvMwTt1sw"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"Driving Test Centre, Clay Ln, Birmingham B26 1EA, UK"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Driving Test Centre"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.45498449999999</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.8098702</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">0.0</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?q=Driving+Test+Centre&amp;ftid=0x4870ba4753de441d:0xccd6ed04336fd733"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">0</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham (Sutton Coldfield)"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"36.5"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">5.679981743492782</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJ7cilVwKlcEgRZ64gPe1-Icw"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"31-33 Birmingham Rd, Sutton Coldfield B72 1QE, United Kingdom"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"DVSA Theory Test Centre"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.558625</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.826348</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">3.0</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=14709177415366651495"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">23</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Birmingham (Wyndley)"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"54.5"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">4.687979677729207</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJZQwbgeSkcEgR6650dA7bWpo"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"110-116 Boldmere Rd, Boldmere, Sutton Coldfield B73 5UB, UK"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"110-116 Boldmere Rd"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.5471217</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-1.8411731</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">0.0</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?q=110-116+Boldmere+Rd,+Boldmere,+The+Royal+Town+of+Sutton+Coldfield,+Sutton+Coldfield+B73+5UB,+UK&amp;ftid=0x4870a4e4811b0c65:0x9a5adb0e7474aeeb"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">0</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Wednesbury"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"39.6"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">7.032517930498278</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJm07r2l-YcEgRVY5l3eqVSg8"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"Knowles St, Wednesbury WS10 9HN, United Kingdom"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"DVSA Driving Test Centre"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">52.5555689</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-2.0128767</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">2.8</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=1101857894814813781"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">85</span>
      }
    }
  ]
}
</code></pre>
<p>and another called <code>aberdeen.json</code> that looks like this:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Aberdeen"</span>,
  <span class="hljs-attr">"postcode"</span>: <span class="hljs-string">"AB24 5BA"</span>,
  <span class="hljs-attr">"testCentres"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Aberdeen North"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"57.3"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">2.4724386179137534</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJC3xww_sNhEgR7iXmPVZSNVA"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"8GT, Balgownie Rd, Bridge of Don, Aberdeen, United Kingdom"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"DVSA"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">57.1856443</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-2.0964023</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">4.1</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=5779616227159057902"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">7</span>
      }
    },
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Aberdeen South (Cove)"</span>,
      <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"64.5"</span>,
      <span class="hljs-attr">"distance"</span>: <span class="hljs-number">4.274066387101663</span>,
      <span class="hljs-attr">"mapDetails"</span>: {
        <span class="hljs-attr">"placeId"</span>: <span class="hljs-string">"ChIJTY7RiP4PhEgRDr0PT-qctJE"</span>,
        <span class="hljs-attr">"address"</span>: <span class="hljs-string">"Moss Rd, Aberdeen AB12 3GQ, United Kingdom"</span>,
        <span class="hljs-attr">"name"</span>: <span class="hljs-string">"DVSA Driving Test Centre"</span>,
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">57.0884979</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-2.1077442</span>,
        <span class="hljs-attr">"rating"</span>: <span class="hljs-number">4.3</span>,
        <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=10499189161470180622"</span>,
        <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">19</span>
      }
    }
  ]
}
</code></pre>
<h2 id="heading-step-2-create-a-page-to-show-all-cities">Step 2: Create a Page To Show All Cities</h2>
<p>The first thing we'll do is create a page that lists all cities. Later we'll use this page to navigate the user to each corresponding city's results page.</p>
<p>Before we create the page we'll create the necessary lib function to get all the city names. We do this in a similar way to how we got all the test centre data.<br />Create a file called <code>cities.ts</code> in the <code>lib</code> directory. This should contain the following:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;
<span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">'path'</span>;

<span class="hljs-keyword">const</span> citiesDirectory = path.join(process.cwd(), <span class="hljs-string">'cities'</span>);

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAllCityIds</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> fileNames = fs.readdirSync(citiesDirectory);

    <span class="hljs-keyword">return</span> fileNames.map(<span class="hljs-function">(<span class="hljs-params">fileName</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> fileName.replace(<span class="hljs-regexp">/\.json$/</span>, <span class="hljs-string">''</span>);
    });
}
</code></pre>
<p>Here we exposed a function called <code>getAllCityIds</code>, which returns the name of all cities, with each city name taken from the name of the JSON files that we saved in the <code>cities</code> directory in the previous step.</p>
<p>We can then create a page to show all the cities. Create a new file, <code>cities.tsx</code> under the <code>pages</code> directory. This should contain the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {Box, Typography} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material"</span>;
<span class="hljs-keyword">import</span> {getAllCityIds} <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/cities"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;


<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> cityIds = getAllCityIds();
    <span class="hljs-keyword">return</span> {
        props: {
            cityIds,
        },
    };
}

<span class="hljs-keyword">interface</span> CitiesProps {
    cityIds: <span class="hljs-built_in">string</span>[]
}


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">capitalizeFirstLetter</span>(<span class="hljs-params"><span class="hljs-built_in">string</span>: <span class="hljs-built_in">string</span></span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">string</span>.split(<span class="hljs-string">" "</span>).map(<span class="hljs-function"><span class="hljs-params">word</span> =&gt;</span> word.charAt(<span class="hljs-number">0</span>).toUpperCase() + word.slice(<span class="hljs-number">1</span>)).join(<span class="hljs-string">" "</span>)
}


<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cities</span>(<span class="hljs-params">{cityIds}: CitiesProps</span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Head&gt;
                &lt;meta charSet=<span class="hljs-string">"utf-8"</span>/&gt;
                &lt;title&gt;Best Driving Test Pass Rates Near Me - Pass Rates&lt;/title&gt;
                &lt;meta name=<span class="hljs-string">"description"</span>
                      content={<span class="hljs-string">`A list of all the cities in the UK, each linking to the latest pass rates for driving centres in that area.`</span>}/&gt;
            &lt;/Head&gt;
            &lt;Box sx={{display: <span class="hljs-string">'flex'</span>, flexDirection: <span class="hljs-string">'column'</span>}}&gt;
                &lt;Typography variant=<span class="hljs-string">"h2"</span>&gt;Cities&lt;/Typography&gt;
                &lt;ul&gt;
                    {cityIds.map(<span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> &lt;li key={id}&gt;{capitalizeFirstLetter(id)}&lt;/li&gt;)}
                &lt;/ul&gt;
            &lt;/Box&gt;
        &lt;/&gt;
    )
}
</code></pre>
<p>Here we load the city names using <code>getStaticProps</code> (so on build time), and render a list containing each city name.</p>
<p>Navigate to <a target="_blank" href="http://localhost:3000/cities"><code>http://localhost:3000/cities</code></a>. You will see a page containing all the city names.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300369428/22e41733-0f6b-4af9-99b0-13c4c121e9bb.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-step-3-create-a-page-for-each-city">Step 3: Create a Page For Each City</h2>
<p>Now that we have a list of all the cities, we need to create a page for each city.</p>
<p>To achieve this, we will create a static page for each city that we have JSON data for in our <code>cities</code> directory. These pages will have the URL format <code>/pass-rates/&lt;city name&gt;</code>.<br />This means that Google can easily index these pages and return them when someone searches for the best driving test centres near a city.<br />You might have seen that other websites do something similar to this, where they create static pages for commonly searched criteria to increase their chances of appearing at the top of Google's search results (amongst other benefits).<br />One example of this is <a target="_blank" href="http://crontab.guru"><code>crontab.guru</code></a> which has static pages for commonly used cron expressions, e.g. every 5 minutes, which can be found at <a target="_blank" href="https://crontab.guru/every-5-minutes.%EF%BF%BCIf">https://crontab.guru/every-5-minutes.<br />If</a> you search in Google for <code>cron job every 5 minutes</code>, then that page will be the first result.</p>
<p>Thankfully we don't have to create each city's page by hand. We can easily accomplish this by using Next.js's <code>getStaticPaths</code> function, which allows us to specify a list of paths that should be generated as pages during build time.</p>
<p>To start, we will need to generate a list of city names. In the <code>cities.ts</code> file located in the <code>lib</code> directory, add the following function:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAllCityPaths</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> fileNames = fs.readdirSync(citiesDirectory);

  <span class="hljs-keyword">return</span> fileNames.map(<span class="hljs-function">(<span class="hljs-params">fileName</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> {
      params: {
        id: fileName.replace(<span class="hljs-regexp">/\.json$/</span>, <span class="hljs-string">''</span>),
      },
    };
  });
}
</code></pre>
<p>This function reads the contents of the <code>cities</code> directory and returns an array of city names by removing the <code>.json</code> extension from the file names. To use <code>getStaticPaths</code> we need to have each city in the format above, which is each city has an object containing a <code>params</code> property, which contains an <code>id</code> property, which is set to the name of the page.</p>
<p>Next, we need to create a new page that will display the test centres for a particular city. Create a directory called <code>pass-rates</code> under the <code>pages</code> directory, and then create a file called <code>[id].tsx</code> under the <code>pass-rates</code> directory. It is important that the square brackets are included in the name, as that tells Next.js that we want the page name to be derived from the list passed to <code>getStaticPaths</code>.<br />The <code>[id].tsx</code> file should contain the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {getAllCityPaths} <span class="hljs-keyword">from</span> <span class="hljs-string">"../../lib/cities"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">City</span>(<span class="hljs-params">{id}: <span class="hljs-built_in">any</span></span>) </span>{
  <span class="hljs-keyword">return</span> &lt;&gt;
    {id}
  &lt;/&gt;;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> paths = getAllCityPaths();
  <span class="hljs-keyword">return</span> {
    paths,
    fallback: <span class="hljs-literal">false</span>,
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params">{params}: <span class="hljs-built_in">any</span></span>) </span>{
  <span class="hljs-keyword">const</span> id = params.id;
  <span class="hljs-keyword">return</span> {
    props: {
      id
    }
  }
}
</code></pre>
<p>This file exports two functions, <code>getStaticPaths</code> and <code>getStaticProps</code>. <code>getStaticPaths</code> is used to generate the paths for each city, and <code>getStaticProps</code> is used to fetch the data for each city based on the path. For now, we are just rendering the name of the city.</p>
<p>As we placed our <code>[id].tsx</code> file under the <code>pass-rates</code> directory, any generated page will be found at <code>/pass-rates/&lt;page&gt;</code>.</p>
<p>Let's test this out! Navigating to the generated page for Birmingham. Go to <a target="_blank" href="http://localhost:3000/pass-rates/birmingham"><code>http://localhost:3000/pass-rates/birmingham</code></a>. You should see a page showing the city name.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300395837/003cc579-e7db-4399-8037-b222d3657099.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-show-the-nearest-test-centres-for-each-city">Show the Nearest Test Centres For Each City</h3>
<p>Now that we have the generated pages, let's populate each of them with the nearest test centre data.</p>
<p>First, we'll need to add another function to <code>cities.ts</code> in the <code>lib</code> directory to get the data for each city. Add the following function:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCityData</span>(<span class="hljs-params">id: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">const</span> fullPath = path.join(citiesDirectory, <span class="hljs-string">`<span class="hljs-subst">${id}</span>.json`</span>);
  <span class="hljs-keyword">const</span> fileContents = <span class="hljs-built_in">JSON</span>.parse(fs.readFileSync(fullPath, <span class="hljs-string">'utf8'</span>));
  <span class="hljs-comment">// Combine the data with the id</span>
  <span class="hljs-keyword">return</span> {
    id,
    ...fileContents,
  };
}
</code></pre>
<p>This function parses the JSON from each city file and returns it, with the city name set as the <code>id</code> property.</p>
<p>Next, let's change the <code>getStaticProps</code> function to call <code>getCityData</code>, and then change the React component to render the results. Replace <code>[id].tsx</code> completely with the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {getAllCityPaths, getCityData} <span class="hljs-keyword">from</span> <span class="hljs-string">"../../lib/cities"</span>;
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> {Box} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/system"</span>;
<span class="hljs-keyword">import</span> ResultsTable <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/ResultsTable"</span>;
<span class="hljs-keyword">import</span> Search <span class="hljs-keyword">from</span> <span class="hljs-string">"../../components/Search"</span>;
<span class="hljs-keyword">import</span> {Typography} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">City</span>(<span class="hljs-params">{cityData}:<span class="hljs-built_in">any</span></span>) </span>{
  <span class="hljs-keyword">return</span> &lt;&gt;
    &lt;Head&gt;
      &lt;meta charSet=<span class="hljs-string">"utf-8"</span>/&gt;
      &lt;title&gt;Best Driving Test Pass Rates Near {cityData.name}&lt;/title&gt;
      &lt;meta name=<span class="hljs-string">"description"</span> content={<span class="hljs-string">`Shows the latest pass rates for driving test centres near <span class="hljs-subst">${cityData.name}</span> within a 10 mile radius`</span>}/&gt;
      &lt;link rel=<span class="hljs-string">"canonical"</span> href={<span class="hljs-string">`https://drivingpassrate.co.uk/pass-rates/<span class="hljs-subst">${cityData.name}</span>`</span>}/&gt;
    &lt;/Head&gt;
    &lt;Box sx={{display: <span class="hljs-string">'flex'</span>, justifyContent: <span class="hljs-string">'center'</span>, mt: <span class="hljs-number">1</span>, mb: <span class="hljs-number">1</span>}}&gt;
      &lt;Search initialPostcode={cityData?.postcode} initialRadius={<span class="hljs-number">10</span>}/&gt;
    &lt;/Box&gt;

    &lt;Typography variant=<span class="hljs-string">"h6"</span> &gt;Test Centres within <span class="hljs-number">10</span> mile radius <span class="hljs-keyword">of</span> {cityData.name}&lt;/Typography&gt;

    &lt;ResultsTable results={cityData.testCentres}/&gt;
  &lt;/&gt;;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticPaths</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">const</span> paths = getAllCityPaths();
  <span class="hljs-keyword">return</span> {
    paths,
    fallback: <span class="hljs-literal">false</span>,
  };
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params">{ params }:<span class="hljs-built_in">any</span></span>) </span>{
  <span class="hljs-keyword">const</span> cityData = getCityData(params.id);
  <span class="hljs-keyword">return</span> {
    props: {
      cityData
    }
  }
}
</code></pre>
<p>What we've changed:</p>
<ol>
<li><p>In <code>getStaticProps</code>, we are now calling <code>getCityData</code> to retrieve the test centre data for the selected city. We then add this data to the props.</p>
</li>
<li><p>We are rendering the <code>Search</code> component, which is used to search for a test centre by postcode. We are passing in the postcode for the selected city so that it is used as the default search term.</p>
</li>
<li><p>We are passing the test centre data to the <code>ResultsTable</code> component, which is responsible for rendering the table that shows the pass rates for each test centre. We are only passing the test centres that are nearest to the selected city.</p>
</li>
</ol>
<p>Going to <a target="_blank" href="http://localhost:3000/pass-rates/birmingham"><code>http://localhost:3000/pass-rates/birmingham</code></a> will now show a page very similar to the <code>/pass-rates</code> page:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300419032/109692ef-3650-42f9-81f8-745c6b6438e2.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-step-4-link-each-city-name-in-cities">Step 4: Link Each City Name in /cities</h2>
<p>The next step is to go back to the <code>/cities</code> page and add a button for each city that links to the city's individual page.</p>
<p>Open the <code>cities.tsx</code> file under <code>pages</code> and replace the React component with the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cities</span>(<span class="hljs-params">{cityIds}: CitiesProps</span>) </span>{
  <span class="hljs-keyword">return</span> (
          &lt;&gt;
            &lt;Head&gt;
              &lt;meta charSet=<span class="hljs-string">"utf-8"</span>/&gt;
              &lt;title&gt;Best Driving Test Pass Rates Near Me - Pass Rates&lt;/title&gt;
              &lt;meta name=<span class="hljs-string">"description"</span>
                    content={<span class="hljs-string">`A list of all the cities in the UK, each linking to the latest pass rates for driving centres in that area.`</span>}/&gt;
            &lt;/Head&gt;
            &lt;Box sx={{display: <span class="hljs-string">'flex'</span>, flexDirection: <span class="hljs-string">'column'</span>}}&gt;
              &lt;Typography variant=<span class="hljs-string">"h2"</span>&gt;Cities&lt;/Typography&gt;
              &lt;Box&gt;
                {cityIds.map(<span class="hljs-function"><span class="hljs-params">id</span> =&gt;</span> {
                  <span class="hljs-keyword">return</span> (
                          &lt;Link href={<span class="hljs-string">`/pass-rates/<span class="hljs-subst">${id}</span>`</span>} passHref key={id} style={{textDecoration: <span class="hljs-string">'none'</span>, color: <span class="hljs-string">'inherit'</span>}}&gt;
                            &lt;ListItemButton &gt;
                              &lt;ListItemText primary={capitalizeFirstLetter(id)}/&gt;
                            &lt;/ListItemButton&gt;
                          &lt;/Link&gt;)
                })
                }
              &lt;/Box&gt;
            &lt;/Box&gt;
          &lt;/&gt;
  )
}
</code></pre>
<p>This code changes each city name into a button that links to the corresponding city page.</p>
<p>Navigate to <a target="_blank" href="http://localhost:3000/cities"><code>http://localhost:3000/cities</code></a>. You will see the updated page with a button for each city.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300449287/86f1bfeb-dec8-44b7-b87b-e33051db31e7.png" alt class="image--center mx-auto" /></p>
<p>Clicking on any button will now bring you to that city's page.</p>
<p>You might notice that it's slow to render the city pages. This is because Next.js is rendering the pages on the fly at runtime when running the app in dev mode. When the application is deployed in production mode it will be much faster as each page will be statically rendered at build time.</p>
<h2 id="heading-step-5-add-a-link-to-cities-on-the-landing-page">Step 5: Add a Link to /cities on the Landing Page</h2>
<p>Finally, let's add a link to our <code>/cities</code> page from the landing page.</p>
<p>Open <code>index.tsx</code> and add the following code under the <code>&lt;Search/&gt;</code> element:</p>
<pre><code class="lang-typescript">&lt;Box sx={{m: <span class="hljs-number">2</span>, mt: <span class="hljs-number">1.8</span>, display: <span class="hljs-string">'flex'</span>, flexDirection: <span class="hljs-string">'column'</span>}}&gt;
  &lt;Link href={<span class="hljs-string">`/cities`</span>} passHref&gt;
    &lt;Button sx={{alignSelf: <span class="hljs-string">'center'</span>}} variant=<span class="hljs-string">"outlined"</span> component=<span class="hljs-string">"a"</span>&gt;See all cities&lt;/Button&gt;
  &lt;/Link&gt;
&lt;/Box&gt;
</code></pre>
<p>Visit <a target="_blank" href="http://localhost:3000"><code>http://localhost:3000</code></a> to see the updated landing page with the "See all cities" button:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300472557/e6cb8f24-ada4-475f-bf4b-c700b4c43584.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This completes part 2!<br />Let's review what we've done here:</p>
<ul>
<li><p>Added the ability to search by postcode and radius on the landing page and the pass rates page</p>
</li>
<li><p>Added autocomplete to the postcodes search field</p>
</li>
<li><p>Created a page for each city, showing the nearest test centres</p>
</li>
<li><p>Created a page listing all cities, and add a link to this page from the landing page</p>
</li>
</ul>
<p>You can find all the code used in this post in my repo <a target="_blank" href="https://github.com/Zinbo/public-data-demo">here</a>. As well, you can find a live version of the application we built <a target="_blank" href="https://drivingpassrate.co.uk/">here</a>.</p>
<p>Look out for part 3, where we'll be adding the finishing touches and deploying our application!</p>
<p><strong>Updated:</strong> Part 3 is now available. Read it <a target="_blank" href="/blog/web-app-public-data-part-3">here</a>.</p>
<p>Till next time!</p>
]]></content:encoded></item><item><title><![CDATA[Creating a Project Using Public Data for Fun and Profit: Part 1]]></title><description><![CDATA[In this post, we'll explore how to create an engaging website using public data that you can monetize and use to impress potential employers. You'll learn the benefits of developing a personal project using public data and follow an example of buildi...]]></description><link>https://stacktobasics.com/web-app-public-data-part-1</link><guid isPermaLink="true">https://stacktobasics.com/web-app-public-data-part-1</guid><category><![CDATA[React]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[SEO]]></category><category><![CDATA[Vercel]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Fri, 14 Apr 2023 15:08:16 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683300522076/1f23bc8f-91a0-419e-8e87-84d8f8f7646a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post, we'll explore how to create an engaging website using public data that you can monetize and use to impress potential employers. You'll learn the benefits of developing a personal project using public data and follow an example of building an app from scratch to deployment.</p>
<p>Throughout this blog, we'll walk through a step-by-step guide on how to build a website that allows users to search for driving test centres near them and show which centres have the best pass rates. We'll be using the UK Government's public data on driving test centre pass rates. I built this app because I'm learning to drive, and I wanted to know which test centre would give me the best chance of passing.</p>
<p>This will be a big post, so I've split it into 4 parts. The other parts will be coming soon!</p>
<p>In this part, we will cover:</p>
<ul>
<li><p>Setting up the initial project</p>
</li>
<li><p>Retrieving and formatting the public test centre data</p>
</li>
<li><p>Creating and styling the landing page</p>
</li>
</ul>
<p>All the code referenced in this post can be found in my repo <a target="_blank" href="https://github.com/Zinbo/public-data-demo">here</a>. As well, you can find a live version of the application we build <a target="_blank" href="https://drivingpassrate.co.uk/">here</a>.</p>
<h2 id="heading-why-should-i-create-my-own-personal-project">Why Should I Create My Own Personal Project?</h2>
<p>Developers create personal projects for several reasons, including showcasing their skills to potential employers, learning new technologies, having fun, and making a profit. Often, a project can fulfil all these reasons simultaneously. Building a personal project is one of the best ways to create a portfolio that can help you break into the software industry and land a job.</p>
<h2 id="heading-why-should-i-use-public-data">Why Should I Use Public Data?</h2>
<p>If you're looking for project ideas, public data can be an excellent source of inspiration. Public data is easy to obtain, it's free, and when combined with other data, it can provide substantial value to users. Even presenting data in a user-friendly format can be a significant win, considering that public data is often not presented this way.</p>
<p>Some examples of sources of public data are government and video games. For example, you can find health data, such as the number of vaccinations in England, on the UK Government's Coronavirus Dashboard <a target="_blank" href="https://coronavirus.data.gov.uk/details/vaccinations?areaType=nation&amp;areaName=England">here</a>. Another example is tax data, such as council tax data, which can be found <a target="_blank" href="https://www.gov.uk/government/statistical-data-sets/live-tables-on-council-tax">here</a>. Alternatively, you can find video game data, such as the World of Warcraft API or the PokéAPI for Pokémon.</p>
<p>You could also scrape data from websites if they don't offer a nice way to get their data, depending on their data usage policies, however, that deserves its own separate post. If you're interested in web scraping, you can find a tutorial <a target="_blank" href="https://www.freecodecamp.org/news/web-scraping-python-tutorial-how-to-scrape-data-from-a-website/">here</a> from FreeCodeCamp.</p>
<p>There are many other sources of public data, and the key is to choose data that interests you or solves a particular problem you're facing. That way you're much more likely to stick with the project!</p>
<h1 id="heading-the-requirements">The Requirements</h1>
<p>Before we begin, let's set out the requirements:</p>
<ul>
<li><p>The user can see the best driving test centres around them, based on their postcode.</p>
</li>
<li><p>The user can see the best driving test centres around popular cities.</p>
</li>
<li><p>The user can see the Google Reviews rating for each test centre.</p>
</li>
<li><p>The user can easily see where the test centre is located on Google Maps.</p>
</li>
</ul>
<h1 id="heading-set-up">Set Up</h1>
<p>For this application, we'll use Next.js as the framework and Material UI for the design. If you haven't used Next.js before, don't worry. Most of it will be clear if you've used React before.</p>
<p>I chose Next.js for its static site generation, which makes it lightning-fast. Since most of the data we use here is not dynamic, we can generate our pages at build time. This not only makes the application load extremely fast, but it's also good for SEO (which we'll cover later), which is crucial if we want to monetize our app later. It's also easy to host, as we'll see later.</p>
<p>Throughout this post, we'll explore how we can use Next.js for static site generation. However, you can read more about Next.js <a target="_blank" href="https://nextjs.org/docs/getting-started">here</a>.</p>
<p>To start, let's initialise our project:</p>
<pre><code class="lang-bash">&gt; npx create-next-app@latest --ts
</code></pre>
<p>Once completed, we can start up our application by running:</p>
<pre><code class="lang-bash">npm run dev
</code></pre>
<p>Opening <a target="_blank" href="http://localhost:3000">localhost:3000</a> should then show us the Next.js example page:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683299471511/cf798c6f-8ff7-48c2-8034-15155256ca12.png" alt class="image--center mx-auto" /></p>
<p>Your directory structure should look like this:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683299483292/8a08792c-06fe-4174-9fe5-bbb870ea2435.png" alt class="image--center mx-auto" /></p>
<p><img src="./resources/folder-structure-1.png" alt /></p>
<h1 id="heading-getting-the-public-data">Getting the Public Data</h1>
<h2 id="heading-step-1-acquiring-test-centre-data">Step 1: Acquiring Test Centre Data</h2>
<p>Please note that in this section I'll be deliberately brief on the details. This is partly because the point of this post isn't to copy what I did entirely, but rather to provide inspiration, but also because I don't want this post to be any longer than it needs to be. I leave the massaging of data as an exercise for the reader.</p>
<p>To build our web app, we first need to obtain data on the driving test centres. This data can be downloaded from the UK government's page on driving test pass rates, which can be found <a target="_blank" href="https://www.gov.uk/government/statistical-data-sets/car-driving-test-data-by-test-centre">here</a>. Specifically, we need the "Car pass rates by gender, month and test centre" document which provides the total pass rate for men and women for each driving test centre per year.</p>
<p>This document contains a tab for each year, with a record of the total pass rate for all men and women that year for each driving test centre.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683299511294/eaeadbe7-000f-49ba-bea0-80ab9d9d8529.png" alt class="image--center mx-auto" /></p>
<p>As an example, we can see that Aberdeen North had a total pass rate of 48.2% for the year 2022-23.<br />Once you have this data, you can use your preferred scripting language to extract a list of each test centre name with its corresponding total pass rate.</p>
<h2 id="heading-step-2-retrieving-test-centre-location-data">Step 2: Retrieving Test Centre Location Data</h2>
<p>In order to calculate the distance between the user and each test centre, we need to determine the location of each test centre. One way to accomplish this is by using the Google Maps Places API. While the API is not free, it offers 28,500 free map-loads per month, which should be more than sufficient for our needs.</p>
<p>To use the Places API, you need to obtain your own API key, which you can do by following the instructions provided <a target="_blank" href="https://developers.google.com/maps/documentation/javascript/get-api-key">here</a>.</p>
<p>We'll be using two endpoints of the Places API - <code>findplacefromtext</code> and <code>details</code>. These endpoints return the following fields:</p>
<ul>
<li><p><code>geometry</code>: The longitude and latitude of the test centre location.</p>
</li>
<li><p><code>rating</code>: The Google reviews rating for the location.</p>
</li>
<li><p><code>user_ratings_total</code>: The total number of reviews left for the location.</p>
</li>
<li><p><code>url</code>: The link to the location in Google Maps.</p>
</li>
</ul>
<p>We can use the <code>findplacefromtext</code> endpoint to search for our test centres by name and retrieve a list of candidates with their corresponding name and place ID. For example, when searching for the Mill Hill (London) driving test centre, you can query the following URL:</p>
<pre><code class="lang-typescript">https:<span class="hljs-comment">//maps.googleapis.com/maps/api/place/findplacefromtext/json?input=Mill%20Hill%20(London)%20Driving%20Test%20Centre&amp;inputtype=textquery&amp;key=&lt;YOUR API KEY&gt;&amp;locationbias=ipbias&amp;fields=name,place_id</span>
</code></pre>
<p>This query returns:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"candidates"</span>: [
    {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Mill Hill Driving Test Centre"</span>,
      <span class="hljs-attr">"place_id"</span>: <span class="hljs-string">"ChIJXdhrutoWdkgRh_OPEmlGwmc"</span>
    }
  ],
  <span class="hljs-attr">"status"</span>: <span class="hljs-string">"OK"</span>
}
</code></pre>
<p>Next, we can use the place ID to retrieve all the details about that test centre using the <code>details</code> endpoint. For example, you can query the following URL to retrieve details for the Mill Hill (London) driving test centre:</p>
<pre><code class="lang-typescript">https:<span class="hljs-comment">//maps.googleapis.com/maps/api/place/details/json?place_id=ChIJXdhrutoWdkgRh_OPEmlGwmc&amp;key=&lt;YOUR API KEY&gt;&amp;fields=geometry,rating,url,user_ratings_total</span>
</code></pre>
<p>This query returns:</p>
<pre><code class="lang-json">{
  <span class="hljs-attr">"html_attributions"</span>: [],
  <span class="hljs-attr">"result"</span>: {
    <span class="hljs-attr">"geometry"</span>: {
      <span class="hljs-attr">"location"</span>: {
        <span class="hljs-attr">"lat"</span>: <span class="hljs-number">51.6103168</span>,
        <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-0.2469914999999999</span>
      },
      <span class="hljs-attr">"viewport"</span>: {
        <span class="hljs-attr">"northeast"</span>: {
          <span class="hljs-attr">"lat"</span>: <span class="hljs-number">51.6115767802915</span>,
          <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-0.2457076697084979</span>
        },
        <span class="hljs-attr">"southwest"</span>: {
          <span class="hljs-attr">"lat"</span>: <span class="hljs-number">51.6088788197085</span>,
          <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-0.248405630291502</span>
        }
      }
    },
    <span class="hljs-attr">"rating"</span>: <span class="hljs-number">4.7</span>,
    <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=7476615748485378951"</span>,
    <span class="hljs-attr">"user_ratings_total"</span>: <span class="hljs-number">350</span>
  },
  <span class="hljs-attr">"status"</span>: <span class="hljs-string">"OK"</span>
}
</code></pre>
<p>For test centres where I got more than one candidate I hand-picked the right one - however, you could write your own algorithm to programmatically figure this out.</p>
<h2 id="heading-step-3-combining-the-data">Step 3: Combining the Data</h2>
<p>We need to combine the data we collected earlier to create a list of all the test centres. We'll include their name, pass rate, lat long, rating, number of ratings, and the Google Maps URL in a JSON file. Let's save this file in a directory called <code>testcentres</code> in the root directory.<br />Here's an example of what the JSON file should look like:</p>
<pre><code class="lang-json">[
  {
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Aberdeen North"</span>,
    <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"57.3"</span>,
    <span class="hljs-attr">"mapDetails"</span>: {
      <span class="hljs-attr">"lat"</span>: <span class="hljs-number">57.1856443</span>,
      <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-2.0964023</span>,
      <span class="hljs-attr">"rating"</span>: <span class="hljs-number">4.1</span>,
      <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=5779616227159057902"</span>,
      <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">7</span>
    }
  },
  {
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Aberdeen South (Cove)"</span>,
    <span class="hljs-attr">"passRate"</span>: <span class="hljs-string">"64.5"</span>,
    <span class="hljs-attr">"mapDetails"</span>: {
      <span class="hljs-attr">"lat"</span>: <span class="hljs-number">57.0884979</span>,
      <span class="hljs-attr">"lng"</span>: <span class="hljs-number">-2.1077442</span>,
      <span class="hljs-attr">"rating"</span>: <span class="hljs-number">4.3</span>,
      <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://maps.google.com/?cid=10499189161470180622"</span>,
      <span class="hljs-attr">"userRatingsTotal"</span>: <span class="hljs-number">19</span>
    }
  },
  ...
]
</code></pre>
<h1 id="heading-creating-the-landing-page">Creating the Landing Page</h1>
<p>In this section, we'll start creating our landing page.</p>
<h2 id="heading-step-1-install-the-necessary-npm-packages">Step 1: Install the Necessary NPM Packages</h2>
<p>We will need to install both <a target="_blank" href="https://mui.com/">Material UI</a> and <a target="_blank" href="https://emotion.sh/docs/introduction">Emotion</a>:</p>
<pre><code class="lang-bash">&gt; npm install @mui/material
&gt; npm install @mui/icons-material
&gt; npm install @emotion/react
&gt; npm install @emotion/styled
</code></pre>
<h2 id="heading-step-2-style-the-landing-page">Step 2: Style the Landing Page</h2>
<p>Next, we'll update our <code>_app.tsx</code> file and <code>index.tsx</code> file to use Material UI components and styles. The changes to <code>_app.tsx</code> will apply our Material UI theme and our Flexbox styling to all pages.</p>
<p>Replace <code>_app.tsx</code> with the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-string">'../styles/globals.css'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> {AppProps} <span class="hljs-keyword">from</span> <span class="hljs-string">'next/app'</span>
<span class="hljs-keyword">import</span> {Container, createTheme, ThemeProvider} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material"</span>;
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> Box <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material/Box"</span>;

<span class="hljs-keyword">const</span> theme = createTheme({
    typography: {
        fontFamily: [
            <span class="hljs-string">"-apple-system"</span>, <span class="hljs-string">"BlinkMacSystemFont"</span>, <span class="hljs-string">"Segoe UI"</span>, <span class="hljs-string">"Roboto"</span>, <span class="hljs-string">"Oxygen"</span>, <span class="hljs-string">"Ubuntu"</span>, <span class="hljs-string">"Cantarell"</span>, <span class="hljs-string">"Fira Sans"</span>, <span class="hljs-string">"Droid Sans"</span>, <span class="hljs-string">"Helvetica Neue"</span>, <span class="hljs-string">"sans-serif"</span>
        ].join(<span class="hljs-string">","</span>)
    },
    palette: {
        primary: {
            light: <span class="hljs-string">'#6985ff'</span>,
            main: <span class="hljs-string">'#008000'</span>,
            dark: <span class="hljs-string">'#0031c3'</span>,
            contrastText: <span class="hljs-string">'#ffffff'</span>,
        },
        secondary: {
            light: <span class="hljs-string">'#9fcfff'</span>,
            main: <span class="hljs-string">'#689eff'</span>,
            dark: <span class="hljs-string">'#2770cb'</span>,
            contrastText: <span class="hljs-string">'#ffffff'</span>,
        },
    },
});

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">MyApp</span>(<span class="hljs-params">{Component, pageProps}: AppProps</span>) </span>{
    <span class="hljs-keyword">return</span> &lt;&gt;
        &lt;Head&gt;
            &lt;title&gt;Best Driving Test Pass Rates Near Me&lt;/title&gt;
        &lt;/Head&gt;
        &lt;ThemeProvider theme={theme}&gt;
            &lt;Box sx={{display: <span class="hljs-string">'flex'</span>, flexDirection: <span class="hljs-string">'column'</span>, minHeight: <span class="hljs-string">'100vh'</span>}}&gt;
                &lt;Container fixed sx={{display: <span class="hljs-string">'flex'</span>, flex: <span class="hljs-number">1</span>, flexDirection: <span class="hljs-string">'column'</span>}}&gt;
                    &lt;Component {...pageProps} /&gt;
                &lt;/Container&gt;
            &lt;/Box&gt;
        &lt;/ThemeProvider&gt;
    &lt;/&gt;
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> MyApp
</code></pre>
<p>Replace <code>index.tsx</code> with the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> {NextPage} <span class="hljs-keyword">from</span> <span class="hljs-string">'next'</span>
<span class="hljs-keyword">import</span> styles <span class="hljs-keyword">from</span> <span class="hljs-string">'../styles/Home.module.css'</span>
<span class="hljs-keyword">import</span> {Typography} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material"</span>;

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

  <span class="hljs-keyword">return</span> (
          &lt;div className={styles.content}&gt;
            &lt;div style={{display: <span class="hljs-string">'flex'</span>}}&gt;
              &lt;Typography variant=<span class="hljs-string">"h2"</span> sx={{m: <span class="hljs-number">1</span>, fontWeight: <span class="hljs-string">'bold'</span>}}&gt;Find The Best &lt;span
                      style={{color: <span class="hljs-string">'green'</span>}}&gt;Pass Rates&lt;<span class="hljs-regexp">/span&gt; Near You &lt;br/</span>&gt;🚗✅&lt;/Typography&gt;
            &lt;/div&gt;
          &lt;/div&gt;
  )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Home
</code></pre>
<p>We then need to change the <code>Home.modules.css</code> file. This file will contain specific styles for our landing page. Replace it with the following:</p>
<pre><code class="lang-css"><span class="hljs-selector-class">.container</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span> <span class="hljs-number">2rem</span>;
}

<span class="hljs-selector-class">.app</span> {
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
  <span class="hljs-attribute">text-align</span>: center;
}

<span class="hljs-selector-class">.content</span> {
  <span class="hljs-attribute">text-align</span>: center;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">align-items</span>: center;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">font-size</span>: <span class="hljs-built_in">calc</span>(<span class="hljs-number">10px</span> + <span class="hljs-number">2vmin</span>);
}

<span class="hljs-selector-class">.main</span> {
  <span class="hljs-attribute">min-height</span>: <span class="hljs-number">100vh</span>;
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">4rem</span> <span class="hljs-number">0</span>;
  <span class="hljs-attribute">flex</span>: <span class="hljs-number">1</span>;
  <span class="hljs-attribute">display</span>: flex;
  <span class="hljs-attribute">flex-direction</span>: column;
  <span class="hljs-attribute">justify-content</span>: center;
  <span class="hljs-attribute">align-items</span>: center;
}
</code></pre>
<p>Finally, we need to change the <code>globals.css</code> file. This file will contain global styles that will be applied to all pages. Replace it with the following:</p>
<pre><code class="lang-css"><span class="hljs-selector-tag">html</span>,
<span class="hljs-selector-tag">body</span> {
  <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
  <span class="hljs-attribute">font-family</span>: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
  Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}

* {
  <span class="hljs-attribute">box-sizing</span>: border-box;
}
</code></pre>
<p>You can also delete the <code>api</code> folder and <code>hello.ts</code> file inside of it, as we will not be creating an API for this application.</p>
<p>Run <code>npm run dev</code> and navigate to <a target="_blank" href="http://localhost:3000"><code>localhost:3000</code></a>. You will now see our styled landing page.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683299541866/729e4af3-08ae-4d1b-a029-b4f0c8aaf3d9.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-showing-the-nearest-test-centres-for-a-specific-postcode">Showing the Nearest Test Centres for a Specific Postcode</h1>
<p>One of our main requirements is to enable users to view the best driving test centres in their area, based on their postcode. To achieve this, we need to find the nearest test centres based on a given postcode and display the results on a dedicated web page.</p>
<h2 id="heading-step-1-create-a-function-to-get-location-data-by-postcode">Step 1: Create a Function To Get Location Data By Postcode</h2>
<p>To do this, we can use <a target="_blank" href="http://postcodes.io">postcodes.io</a>, an open-source postcode and geolocation API for the UK, to find the nearest test centres based on a postcode. <a target="_blank" href="http://Postcodes.io">Postcodes.io</a> API provides us with approximate longitude and latitude data based on a postcode, and vice versa. It even offers autocomplete data for partial postcodes, which we'll use later.</p>
<p>To start, we'll define a file that contains all the logic to connect to <a target="_blank" href="http://postcodes.io">postcodes.io</a> API and gets the location data for a specific postcode.</p>
<p>We'll create a new directory called <code>api</code> and a new file called <code>PostcodesAPI.ts</code> inside it, which should contain the following:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> LongLat <span class="hljs-keyword">from</span> <span class="hljs-string">"./LongLat"</span>;

<span class="hljs-keyword">const</span> baseUrl = <span class="hljs-string">'https://api.postcodes.io/postcodes/'</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLongLatFromPostcode</span>(<span class="hljs-params">postcode: <span class="hljs-built_in">string</span></span>) </span>{
  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> getPostcodeResponse(postcode);
  <span class="hljs-keyword">if</span> (!response) <span class="hljs-keyword">return</span>;
  <span class="hljs-keyword">return</span> response.result;
}

<span class="hljs-keyword">const</span> getPostcodeResponse = <span class="hljs-keyword">async</span> (postcode: <span class="hljs-built_in">string</span>): <span class="hljs-built_in">Promise</span>&lt;PostcodeResponseDTO | <span class="hljs-literal">null</span>&gt; =&gt; {
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`<span class="hljs-subst">${baseUrl}</span><span class="hljs-subst">${postcode}</span>`</span>, {
    headers: {
      <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
      <span class="hljs-string">'Accept'</span>: <span class="hljs-string">'application/json'</span>
    }
  }).then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> {
    <span class="hljs-keyword">return</span> response.json();
  }).catch(<span class="hljs-function"><span class="hljs-params">error</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.error(error);
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  })
}

<span class="hljs-keyword">interface</span> PostcodeResponseDTO {
    result: LongLat
}
</code></pre>
<p>Next, we'll create a new directory called <code>lib</code> and add a file called <code>LongLat.ts</code>. This file contains the following code:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-keyword">interface</span> LongLat {
    longitude: <span class="hljs-built_in">number</span>
    latitude: <span class="hljs-built_in">number</span>
}
</code></pre>
<h2 id="heading-step-2-create-a-function-to-get-test-centre-data">Step 2: Create a Function To Get Test Centre Data</h2>
<p>We need to create a function that returns the list of objects from the <code>testcentres.json</code> file that we generated earlier.</p>
<p>Create a new file inside the <code>lib</code> directory called <code>testcentres.ts</code> containing the following:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">import</span> path <span class="hljs-keyword">from</span> <span class="hljs-string">"path"</span>;
<span class="hljs-keyword">import</span> fs <span class="hljs-keyword">from</span> <span class="hljs-string">"fs"</span>;
<span class="hljs-keyword">import</span> {TestCentre} <span class="hljs-keyword">from</span> <span class="hljs-string">"./TestCentre"</span>;

<span class="hljs-keyword">const</span> directory = path.join(process.cwd(), <span class="hljs-string">'testcentres'</span>);

<span class="hljs-keyword">export</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTestCentres</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> fullPath = path.join(directory, <span class="hljs-string">`testcentres.json`</span>);
    <span class="hljs-keyword">const</span> testCentres : TestCentre[] = <span class="hljs-built_in">JSON</span>.parse(fs.readFileSync(fullPath, <span class="hljs-string">'utf8'</span>));
    <span class="hljs-keyword">return</span> testCentres;
}
</code></pre>
<p>We also need to create another file called <code>TestCentre.ts</code>, which contains the following:</p>
<pre><code class="lang-ts"><span class="hljs-keyword">interface</span> MapDetails {
    lat: <span class="hljs-built_in">number</span>
    lng: <span class="hljs-built_in">number</span>
    rating: <span class="hljs-built_in">number</span>
    url: <span class="hljs-built_in">string</span>
    userRatingsTotal: <span class="hljs-built_in">number</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> TestCentre {
    name: <span class="hljs-built_in">string</span>
    passRate: <span class="hljs-built_in">string</span>
    mapDetails: MapDetails
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> TestCentreWithDistance <span class="hljs-keyword">extends</span> TestCentre {
    distance: <span class="hljs-built_in">number</span>
}
</code></pre>
<h2 id="heading-step-3-create-a-page-to-show-results">Step 3: Create A Page to Show Results</h2>
<p>Now, we can create a new page called <code>pass-rates.tsx</code> in the <code>pages</code> directory. This will display the details of all test centres nearest to the supplied postcode, within the supplied radius.<br />The <code>pass-rates.tsx</code> file should contain the following code:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> React, {useEffect} <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> {useRouter} <span class="hljs-keyword">from</span> <span class="hljs-string">'next/router'</span>
<span class="hljs-keyword">import</span> Head <span class="hljs-keyword">from</span> <span class="hljs-string">"next/head"</span>;
<span class="hljs-keyword">import</span> {Typography} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material"</span>;

<span class="hljs-keyword">import</span> {getLongLatFromPostcode} <span class="hljs-keyword">from</span> <span class="hljs-string">"../api/PostcodesAPI"</span>;
<span class="hljs-keyword">import</span> LongLat <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/LongLat"</span>;
<span class="hljs-keyword">import</span> {getTestCentres} <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/testcentres"</span>;
<span class="hljs-keyword">import</span> {TestCentre, TestCentreWithDistance} <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/TestCentre"</span>;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStaticProps</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">const</span> testCentres = getTestCentres();
    <span class="hljs-keyword">return</span> {
        props: {
            testCentres
        },
    };
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTestCentresWithinRadius</span>(<span class="hljs-params">usersLongLat: LongLat, radius: <span class="hljs-built_in">number</span>, testCentres: TestCentre[]</span>) : <span class="hljs-title">TestCentreWithDistance</span>[] </span>{
    <span class="hljs-keyword">return</span> testCentres.flatMap(<span class="hljs-function"><span class="hljs-params">centre</span> =&gt;</span> {
        <span class="hljs-keyword">const</span> distance = getDistanceFromLatLon(usersLongLat.latitude, usersLongLat.longitude, centre.mapDetails.lat, centre.mapDetails.lng);
        <span class="hljs-keyword">if</span>(distance &gt; radius) <span class="hljs-keyword">return</span> [];
        <span class="hljs-keyword">const</span> centreWithDistance = structuredClone(centre) <span class="hljs-keyword">as</span> TestCentreWithDistance;
        centreWithDistance.distance = distance;
        <span class="hljs-keyword">return</span> centreWithDistance;
    })
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getDistanceFromLatLon</span>(<span class="hljs-params">lat1: <span class="hljs-built_in">number</span>, lon1: <span class="hljs-built_in">number</span>, lat2: <span class="hljs-built_in">number</span>, lon2: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">number</span> </span>{
    <span class="hljs-keyword">const</span> R = <span class="hljs-number">6371</span>; <span class="hljs-comment">// Radius of the earth in km</span>
    <span class="hljs-keyword">const</span> dLat = deg2rad(lat2 - lat1);  <span class="hljs-comment">// deg2rad below</span>
    <span class="hljs-keyword">const</span> dLon = deg2rad(lon2 - lon1);
    <span class="hljs-keyword">const</span> a =
        <span class="hljs-built_in">Math</span>.sin(dLat / <span class="hljs-number">2</span>) * <span class="hljs-built_in">Math</span>.sin(dLat / <span class="hljs-number">2</span>) +
        <span class="hljs-built_in">Math</span>.cos(deg2rad(lat1)) * <span class="hljs-built_in">Math</span>.cos(deg2rad(lat2)) *
        <span class="hljs-built_in">Math</span>.sin(dLon / <span class="hljs-number">2</span>) * <span class="hljs-built_in">Math</span>.sin(dLon / <span class="hljs-number">2</span>);
    <span class="hljs-keyword">const</span> c = <span class="hljs-number">2</span> * <span class="hljs-built_in">Math</span>.atan2(<span class="hljs-built_in">Math</span>.sqrt(a), <span class="hljs-built_in">Math</span>.sqrt(<span class="hljs-number">1</span> - a));
    <span class="hljs-keyword">return</span> (R * c) * <span class="hljs-number">0.62137119</span>; <span class="hljs-comment">// Convert distance from km to miles</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deg2rad</span>(<span class="hljs-params">deg: <span class="hljs-built_in">number</span></span>): <span class="hljs-title">number</span> </span>{
    <span class="hljs-keyword">return</span> deg * (<span class="hljs-built_in">Math</span>.PI / <span class="hljs-number">180</span>);
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Results</span>(<span class="hljs-params">{testCentres}:<span class="hljs-built_in">any</span></span>) </span>{
    <span class="hljs-keyword">const</span> router = useRouter()
    <span class="hljs-keyword">const</span> {postcode, radius} = router.query;
    <span class="hljs-keyword">const</span> [results, setResults] = React.useState&lt;TestCentreWithDistance[]&gt;([]);

    <span class="hljs-keyword">const</span> getResults = <span class="hljs-keyword">async</span> () =&gt; {
        <span class="hljs-keyword">if</span> (!postcode) <span class="hljs-keyword">return</span>;
        <span class="hljs-keyword">if</span> (!radius) <span class="hljs-keyword">return</span>;
        <span class="hljs-keyword">const</span> usersLongLat = <span class="hljs-keyword">await</span> getLongLatFromPostcode(postcode <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>);
        <span class="hljs-keyword">if</span> (!usersLongLat) <span class="hljs-keyword">return</span>;

        setResults(getTestCentresWithinRadius(usersLongLat, <span class="hljs-built_in">parseInt</span>(radius <span class="hljs-keyword">as</span> <span class="hljs-built_in">string</span>), testCentres));
    }

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


    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;Head&gt;
                &lt;meta charSet=<span class="hljs-string">"utf-8"</span>/&gt;
                &lt;title&gt;Best Driving Test Pass Rates Near Me - Pass Rates&lt;/title&gt;
                &lt;meta name=<span class="hljs-string">"description"</span> content={<span class="hljs-string">`Shows the latest pass rates for driving test centres near <span class="hljs-subst">${postcode}</span> within a <span class="hljs-subst">${radius}</span> mile radius`</span>}/&gt;
            &lt;/Head&gt;

            &lt;Typography variant=<span class="hljs-string">"h6"</span>&gt;Test Centres within {radius} mile radius <span class="hljs-keyword">of</span> {postcode}&lt;/Typography&gt;
            &lt;ul&gt;

            {
                results.map(
                    <span class="hljs-function"><span class="hljs-params">r</span> =&gt;</span> &lt;li key={r.name}&gt;Name: {r.name}, Distance: {r.distance}, Rating: {r.mapDetails.rating}⭐ ({r.mapDetails.userRatingsTotal} reviews), mapUrl: {r.mapDetails.url}&lt;/li&gt;
                )
            }
            &lt;/ul&gt;

        &lt;/&gt;
    );
}
</code></pre>
<p>Let's go through in detail what is happening here.<br />To get the supplied postcode and radius we use Next.js's Router in the line <code>const {postcode, radius} = router.query</code>. This reads the query parameters from the URL and copies them into parameters with the same name.<br />For example, if the URL is <a target="_blank" href="http://localhost:3000/pass-rates?postcode=NG16JX&amp;radius=10"><code>http://localhost:3000/pass-rates?postcode=NG16JX&amp;radius=10</code></a>, then the <code>postcode</code> variable will be set to NG16JX, and the <code>radius</code> variable will be set to <code>10</code>.</p>
<p>To get the test centre data we use the function we defined in the previous step, <code>getTestCentres</code>. We use this in combination with Next.js's <code>getStaticProps</code> function. <code>getStaticProps</code> is a special function that is run when the page is statically generated at build time. That means the test centre's data is already loaded before a user loads the page, which drastically helps speed up the rendering time for a page. You can read more about this <a target="_blank" href="https://nextjs.org/learn/basics/data-fetching/with-data">here</a>.</p>
<p>To know which test centres are within the specified radius we first calculate the latitude and longitude of the specified postcode by calling the <code>getLongLatFromPostcode</code> function we defined earlier. Using the longitude and latitude, along with the radius and all the test centre data, we calculate the distance between the specified postcode and each test centre using the <a target="_blank" href="https://en.wikipedia.org/wiki/Haversine_formula">Haversine formula</a>. We then convert that distance into miles (as our radius measurement is miles). We then use this distance to filter out any test centre with a distance greater than the specified radius.</p>
<p>Finally, we display all the results in a list.</p>
<p>We can now navigate to the <code>/pass-rates</code> page and pass in a postcode and a radius. E.g. to find all test centres within a 10-mile radius of Nottingham we can navigate to: <a target="_blank" href="http://localhost:3000/pass-rates?postcode=NG16JX&amp;radius=10">http://localhost:3000/pass-rates?postcode=NG16JX&amp;radius=10</a></p>
<p>Which will show us a page will all the test centres within 10 miles of Nottingham.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683299561793/bf5ff534-2d2c-4cbf-9210-70b81f89a766.png" alt class="image--center mx-auto" /></p>
<p>Try it out with your own postcode!</p>
<h2 id="heading-step-4-showing-the-results-as-a-table">Step 4: Showing the Results as a Table</h2>
<p>While our <code>/pass-rates</code> page does show the correct results, it's not very pretty. Let's use a table instead to render the data.</p>
<p>To do this, let's add a new file <code>ResultsTable.tsx</code> to our <code>components</code> directory, which should contain the following:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> * <span class="hljs-keyword">as</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>;
<span class="hljs-keyword">import</span> Box <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/Box'</span>;
<span class="hljs-keyword">import</span> Table <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/Table'</span>;
<span class="hljs-keyword">import</span> TableBody <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/TableBody'</span>;
<span class="hljs-keyword">import</span> TableCell <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/TableCell'</span>;
<span class="hljs-keyword">import</span> TableContainer <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/TableContainer'</span>;
<span class="hljs-keyword">import</span> TableHead <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/TableHead'</span>;
<span class="hljs-keyword">import</span> TableRow <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/TableRow'</span>;
<span class="hljs-keyword">import</span> TableSortLabel <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/TableSortLabel'</span>;
<span class="hljs-keyword">import</span> Paper <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/material/Paper'</span>;
<span class="hljs-keyword">import</span> {visuallyHidden} <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/utils'</span>;
<span class="hljs-keyword">import</span> {TestCentreWithDistance} <span class="hljs-keyword">from</span> <span class="hljs-string">"../lib/TestCentre"</span>;
<span class="hljs-keyword">import</span> {Tooltip} <span class="hljs-keyword">from</span> <span class="hljs-string">"@mui/material"</span>;
<span class="hljs-keyword">import</span> HelpIcon <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/icons-material/HelpOutline'</span>;
<span class="hljs-keyword">import</span> MapIcon <span class="hljs-keyword">from</span> <span class="hljs-string">'@mui/icons-material/Map'</span>;

<span class="hljs-keyword">interface</span> Data {
    name: <span class="hljs-built_in">string</span>
    passRate: <span class="hljs-built_in">number</span>
    rating: <span class="hljs-built_in">string</span>
    distance: <span class="hljs-built_in">number</span>
    mapURL: <span class="hljs-built_in">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">descendingComparator</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">a: T, b: T, orderBy: keyof T</span>) </span>{
    <span class="hljs-keyword">if</span> (b[orderBy] &lt; a[orderBy]) {
        <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    }
    <span class="hljs-keyword">if</span> (b[orderBy] &gt; a[orderBy]) {
        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
    }
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}

<span class="hljs-keyword">type</span> Order = <span class="hljs-string">'asc'</span> | <span class="hljs-string">'desc'</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getComparator</span>&lt;<span class="hljs-title">Key</span> <span class="hljs-title">extends</span> <span class="hljs-title">keyof</span> <span class="hljs-title">any</span>&gt;(<span class="hljs-params">
    order: Order,
    orderBy: Key,
</span>): (<span class="hljs-params">
    a: { [key <span class="hljs-keyword">in</span> Key]: <span class="hljs-built_in">number</span> | <span class="hljs-built_in">string</span> },
    b: { [key <span class="hljs-keyword">in</span> Key]: <span class="hljs-built_in">number</span> | <span class="hljs-built_in">string</span> },
</span>) =&gt; <span class="hljs-title">number</span> </span>{
    <span class="hljs-keyword">return</span> order === <span class="hljs-string">'desc'</span>
        ? <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> descendingComparator(a, b, orderBy)
        : <span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> -descendingComparator(a, b, orderBy);
}

<span class="hljs-comment">// This method is created for cross-browser compatibility, if you don't</span>
<span class="hljs-comment">// need to support IE11, you can use Array.prototype.sort() directly</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">stableSort</span>&lt;<span class="hljs-title">T</span>&gt;(<span class="hljs-params">array: <span class="hljs-keyword">readonly</span> T[], comparator: (a: T, b: T) =&gt; <span class="hljs-built_in">number</span></span>) </span>{
    <span class="hljs-keyword">const</span> stabilizedThis = array.map(<span class="hljs-function">(<span class="hljs-params">el, index</span>) =&gt;</span> [el, index] <span class="hljs-keyword">as</span> [T, <span class="hljs-built_in">number</span>]);
    stabilizedThis.sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> order = comparator(a[<span class="hljs-number">0</span>], b[<span class="hljs-number">0</span>]);
        <span class="hljs-keyword">if</span> (order !== <span class="hljs-number">0</span>) {
            <span class="hljs-keyword">return</span> order;
        }
        <span class="hljs-keyword">return</span> a[<span class="hljs-number">1</span>] - b[<span class="hljs-number">1</span>];
    });
    <span class="hljs-keyword">return</span> stabilizedThis.map(<span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span> el[<span class="hljs-number">0</span>]);
}

<span class="hljs-keyword">interface</span> HeadCell {
    disablePadding: <span class="hljs-built_in">boolean</span>;
    id: keyof Data;
    label: <span class="hljs-built_in">string</span>;
    numeric: <span class="hljs-built_in">boolean</span>;
}

<span class="hljs-keyword">const</span> headCells: <span class="hljs-keyword">readonly</span> HeadCell[] = [
    {
        id: <span class="hljs-string">'name'</span>,
        numeric: <span class="hljs-literal">false</span>,
        disablePadding: <span class="hljs-literal">true</span>,
        label: <span class="hljs-string">'Test Centre'</span>,
    },
    {
        id: <span class="hljs-string">'passRate'</span>,
        numeric: <span class="hljs-literal">true</span>,
        disablePadding: <span class="hljs-literal">true</span>,
        label: <span class="hljs-string">'Pass Rate'</span>,
    },
    {
        id: <span class="hljs-string">'rating'</span>,
        numeric: <span class="hljs-literal">true</span>,
        disablePadding: <span class="hljs-literal">true</span>,
        label: <span class="hljs-string">'Rating'</span>
    },
    {
        id: <span class="hljs-string">'distance'</span>,
        numeric: <span class="hljs-literal">true</span>,
        disablePadding: <span class="hljs-literal">true</span>,
        label: <span class="hljs-string">'Distance (miles)'</span>,
    }
];

<span class="hljs-keyword">interface</span> EnhancedTableProps {
    onRequestSort: <span class="hljs-function">(<span class="hljs-params">event: React.MouseEvent&lt;unknown&gt;, property: keyof Data</span>) =&gt;</span> <span class="hljs-built_in">void</span>;
    order: Order;
    orderBy: <span class="hljs-built_in">string</span>;
    rowCount: <span class="hljs-built_in">number</span>;
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">EnhancedTableHead</span>(<span class="hljs-params">props: EnhancedTableProps</span>) </span>{
    <span class="hljs-keyword">const</span> {order, orderBy, onRequestSort} =
        props;
    <span class="hljs-keyword">const</span> createSortHandler =
        <span class="hljs-function">(<span class="hljs-params">property: keyof Data</span>) =&gt;</span> (event: React.MouseEvent&lt;unknown&gt;) =&gt; {
            onRequestSort(event, property);
        };

    <span class="hljs-keyword">return</span> (
        &lt;TableHead&gt;
            &lt;TableRow&gt;
                {headCells.map(<span class="hljs-function">(<span class="hljs-params">headCell</span>) =&gt;</span>
                    (&lt;TableCell
                            key={headCell.id}
                            align={headCell.numeric ? <span class="hljs-string">'right'</span> : <span class="hljs-string">'left'</span>}
                            sortDirection={orderBy === headCell.id ? order : <span class="hljs-literal">false</span>}
                        &gt;
                            &lt;TableSortLabel
                                active={orderBy === headCell.id}
                                direction={orderBy === headCell.id ? order : <span class="hljs-string">'asc'</span>}
                                onClick={createSortHandler(headCell.id)}
                            &gt;
                                {headCell.label !== <span class="hljs-string">'Rating'</span> ?
                                    headCell.label : &lt;&gt;{<span class="hljs-string">'Rating'</span>} &lt;Tooltip
                                        title=<span class="hljs-string">"Ratings from Google"</span>&gt;&lt;HelpIcon sx={{ml: <span class="hljs-number">1</span>}}/&gt;&lt;<span class="hljs-regexp">/Tooltip&gt;&lt;/</span>&gt;}
                                {orderBy === headCell.id ? (
                                    &lt;Box component=<span class="hljs-string">"span"</span> sx={visuallyHidden}&gt;
                                        {order === <span class="hljs-string">'desc'</span> ? <span class="hljs-string">'sorted descending'</span> : <span class="hljs-string">'sorted ascending'</span>}
                                    &lt;/Box&gt;
                                ) : <span class="hljs-literal">null</span>}
                            &lt;/TableSortLabel&gt;
                        &lt;/TableCell&gt;
                    ))}
            &lt;/TableRow&gt;
        &lt;/TableHead&gt;
    );
}

<span class="hljs-keyword">interface</span> ResultsTableProps {
    results: TestCentreWithDistance[]
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ResultsTable</span>(<span class="hljs-params">{results}: ResultsTableProps</span>) </span>{
    <span class="hljs-keyword">const</span> [order, setOrder] = React.useState&lt;Order&gt;(<span class="hljs-string">'desc'</span>);
    <span class="hljs-keyword">const</span> [orderBy, setOrderBy] = React.useState&lt;keyof Data&gt;(<span class="hljs-string">'passRate'</span>);

    <span class="hljs-keyword">const</span> r = results.map(<span class="hljs-function"><span class="hljs-params">result</span> =&gt;</span> {
        <span class="hljs-keyword">return</span> {
            name: result.name, passRate: result.passRate,
            rating: result.mapDetails.rating ? <span class="hljs-string">`<span class="hljs-subst">${result.mapDetails.rating}</span>⭐ (<span class="hljs-subst">${result.mapDetails.userRatingsTotal}</span> reviews)`</span> : <span class="hljs-string">``</span>,
            mapURL: result.mapDetails.url,
            distance: result.distance
        };
    })

    <span class="hljs-keyword">const</span> handleRequestSort = <span class="hljs-function">(<span class="hljs-params">
        event: React.MouseEvent&lt;unknown&gt;,
        property: keyof Data,
    </span>) =&gt;</span> {
        <span class="hljs-keyword">const</span> isAsc = orderBy === property &amp;&amp; order === <span class="hljs-string">'asc'</span>;
        setOrder(isAsc ? <span class="hljs-string">'desc'</span> : <span class="hljs-string">'asc'</span>);
        setOrderBy(property);
    };

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            &lt;TableContainer component={Paper}&gt;
                &lt;Table
                    size={<span class="hljs-string">"medium"</span>}
                    aria-labelledby=<span class="hljs-string">"tableTitle"</span>
                    style={{ tableLayout: <span class="hljs-string">'auto'</span> }}
                    padding={<span class="hljs-string">'normal'</span>}

                &gt;
                    &lt;EnhancedTableHead
                        order={order}
                        orderBy={orderBy}
                        onRequestSort={handleRequestSort}
                        rowCount={results.length}
                    /&gt;
                    &lt;TableBody&gt;
                        {<span class="hljs-comment">/* if you don't need to support IE11, you can replace the `stableSort` call with:
              rows.slice().sort(getComparator(order, orderBy)) */</span>}
                        {stableSort(r, getComparator(order, orderBy))
                            .map(<span class="hljs-function">(<span class="hljs-params">row</span>) =&gt;</span> {
                                <span class="hljs-keyword">return</span> (
                                    &lt;TableRow
                                        hover
                                        tabIndex={<span class="hljs-number">-1</span>}
                                        key={row.name}
                                    &gt;
                                        &lt;TableCell&gt;{row.name}&lt;MapIcon onClick={<span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">window</span>.open(row.mapURL)}/&gt;&lt;/TableCell&gt;
                                        &lt;TableCell align=<span class="hljs-string">"right"</span>&gt;{row.passRate}%&lt;/TableCell&gt;
                                        &lt;TableCell align=<span class="hljs-string">"right"</span>&gt;{row.rating}&lt;/TableCell&gt;
                                        &lt;TableCell
                                            align=<span class="hljs-string">"right"</span>&gt;{<span class="hljs-built_in">Math</span>.round((row.distance + <span class="hljs-built_in">Number</span>.EPSILON) * <span class="hljs-number">100</span>) / <span class="hljs-number">100</span>}&lt;/TableCell&gt;
                                    &lt;/TableRow&gt;
                                );
                            })}
                    &lt;/TableBody&gt;
                &lt;/Table&gt;
            &lt;/TableContainer&gt;
        &lt;/&gt;
    );
}
</code></pre>
<p>There's a lot of code in this file, however, a lot of it is copied from the "Sorting &amp; Selecting" example given for tables in Material UI <a target="_blank" href="https://mui.com/material-ui/react-table/#sorting-amp-selecting">here</a>.<br />The most important section to focus on is the tsx returned from the function, showing how our table is rendered and with what cells and rows.<br />The table contains functionality for the user to sort by each column, and each column contains a map icon, linking the user to the test centre on Google Maps.</p>
<p>Navigating to <a target="_blank" href="http://localhost:3000/pass-rates?postcode=NG16JX&amp;radius=10">http://localhost:3000/pass-rates?postcode=NG16JX&amp;radius=10</a> now shows us a table with the same results as before.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683299578474/04712f2b-a841-47e2-929b-53ced65326a7.png" alt class="image--center mx-auto" /></p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This completes part 1!<br />Let's review what we've done here:</p>
<ul>
<li><p>Set up the initial project</p>
</li>
<li><p>Retrieved and formatted the public test centre data</p>
</li>
<li><p>Created and styled the landing page</p>
</li>
</ul>
<p>You can find all the code used in this post in my repo <a target="_blank" href="https://github.com/Zinbo/public-data-demo">here</a>. As well, you can find a live version of the application we built <a target="_blank" href="https://drivingpassrate.co.uk/">here</a>.</p>
<p>Look out for part 2, where we'll be adding search functionality and displaying the nearest test centres for each city!</p>
<p><strong>Updated:</strong> Part 2 is now available. Read it <a target="_blank" href="/blog/web-app-public-data-part-2">here</a>.</p>
<p>Till next time!</p>
]]></content:encoded></item><item><title><![CDATA[How To Deploy A Next.js App Using Spring Boot]]></title><description><![CDATA[Next.js is now my favourite way to build front-end applications. With Next.js you can develop a full stack application, including your own API, however, sometimes you want to serve the front end by adding static assets to a backend application.In thi...]]></description><link>https://stacktobasics.com/deploy-nextjs-with-spring-boot</link><guid isPermaLink="true">https://stacktobasics.com/deploy-nextjs-with-spring-boot</guid><category><![CDATA[Next.js]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><category><![CDATA[Java]]></category><category><![CDATA[TypeScript]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Tue, 04 Apr 2023 20:45:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683296208935/2d92c833-3a15-468a-bafb-6dd0760ec762.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Next.js is now my favourite way to build front-end applications. With Next.js you can develop a full stack application, including your own API, however, sometimes you want to serve the front end by adding static assets to a backend application.<br />In this guide, we will learn how to serve Next.js static assets from a Spring Boot application.</p>
<h1 id="heading-how-we-usually-deploy-nextjs-applications">How we usually deploy Next.js applications</h1>
<p>Before we walk through how to serve Next.js assets from Spring Boot we should first understand how Next.js applications are usually deployed.<br />When we want to deploy a Next.js application we must first run <code>next build</code> to build the application. As described in the official documentation <a target="_blank" href="https://nextjs.org/docs/deployment">here</a>, this produces:</p>
<ul>
<li><p>HTML files for pages using getStaticProps or Automatic Static Optimization</p>
</li>
<li><p>CSS files for global styles or for individually scoped styles</p>
</li>
<li><p>JavaScript for pre-rendering dynamic content from the Next.js server</p>
</li>
<li><p>JavaScript for interactivity on the client-side through React</p>
</li>
</ul>
<p>We must then run <code>next start</code>. This spins up a Node.js server that handles everything for us, including serving the static assets, handling API endpoints, routing, etc.</p>
<p>This is the recommended way to deploy Next.js applications however, in some cases, we don't want to have a separate server for our front end if we're already implementing back-end functionality in a different place, especially if we're not using Node.js.<br />In the next section, we'll explore how to serve our Next.js application using Spring Boot, without requiring a Node.js server.</p>
<h1 id="heading-deploying-a-nextjs-application-using-spring-boot">Deploying a Next.js application using Spring Boot</h1>
<p>The easiest way to explain how to do this is through a demo. In this section, I'll walk through a step-by-step guide.</p>
<h2 id="heading-setting-up">Setting up</h2>
<p>First, let's create a folder called <code>spring-boot-nextjs-demo</code>:</p>
<pre><code class="lang-bash">mkdir spring-boot-nextjs-demo
<span class="hljs-built_in">cd</span> spring-boot-nextjs-demo
</code></pre>
<p>Then we'll create a Spring Boot application using the Spring Initializr tool.</p>
<p>You can create the project with all of the required tools using the link <a target="_blank" href="https://start.spring.io/#!type=maven-project&amp;language=java&amp;platformVersion=3.0.2&amp;packaging=jar&amp;jvmVersion=17&amp;groupId=com.stacktobasics&amp;artifactId=spring-boot-app&amp;name=spring-boot-app&amp;description=Demo%20project%20for%20Spring%20Boot%20that%20serves%20Next.js%20assets&amp;packageName=com.stacktobasics.spring-boot-app&amp;dependencies=web">here</a>. Click "GENERATE" and extract the zip to the <code>spring-boot-nextjs-demo</code> folder.</p>
<p>Next, let's create the Next.js app. Make sure you're in the <code>spring-boot-nextjs-demo</code> folder and run:</p>
<pre><code class="lang-plaintext">npx create-next-app@latest --ts
</code></pre>
<p>Call the app <code>nextjs-app</code> and follow the rest of the defaults.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683232167949/12b2bd9a-5c9e-4de9-b8d9-615b02673d35.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-setting-up-the-maven-modules">Setting up the Maven modules</h2>
<p>In this project we will have the following Maven structure:</p>
<ol>
<li><p>a parent Maven project called <code>spring-boot-nextjs-demo</code></p>
</li>
<li><p>a child module called <code>nextjs-app</code>, which is responsible for building the Next.js application</p>
</li>
<li><p>a child module called <code>spring-boot-app</code>, which is responsible for building our Spring Boot application and adding our Next.js assets to the jar.</p>
</li>
</ol>
<h3 id="heading-parent-maven-setup">Parent Maven setup</h3>
<p>We'll create a <code>pom.xml</code> file in the root folder, <code>spring-boot-nextjs-demo</code>, which contains the following:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
    <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span> <span class="hljs-comment">&lt;!-- lookup parent from repository --&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.stacktobasics<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-nextjs-demo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">packaging</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">packaging</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>Spring Boot Nextjs Demo<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modules</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">module</span>&gt;</span>nextjs-app<span class="hljs-tag">&lt;/<span class="hljs-name">module</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">module</span>&gt;</span>spring-boot-app<span class="hljs-tag">&lt;/<span class="hljs-name">module</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">modules</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>17<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<p>This quite simply inherits from the Spring Boot parent pom (as is usually the case for Spring Boot applications) and declares two child modules: our Next.js app and our Spring Boot app.</p>
<h3 id="heading-nextjs-maven-setup">Next.js Maven setup</h3>
<p>We'll create a <code>pom.xml</code> file in our <code>nextjs-app</code> folder, which contains the following:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
    <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-nextjs-demo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.stacktobasics<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>

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

    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.github.eirslett<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>frontend-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.11.3<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">executions</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>install node and npm<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>install-node-and-npm<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>npm install<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>npm<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">phase</span>&gt;</span>generate-resources<span class="hljs-tag">&lt;/<span class="hljs-name">phase</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>npm run build<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>npm<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">phase</span>&gt;</span>generate-resources<span class="hljs-tag">&lt;/<span class="hljs-name">phase</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">arguments</span>&gt;</span>run build<span class="hljs-tag">&lt;/<span class="hljs-name">arguments</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">executions</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">nodeVersion</span>&gt;</span>v18.14.0<span class="hljs-tag">&lt;/<span class="hljs-name">nodeVersion</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<p>In this maven module we're including the <code>frontend-maven-plugin</code>.<br />When this module is built this plugin will run the execution steps that we've defined, <code>npm install</code> and <code>npm run build</code> in this case.<br />If we look in the <code>package.json</code> file generated by Next.js we'll see that <code>npm run build</code> runs <code>next build</code>, which generates all the files required for a production build in the <code>out</code> folder.</p>
<h3 id="heading-spring-boot-maven-setup">Spring Boot Maven setup</h3>
<p>Next, let's edit the <code>pom.xml</code> file in the <code>spring-boot-app</code> folder to inherit from our new parent pom, and also to include our Next.js static assets in our Spring Boot application jar. The final pom.xml file will look like this:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span> <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
    <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>

    <span class="hljs-comment">&lt;!-- Change parent tag from spring-boot-starter-parent to spring-boot-nextjs-demo --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.stacktobasics<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-nextjs-demo<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.stacktobasics<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-app<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">name</span>&gt;</span>spring-boot-app<span class="hljs-tag">&lt;/<span class="hljs-name">name</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">description</span>&gt;</span>Demo project for Spring Boot that serves Next.js assets<span class="hljs-tag">&lt;/<span class="hljs-name">description</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>17<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>test<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">build</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">plugins</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-maven-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">plugin</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>maven-resources-plugin<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.2.0<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">executions</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">execution</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">id</span>&gt;</span>copy-resources<span class="hljs-tag">&lt;/<span class="hljs-name">id</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">phase</span>&gt;</span>generate-resources<span class="hljs-tag">&lt;/<span class="hljs-name">phase</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">goals</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">goal</span>&gt;</span>copy-resources<span class="hljs-tag">&lt;/<span class="hljs-name">goal</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">goals</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">configuration</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">outputDirectory</span>&gt;</span>${basedir}/target/classes/static<span class="hljs-tag">&lt;/<span class="hljs-name">outputDirectory</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">resources</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">resource</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">directory</span>&gt;</span>${basedir}/../nextjs-app/out<span class="hljs-tag">&lt;/<span class="hljs-name">directory</span>&gt;</span>
                                    <span class="hljs-tag">&lt;<span class="hljs-name">filtering</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">filtering</span>&gt;</span>
                                <span class="hljs-tag">&lt;/<span class="hljs-name">resource</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">resources</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">configuration</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">execution</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">executions</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">plugin</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">plugins</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">build</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<p>Then <code>maven-resources-plugin</code> will copy our Next.js static assets created by our Next.js maven module, into the static folder in our jar.</p>
<h2 id="heading-enable-static-export">Enable static export</h2>
<p>Finally, we need to enable the exporting of our Next.js application to HTML. To do this, edit the build script in <code>package.json</code> to include <code>next export</code>:</p>
<pre><code class="lang-json"><span class="hljs-string">"scripts"</span>: {
    <span class="hljs-attr">"dev"</span>: <span class="hljs-string">"next dev"</span>,
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"next build &amp;&amp; next export"</span>,
    <span class="hljs-attr">"start"</span>: <span class="hljs-string">"next start"</span>,
    <span class="hljs-attr">"lint"</span>: <span class="hljs-string">"next lint"</span>
},
</code></pre>
<p>Now, let's build the application!</p>
<p><strong>Note</strong>: If you don't have Maven installed then copy the <code>.mvn</code> folder and the <code>mvnw</code> file (or <code>mvnw.cmd</code> file if you're on windows) to the root directory, <code>spring-boot-nextjs-demo</code>.</p>
<p>From the root directory, run <code>mvnw clean install</code>.</p>
<p>Oops! The build failed!</p>
<p>You will see something like:</p>
<pre><code class="lang-plaintext">[INFO] info  - using build directory: D:\Documents\Development\spring-boot-nextjs-demo\nextjs-app\.next
[INFO] info  - Copying "static build" directory
[INFO] info  - No "exportPathMap" found in "D:\Documents\Development\spring-boot-nextjs-demo\nextjs-app\next.con
fig.js". Generating map from "./pages"
[INFO] Error: Image Optimization using Next.js' default loader is not compatible with `next export`.
[INFO]   Possible solutions:
[INFO]     - Use `next start` to run a server, which includes the Image Optimization API.
[INFO]     - Configure `images.unoptimized = true` in `next.config.js` to disable the Image Optimization API.   
[INFO]   Read more: https://nextjs.org/docs/messages/export-image-api
[INFO]     at D:\Documents\Development\spring-boot-nextjs-demo\nextjs-app\node_modules\next\dist\export\index.js
:153:23
[INFO]     at async Span.traceAsyncFn (D:\Documents\Development\spring-boot-nextjs-demo\nextjs-app\node_modules\
next\dist\trace\trace.js:79:20)
[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Spring Boot Nextjs Demo 0.0.1-SNAPSHOT:
[INFO]
[INFO] Spring Boot Nextjs Demo ............................ SUCCESS [  0.406 s]
[INFO] nextjs-app ......................................... FAILURE [ 17.515 s]
[INFO] spring-boot-app .................................... SKIPPED
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  18.369 s
[INFO] Finished at: 2023-02-18T18:08:02Z
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal com.github.eirslett:frontend-maven-plugin:1.11.3:npm (npm run build) on project n
extjs-app: Failed to run task: 'npm run build' failed. org.apache.commons.exec.ExecuteException: Process exited 
with an error: 1 (Exit value: 1) -&gt; [Help 1]
</code></pre>
<h2 id="heading-disabling-nextjs-server-side-features">Disabling Next.js' server-side features</h2>
<p>The reason we see this error is that there are limitations when using <code>next export</code>. We'll cover this in more detail in the next section, but for now, change your <code>next.config.js</code> file in <code>nextjs-app</code> to the following:</p>
<pre><code class="lang-json"><span class="hljs-comment">/** @type {import('next').NextConfig} */</span>
const nextConfig = {
  reactStrictMode: <span class="hljs-literal">true</span>,
  images: {
    unoptimized: <span class="hljs-literal">true</span>
  },
}

module.exports = nextConfig
</code></pre>
<p>Now let's run our build again using <code>mvnw clean install</code></p>
<p>Hopefully, by the end of the build, you should see:</p>
<pre><code class="lang-plaintext">[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary for Spring Boot Nextjs Demo 0.0.1-SNAPSHOT:
[INFO]
[INFO] Spring Boot Nextjs Demo ............................ SUCCESS [  0.355 s]
[INFO] nextjs-app ......................................... SUCCESS [01:39 min]
[INFO] spring-boot-app .................................... SUCCESS [  8.813 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  01:49 min
[INFO] Finished at: 2023-02-18T18:02:11Z
[INFO] ------------------------------------------------------------------------
</code></pre>
<p>Let's test our spring boot app to see if it's rendering our Next.js static assets.<br />Run <code>java -jar spring-boot-app/target/spring-boot-app-0.0.1-SNAPSHOT.jar</code>.<br />Once you see <code>c.s.s.SpringBootAppApplication : Started SpringBootAppApplication in 2.334 seconds (process running for 2.847)</code> go to <a target="_blank" href="http://localhost:8080/.%EF%BF%BCYou">http://localhost:8080/.</a></p>
<p><a target="_blank" href="http://localhost:8080/.%EF%BF%BCYou">You</a> should see the generated HTML from the <code>index.tsx</code> page:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683232574620/10aed469-4f4a-4617-b129-7a405f410290.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-adding-an-endpoint-to-our-api">Adding an Endpoint to our API</h2>
<p>To make sure that we can reach both our API and our HTML pages let's add a simple controller.<br />Under <code>spring-boot-app/src/main/java/com.stacktobasics.springbootapp</code> add the following <code>HelloController.java</code> file:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.springbootapp;

<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;

<span class="hljs-meta">@RestController</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloController</span> </span>{
    <span class="hljs-meta">@GetMapping("/api/hello-world")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">sayHello</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">"Hello!"</span>;
    }
}
</code></pre>
<p>Let's rebuild and re-run our application.<br />Stop the <code>java -jar</code> process if you haven't already and run <code>mvnw clean install</code>, followed by <code>java -jar spring-boot-app/target/spring-boot-app-0.0.1-SNAPSHOT.jar</code>.</p>
<p>Once our Spring Boot app has started, going to <a target="_blank" href="http://localhost:8080/">http://localhost:8080/</a> will again show us the landing page.<br />Going to <a target="_blank" href="http://localhost:8080/api/hello-world">http://localhost:8080/api/hello-world</a> will return <code>Hello!</code> as expected, proving that Spring Boot is still routing to our API correctly!</p>
<h2 id="heading-adding-another-nextjs-page">Adding another Next.js page</h2>
<p>Lastly, let's check that we're able to navigate between different Next.js pages.</p>
<p>Add a new file called <code>hello.tsx</code> under the <code>nextjs-app\pages</code> folder with the following contents:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{
    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            Hello world!
        &lt;/&gt;
    )
}
</code></pre>
<p>Next, we'll create a link to this page. Open the <code>index.tsx</code> file and import the <code>next/link</code> package:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"next/link"</span>;
</code></pre>
<p>Then, add a link to our new page at the bottom:</p>
<pre><code class="lang-typescript">...
          &lt;div&gt;
            &lt;Link href={<span class="hljs-string">"/hello"</span>}&gt;Go to hello page&lt;/Link&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/main&gt;
    &lt;/&gt;
  )
}
</code></pre>
<p>Let's rebuild and re-run our application.<br />Stop the <code>java -jar</code> process if you haven't already and run <code>mvnw clean install</code>, followed by <code>java -jar spring-boot-app/target/spring-boot-app-0.0.1-SNAPSHOT.jar</code>.</p>
<p>We will now see a link to our <code>Hello</code> page at the bottom of the landing page:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683232709151/1a22ae3c-3a5a-4be7-a979-535a5f61a710.png" alt class="image--center mx-auto" /></p>
<p>Clicking on this link will bring us to our new page:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683232729915/9aa387ed-2c13-456d-ace2-ac9ca1ee2ad5.png" alt class="image--center mx-auto" /></p>
<p>However, try refreshing the page. You'll see a 404 error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683232743300/9d1b23f4-9c24-4883-b0db-cb92992a8d8f.png" alt class="image--center mx-auto" /></p>
<p>Huh? What happened?</p>
<h2 id="heading-routing-to-pages-without-html">Routing to pages without .html</h2>
<p>When we clicked the link to go to our hello page from our index page the request to change to a different route was handled from the client side, which worked thanks to Next.js's routing.<br />However, when we navigate directly to <code>/hello</code> we're going straight to the server. Our server does not have a resource that maps to <code>/hello</code>, so it returns a 404.</p>
<p>We do however have a file called <code>/hello.html</code>. If you navigate to <a target="_blank" href="http://localhost:8080/hello.html"><code>localhost:8080/hello.html</code></a> you will see our hello page.<br />This isn't great though, we want users to be able to get to our <code>/hello</code> page directly, without the <code>.html</code> extension!</p>
<p>One easy way to do this is to add a Spring MVC controller (Note the use of <code>@Controller</code>, not <code>@RestController</code>!) to map any request which does not have a file extension to the corresponding HTML page.</p>
<p>Add a class called <code>HtmlController</code> containing the following:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics.springbootapp;

<span class="hljs-keyword">import</span> org.slf4j.Logger;
<span class="hljs-keyword">import</span> org.slf4j.LoggerFactory;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Controller;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.PathVariable;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;

<span class="hljs-meta">@Controller</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HtmlController</span> </span>{

    Logger logger = LoggerFactory.getLogger(HtmlController.class);

    <span class="hljs-meta">@RequestMapping("/{page:^(?!.*[.].*$).*$}")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">requestPage</span><span class="hljs-params">(<span class="hljs-meta">@PathVariable("page")</span> String page)</span> </span>{
        String htmlPage = <span class="hljs-string">"/"</span>+page+<span class="hljs-string">".html"</span>;
        logger.info(<span class="hljs-string">"forwarding request to {}"</span>, htmlPage);
        <span class="hljs-keyword">return</span> htmlPage;
    }
}
</code></pre>
<p>Let's rebuild and re-run our application.<br />Stop the <code>java -jar</code> process if you haven't already and run <code>mvnw clean install</code>, followed by <code>java -jar spring-boot-app/target/spring-boot-app-0.0.1-SNAPSHOT.jar</code>.</p>
<p>Now going to <a target="_blank" href="http://localhost:8080/hello"><code>localhost:8080/hello</code></a> will correctly render our hello page.<br />We also see the following log, showing that our <code>HtmlController</code> is working:<br /><code>[nio-8080-exec-2] c.s.springbootapp.HtmlController : forwarding request to /hello.html</code></p>
<h2 id="heading-making-local-development-easier">Making local development easier</h2>
<p>Whilst what we've done above works well for generating a production build it's quite cumbersome for local development.</p>
<p>Instead, I would recommend using <code>next dev</code> for local development and starting your Spring Boot app in the usual way - such as from an IDE like IntelliJ.</p>
<p>Let's test this out!</p>
<p>Run <code>npm run dev</code> (which runs <code>next dev</code>) from the <code>nextjs-app</code> folder and verify that you can load both <a target="_blank" href="http://localhost:3000"><code>localhost:3000</code></a> and <a target="_blank" href="http://localhost:3000/hello"><code>localhost:3000/hello</code></a>.</p>
<p>Start up your Spring Boot app from your IDE.</p>
<p>You might have guessed a problem we will face with this approach (hint: it's related to port numbers!).<br />When we've built our application using maven and running <code>java -jar</code> then both our API and our front-end assets are being served from our Spring Boot application on port 8080.<br />However, when using <code>next dev</code> the front end will be served from Node.js on port 3000.</p>
<p>This can be problematic when making calls to our back-end service from our front-end application.</p>
<p>Let's show this issue with an example.</p>
<p>Firstly, stop both your Next.js and Spring Boot processes.<br />Secondly, change your <code>hello.tsx</code> file to make a call to the <code>api/hello-world</code> endpoint, like so:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { useState, useEffect } <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">Home</span>(<span class="hljs-params"></span>) </span>{

    <span class="hljs-keyword">const</span> [helloResponse, setHelloResponse] = useState&lt;<span class="hljs-built_in">string</span>|<span class="hljs-literal">null</span>&gt;(<span class="hljs-literal">null</span>)

    useEffect(<span class="hljs-function">() =&gt;</span> {
        fetch(<span class="hljs-string">'/api/hello-world'</span>)
            .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> res.text())
            .then(<span class="hljs-function">(<span class="hljs-params">data</span>) =&gt;</span> {
                setHelloResponse(data)
            })
    }, [])

    <span class="hljs-keyword">return</span> (
        &lt;&gt;
            Hello world!
            Response <span class="hljs-keyword">from</span> server call to /api/hello-world: {helloResponse}
        &lt;/&gt;
    )
}
</code></pre>
<p>Run <code>mvnw clean install</code>, followed by <code>java -jar spring-boot-app/target/spring-boot-app-0.0.1-SNAPSHOT.jar</code>.</p>
<p>Then navigate to <a target="_blank" href="http://localhost:8080/hello"><code>localhost:8080/hello</code></a>. This will show the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683232927087/7fa35dd5-98de-472a-815a-14f14bd51da8.png" alt class="image--center mx-auto" /></p>
<p>We can see the response is returned from our <code>/api/hello-world</code> call, as expected.</p>
<p>This time, stop your <code>java -jar</code> process, and run your Next.js app using <code>npm run dev</code> and your Spring Boot app from your IDE.<br />Navigate again to <code>localhost:8080/hello</code>.<br />This time you'll see an error:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1683232994216/ebd04a5f-319c-4d07-9904-60d1a13a283c.png" alt class="image--center mx-auto" /></p>
<p>The reason we see this error is that your request is going to <a target="_blank" href="http://localhost:3000/api/hello-world"><code>localhost:3000/api/hello-world</code></a>, rather than <a target="_blank" href="http://localhost:8080/api/hello-world"><code>localhost:8080/api/hello-world</code></a>.</p>
<p>We can get around this by using Rewrites. To do this we must change our <code>next.config.js</code> file to the following:</p>
<pre><code class="lang-js"><span class="hljs-comment">/** <span class="hljs-doctag">@type <span class="hljs-type">{import('next').NextConfig}</span> </span>*/</span>
<span class="hljs-keyword">const</span> nextConfig = {
    <span class="hljs-attr">reactStrictMode</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">images</span>: {
        <span class="hljs-attr">unoptimized</span>: <span class="hljs-literal">true</span>
    },
    <span class="hljs-keyword">async</span> rewrites() {
        <span class="hljs-keyword">return</span> [
            {
                <span class="hljs-attr">source</span>: <span class="hljs-string">'/api/:path*'</span>,
                <span class="hljs-attr">destination</span>: <span class="hljs-string">'http://localhost:8080/api/:path*'</span>,
            },
        ]
    }
}

<span class="hljs-built_in">module</span>.exports = nextConfig
</code></pre>
<p>Here we've added config to rewrite every request matching <code>/api/**</code> to <a target="_blank" href="http://localhost:8080/api/**"><code>http://localhost:8080/api/**</code></a>.</p>
<p>Restart the Node server by re-running <code>npm run dev</code>.<br />You'll now see that navigating to <a target="_blank" href="http://localhost:3000/hello"><code>localhost:3000/hello</code></a> renders the data from <a target="_blank" href="http://localhost:8080/api/hello-world"><code>localhost:8080/api/hello-world</code></a></p>
<h1 id="heading-limitations-of-hosting-nextjs-static-assets-from-spring-boot">Limitations of hosting Next.js static assets from Spring Boot</h1>
<p>We touched on the limitations of <code>next export</code> briefly when we had to change our <code>next.config.js</code> file to specify that images should not be optimised.</p>
<p>Next.js has a lot of features - some that work on the server-side and some that work on the client-side. When we run <code>next export</code> we no longer have the Node.js server, and thus we can't take advantage of the server-side features of Next.js that rely on a dedicated Node.js server.</p>
<p>It's worth keeping this in mind as there may be features that you want to use that are server-side features. The following features are not supported:</p>
<ul>
<li><p>Image Optimization (default loader)</p>
</li>
<li><p>Internationalized Routing</p>
</li>
<li><p>API Routes</p>
</li>
<li><p>Rewrites</p>
</li>
<li><p>Redirects</p>
</li>
<li><p>Headers</p>
</li>
<li><p>Middleware</p>
</li>
<li><p>Incremental Static Regeneration</p>
</li>
<li><p>fallback: true</p>
</li>
<li><p>getServerSideProps</p>
</li>
</ul>
<p>More on this can be read in the official documentation <a target="_blank" href="https://nextjs.org/docs/advanced-features/static-html-export#unsupported-features">here</a>.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In this post, we have learned how to generate static assets for Next.js and serve them with a Spring Boot back-end application.<br />We have learned about some of the common pitfalls of this approach and found ways to get around them.<br />We have also learned the best way to do local development when using this approach by using Next.js Rewrites.<br />Finally, we have learned about some of the limitations of using the Export feature in Next.js.</p>
<p>All of the code from this post can be found <a target="_blank" href="https://github.com/Zinbo/spring-boot-nextjs-demo">here</a>.</p>
<p>Till next time!</p>
]]></content:encoded></item><item><title><![CDATA[Adding Correlation IDs to Easily Track Down Errors]]></title><description><![CDATA[In this post, we'll look at how we can use Spring Cloud Sleuth to add trace IDs to our application to track down calls and exceptions.
Why Would We Want To Use Correlation IDs?
If you've ever worked on an application that has numerous concurrent call...]]></description><link>https://stacktobasics.com/correlation-ids</link><guid isPermaLink="true">https://stacktobasics.com/correlation-ids</guid><category><![CDATA[Java]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[spring cloud]]></category><category><![CDATA[error handling]]></category><category><![CDATA[Error Tracking]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Wed, 13 Jul 2022 20:20:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683231937187/c6c3977b-ce4c-42f6-ab20-12684edd23f5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post, we'll look at how we can use Spring Cloud Sleuth to add trace IDs to our application to track down calls and exceptions.</p>
<h1 id="heading-why-would-we-want-to-use-correlation-ids">Why Would We Want To Use Correlation IDs?</h1>
<p>If you've ever worked on an application that has numerous concurrent calls happening you'll know that it's hard to figure out from the logs which message belongs to which call.<br />Likewise, if you get an exception in your application and you want to find the root cause it can be hard to know where to look in the logs.</p>
<p>We can use correlation IDs to correlate our log messages and exception stack traces to specific calls in our web application. As well, if we include correlation IDs in our error responses sent back to clients we can use those IDs to trace back to the cause of the error.</p>
<p>One way we can easily add correlation IDs to our application is Spring Cloud Sleuth.</p>
<h1 id="heading-spring-cloud-sleuth">Spring Cloud Sleuth</h1>
<p><a target="_blank" href="https://spring.io/projects/spring-cloud-sleuth">Spring Cloud Sleuth</a> is part of the wider Spring Cloud library and provides the configuration required for distributed tracing.<br />Distributed tracing is a great tool for tracing your calls through various microservices, however, the part that we're interested in here is this:</p>
<blockquote>
<p>Adds trace and span ids to the Slf4J MDC, so you can extract all the logs from a given trace or span in a log aggregator.</p>
</blockquote>
<p>The trace ID is what we will use as our correlation ID.</p>
<p>By default, Spring Cloud Sleuth will add the trace ID as a header when calling other services, which is how the distributed trace is tracked. However, it does not automatically add the trace id to responses from our application. This can easily be added with a filter, which we'll show below.</p>
<h1 id="heading-setting-up-a-spring-boot-app-with-sleuth">Setting Up A Spring Boot App with Sleuth</h1>
<h2 id="heading-adding-a-simple-controller-and-service">Adding a Simple Controller and Service</h2>
<p>Let's start by creating a Spring Boot application with a simple controller and service.</p>
<p><strong>Pom.xml</strong></p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.example<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>Blogging-correlation-ids<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.7.1<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">maven.compiler.source</span>&gt;</span>17<span class="hljs-tag">&lt;/<span class="hljs-name">maven.compiler.source</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">maven.compiler.target</span>&gt;</span>17<span class="hljs-tag">&lt;/<span class="hljs-name">maven.compiler.target</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>17<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">spring-cloud.version</span>&gt;</span>2021.0.3<span class="hljs-tag">&lt;/<span class="hljs-name">spring-cloud.version</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">dependencyManagement</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-dependencies<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>${spring-cloud.version}<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">type</span>&gt;</span>pom<span class="hljs-tag">&lt;/<span class="hljs-name">type</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">scope</span>&gt;</span>import<span class="hljs-tag">&lt;/<span class="hljs-name">scope</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencyManagement</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-actuator<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.cloud<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-cloud-starter-sleuth<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.projectlombok<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>lombok<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">optional</span>&gt;</span>true<span class="hljs-tag">&lt;/<span class="hljs-name">optional</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>

<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<p><strong>StackToBasicsApplication</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics;

<span class="hljs-keyword">import</span> org.springframework.boot.SpringApplication;
<span class="hljs-keyword">import</span> org.springframework.boot.autoconfigure.SpringBootApplication;

<span class="hljs-meta">@SpringBootApplication</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">StackToBasicsApplication</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        SpringApplication.run(StackToBasicsApplication.class, args);
    }
}
</code></pre>
<p><strong>HelloWorldController</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics;

<span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;
<span class="hljs-keyword">import</span> org.springframework.http.ResponseEntity;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.GetMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RequestMapping;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestController;

<span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldController</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> HelloWorldService helloWorldService;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HelloWorldController</span><span class="hljs-params">(HelloWorldService helloWorldService)</span> </span>{
        <span class="hljs-keyword">this</span>.helloWorldService = helloWorldService;
    }

    <span class="hljs-meta">@GetMapping("/hello")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;String&gt; <span class="hljs-title">sayHello</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"Someone called the /hello endpoint"</span>);
        <span class="hljs-keyword">return</span> ResponseEntity.ok(helloWorldService.sayHello());
    }
}
</code></pre>
<p><strong>HelloWorldService</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics;

<span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Component;

<span class="hljs-meta">@Component</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldService</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">sayHello</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"Returning hello from service"</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-string">"hello"</span>;
    }
}
</code></pre>
<p>When we launch this application and call <code>GET /hello</code> we see the following:</p>
<pre><code class="lang-plaintext">2022-07-13 18:30:39.545  INFO [,10f5c8744d01e2d4,10f5c8744d01e2d4] 19460 --- [nio-8080-exec-6] com.stacktobasics.HelloWorldController   : Someone called the /hello endpoint
2022-07-13 18:30:39.545  INFO [,10f5c8744d01e2d4,10f5c8744d01e2d4] 19460 --- [nio-8080-exec-6] com.stacktobasics.HelloWorldService      : Returning hello from service
</code></pre>
<p>Notice the <code>10f5c8744d01e2d4</code> ID in our logs. This is repeated twice, once for the span ID and once for the trace ID. In this case they are the same value. We can see that the ID is injected into both our controller and our service logs. This means we can trace the call through whatever classes are used!</p>
<p>If we call <code>GET /hello</code> again we can see we get a different ID:</p>
<pre><code class="lang-plaintext">2022-07-13 18:32:51.910  INFO [,4a6379092ac32a93,4a6379092ac32a93] 19460 --- [nio-8080-exec-7] com.stacktobasics.HelloWorldController   : Someone called the /hello endpoint
2022-07-13 18:32:51.911  INFO [,4a6379092ac32a93,4a6379092ac32a93] 19460 --- [nio-8080-exec-7] com.stacktobasics.HelloWorldService      : Returning hello from service
</code></pre>
<h2 id="heading-adding-the-correlation-id-to-exception-responses">Adding the Correlation ID to Exception Responses</h2>
<p>It would be useful to be able to return the trace ID when sending back error responses. That way if a user of the application tells us about an error they are experiencing, we can ask them for the response they received and look for the trace ID in our logs.</p>
<p><strong>HttpExceptionHandler</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics;

<span class="hljs-keyword">import</span> lombok.extern.slf4j.Slf4j;
<span class="hljs-keyword">import</span> org.springframework.http.HttpStatus;
<span class="hljs-keyword">import</span> org.springframework.http.ResponseEntity;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.ExceptionHandler;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.ResponseStatus;
<span class="hljs-keyword">import</span> org.springframework.web.bind.annotation.RestControllerAdvice;

<span class="hljs-meta">@RestControllerAdvice</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HttpExceptionHandler</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CorrelationIDHandler correlationIDHandler;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">HttpExceptionHandler</span><span class="hljs-params">(CorrelationIDHandler correlationIDHandler)</span> </span>{
        <span class="hljs-keyword">this</span>.correlationIDHandler = correlationIDHandler;
    }

    <span class="hljs-meta">@ExceptionHandler(IllegalArgumentException.class)</span>
    <span class="hljs-meta">@ResponseStatus(HttpStatus.BAD_REQUEST)</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> ResponseEntity&lt;ExceptionResponse&gt; <span class="hljs-title">handleIllegalArgumentException</span><span class="hljs-params">(IllegalArgumentException exception)</span> </span>{
        log.warn(exception.getMessage(), exception);
        <span class="hljs-keyword">var</span> exceptionResponse = <span class="hljs-keyword">new</span> ExceptionResponse(HttpStatus.BAD_REQUEST, exception, correlationIDHandler.getCorrelationId());
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> ResponseEntity&lt;&gt;(exceptionResponse, exceptionResponse.getStatus());
    }
}
</code></pre>
<p><strong>CorrelationIDHandler</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics;

<span class="hljs-keyword">import</span> org.springframework.cloud.sleuth.Span;
<span class="hljs-keyword">import</span> org.springframework.cloud.sleuth.TraceContext;
<span class="hljs-keyword">import</span> org.springframework.cloud.sleuth.Tracer;
<span class="hljs-keyword">import</span> org.springframework.stereotype.Component;

<span class="hljs-keyword">import</span> java.util.Optional;

<span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CorrelationIDHandler</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Tracer tracer;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">CorrelationIDHandler</span><span class="hljs-params">(Tracer tracer)</span> </span>{
        <span class="hljs-keyword">this</span>.tracer = tracer;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getCorrelationId</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> Optional.of(tracer).map(Tracer::currentSpan).map(Span::context).map(TraceContext::traceId).orElse(<span class="hljs-string">""</span>);
    }
}
</code></pre>
<p><strong>ExceptionResponse</strong></p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> com.stacktobasics;

<span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonFormat;
<span class="hljs-keyword">import</span> lombok.Getter;
<span class="hljs-keyword">import</span> lombok.NonNull;
<span class="hljs-keyword">import</span> org.springframework.http.HttpStatus;

<span class="hljs-keyword">import</span> java.time.LocalDateTime;

<span class="hljs-meta">@Getter</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ExceptionResponse</span> </span>{
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String correlationId;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> HttpStatus status;
    <span class="hljs-meta">@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss")</span>
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> LocalDateTime datetime;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String error;

    ExceptionResponse(<span class="hljs-meta">@NonNull</span> HttpStatus status, <span class="hljs-meta">@NonNull</span> Exception ex, String correlationId) {
        datetime = LocalDateTime.now();
        <span class="hljs-keyword">this</span>.status = status;
        <span class="hljs-keyword">this</span>.error = ex.getMessage();
        <span class="hljs-keyword">this</span>.correlationId = correlationId;
    }
}
</code></pre>
<p>Let's add a method to our controller and service to trigger an exception.</p>
<p><strong>HelloWorldController</strong></p>
<pre><code class="lang-java">...

<span class="hljs-meta">@RestController</span>
<span class="hljs-meta">@RequestMapping</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldController</span> </span>{

    ...

    <span class="hljs-meta">@GetMapping("/bad-call")</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">badCall</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"Someone called the /bad-call endpoint"</span>);
        helloWorldService.fakeBadCall();
    }
}
</code></pre>
<p>**HelloWorldService</p>
<pre><code class="lang-java">...

<span class="hljs-meta">@Component</span>
<span class="hljs-meta">@Slf4j</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">HelloWorldService</span> </span>{

    ...

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">fakeBadCall</span><span class="hljs-params">()</span> </span>{
        log.info(<span class="hljs-string">"About to throw IllegalArgumentException..."</span>);
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalArgumentException(<span class="hljs-string">"Exception from Hello World Service"</span>);
    }
}
</code></pre>
<p>Things to notice are:</p>
<ul>
<li><p><code>CorrelationIDHandler</code> uses the injected <code>Tracer</code> object to get a copy of the trace ID.</p>
</li>
<li><p><code>ExceptionResponse</code> is the response object we return to users when we encounter an exception. It contains the trace ID as a correlation ID field.</p>
</li>
<li><p>We have an exception handler <code>HttpExceptionHandler</code> which handles uncaught exceptions and provides and appropriate response to the user. Here we are only handling <code>IllegalArgumentException</code>.</p>
</li>
</ul>
<p>When we call <code>GET /bad-call</code> we see the following in our logs:</p>
<pre><code class="lang-plaintext">2022-07-13 18:47:20.751  INFO [,b490ea4b7230260a,b490ea4b7230260a] 14716 --- [nio-8080-exec-5] com.stacktobasics.HelloWorldController   : Someone called the /bad-call endpoint
2022-07-13 18:47:20.751  INFO [,b490ea4b7230260a,b490ea4b7230260a] 14716 --- [nio-8080-exec-5] com.stacktobasics.HelloWorldService      : About to throw IllegalArgumentException...
2022-07-13 18:47:20.755  WARN [,b490ea4b7230260a,b490ea4b7230260a] 14716 --- [nio-8080-exec-5] com.stacktobasics.HttpExceptionHandler   : Exception from Hello World Service

java.lang.IllegalArgumentException: Exception from Hello World Service
    at com.stacktobasics.HelloWorldService.fakeBadCall(HelloWorldService.java:17) ~[classes/:na]
    at com.stacktobasics.HelloWorldController.badCall(HelloWorldController.java:29) ~[classes/:na]
    ...
</code></pre>
<p>We also see the following response:</p>
<pre><code class="lang-json">{
<span class="hljs-attr">"correlationId"</span>: <span class="hljs-string">"b490ea4b7230260a"</span>,
<span class="hljs-attr">"status"</span>: <span class="hljs-string">"BAD_REQUEST"</span>,
<span class="hljs-attr">"datetime"</span>: <span class="hljs-string">"2022-07-13 18:47:20"</span>,
<span class="hljs-attr">"error"</span>: <span class="hljs-string">"Exception from Hello World Service"</span>
}
</code></pre>
<p>We can see that the correlationID in the response matches the trace ID in the logs.</p>
<h2 id="heading-adding-the-trace-id-to-the-header">Adding the Trace ID to the Header</h2>
<p>Having the trace ID is great for tracing calls through our logs. However, if we want to track the trace ID in another application that doesn't use Spring Sleuth (such as a Node.JS application) then we need to find a way of exposing the trace ID to callers.<br />This can easily be done by adding a filter to our application which adds the trace ID as a header.</p>
<p><strong>TraceFilter</strong></p>
<pre><code class="lang-java"><span class="hljs-meta">@Component</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TraceFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Filter</span> </span>{

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String TRACE_ID_HEADER_NAME = <span class="hljs-string">"X-B3-TraceId"</span>;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> CorrelationIDHandler correlationIDHandler;

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">TraceFilter</span><span class="hljs-params">(CorrelationIDHandler correlationIDHandler)</span> </span>{
        <span class="hljs-keyword">this</span>.correlationIDHandler = correlationIDHandler;
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">doFilter</span><span class="hljs-params">(ServletRequest req, ServletResponse res,
                         FilterChain chain)</span> <span class="hljs-keyword">throws</span> IOException, ServletException </span>{

        HttpServletResponse response = (HttpServletResponse) res;
        <span class="hljs-keyword">if</span> (!response.getHeaderNames().contains(TRACE_ID_HEADER_NAME)) {
            <span class="hljs-keyword">var</span> id = correlationIDHandler.getCorrelationId();
            <span class="hljs-keyword">if</span> (!id.isEmpty()) response.setHeader(TRACE_ID_HEADER_NAME, id);
        }
        chain.doFilter(req, res);
    }
}
</code></pre>
<p>This filter adds the <code>X-B3-TraceId</code> header to any response that does not already contain a trace ID. The <code>X-B3-TraceId</code> header is what Sleuth adds when sending requests to other services to keep track of the trace through microservices, so we've used the same name to be consistent.</p>
<p>If we run our application again and call <code>GET /hello</code> we will find the trace ID has been added to the response headers:</p>
<pre><code class="lang-plaintext">HTTP/1.1 200
X-B3-TraceId: c9ac548611166be7
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In this post, we've learned how to use Spring Cloud Sleuth to trace calls through our application. We've also learned how to include those trace IDs in error responses sent back to the client, as well as including the trace ID as an HTTP header to propagate it through other clients.</p>
<p>Till next time!</p>
]]></content:encoded></item><item><title><![CDATA[Using Jackson Subtypes to Write Better Code]]></title><description><![CDATA[In this post, we'll look at how Jackson Subtypes can automatically map JSON to the right subclass, without having to write your own converter.First, we'll give some background into why this is useful, and then we'll go through some practical examples...]]></description><link>https://stacktobasics.com/jackson-sub-types</link><guid isPermaLink="true">https://stacktobasics.com/jackson-sub-types</guid><category><![CDATA[Java]]></category><category><![CDATA[jackson]]></category><category><![CDATA[json]]></category><category><![CDATA[REST API]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Fri, 29 Apr 2022 15:54:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683216109630/c3601d17-97ce-47f6-bf99-6b1c654ff4b8.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this post, we'll look at how Jackson Subtypes can automatically map JSON to the right subclass, without having to write your own converter.<br />First, we'll give some background into why this is useful, and then we'll go through some practical examples for two different use cases.</p>
<h1 id="heading-why-are-jackson-subtypes-useful">Why are Jackson Subtypes Useful?</h1>
<p>You'll sometimes find that you receive JSON HTTP responses that look similar but have a few fields which are different. If you have to map these to Java classes then it can be annoying to have to replicate all of the common fields across each of your DTO classes.</p>
<p>Let's look at an example of the problem.</p>
<p>Let's say you get a response from a service that looks like this:</p>
<pre><code class="lang-json">[
  {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"1"</span>,
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"onlineOrder"</span>,
    <span class="hljs-attr">"quantity"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">"order"</span>: {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Playstation 5"</span>,
      <span class="hljs-attr">"onlineCode"</span>: <span class="hljs-string">"123456"</span>,
      <span class="hljs-attr">"referralId"</span>: <span class="hljs-string">"6789"</span>,
      <span class="hljs-attr">"customerId"</span>: <span class="hljs-string">"5"</span>
    }
  },
  {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"2"</span>,
    <span class="hljs-attr">"type"</span>: <span class="hljs-string">"shopOrder"</span>,
    <span class="hljs-attr">"quantity"</span>: <span class="hljs-number">3</span>,
    <span class="hljs-attr">"order"</span>: {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Xbox One"</span>,
      <span class="hljs-attr">"shopId"</span>: <span class="hljs-string">"2"</span>,
      <span class="hljs-attr">"paymentType"</span>: <span class="hljs-string">"card"</span>
    }
  }
]
</code></pre>
<p>We can see that the two objects in the list look similar, but both have a different <code>order</code> object format.<br />We could map these to DTO objects using the following class:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">GenericOrderDTO</span> </span>{
    <span class="hljs-keyword">private</span> String id;
    <span class="hljs-keyword">private</span> String type;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> quantity;
    <span class="hljs-keyword">private</span> Object order;

    <span class="hljs-comment">// getters and setters omitted</span>
}
</code></pre>
<p>This will work, but it won't give us any indication about what is inside the order object, plus we'll have to do some casting.<br />We could also use <code>Map&lt;String, String&gt;</code> instead to represent the order object, which will give us a map of field name to value.<br />In either case, we would need to write our own mapper to figure out if the order is a shop type or an online type and map the fields accordingly.</p>
<p>A better way to do this is to use Jackson's <code>@JsonTypeInfo</code> and <code>@JsonSubTypes</code> annotations with inheritance.</p>
<h1 id="heading-how-jackson-subtypes-work">How Jackson Subtypes Work</h1>
<p>Jackson has a concept of subtypes, which is similar to a subclass.</p>
<p>With inheritance you have your parent class which contains common fields across each of your subclasses, and your subclasses which contain fields unique to them. Subtypes work in a similar way, with the help of some annotations.</p>
<p>In order to use subtypes we need to have some property in the JSON that tells us which subtype to use. In our example above we have the <code>type</code> field, which states whether the <code>order</code> object is an online order or a shop order. Another way is when the type field is inside the subtype itself.</p>
<p>Let's look at both.</p>
<h2 id="heading-type-field-is-on-the-root-object">Type Field is on the Root Object</h2>
<p>Using our example above, we can map the JSON to the following DTOs:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonTypeInfo;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderDTO</span> </span>{
    <span class="hljs-keyword">private</span> String id;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> quantity;
    <span class="hljs-keyword">private</span> String type;
    <span class="hljs-meta">@JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.EXTERNAL_PROPERTY,
            property = "type",
            defaultImpl = UnknownTypeOrderDTO.class
    )</span>
    <span class="hljs-keyword">private</span> OrderTypeDTO orderTypeDTO;

    <span class="hljs-comment">// getters and setters omitted</span>
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonSubTypes;

<span class="hljs-meta">@JsonSubTypes({
        @JsonSubTypes.Type(value = OnlineTypeOrderDTO.class, name = "onlineOrder"),
        @JsonSubTypes.Type(value = ShopTypeOrderDTO.class, name = "shopOrder")
})</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderTypeDTO</span> </span>{
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OnlineTypeOrderDTO</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">OrderTypeDTO</span> </span>{
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> String referralId;
    <span class="hljs-keyword">private</span> String customerId;

    <span class="hljs-comment">// getters and setters omitted</span>
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ShopTypeOrderDTO</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">OrderTypeDTO</span> </span>{
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> String shopId;
    <span class="hljs-keyword">private</span> String paymentType;

    <span class="hljs-comment">// getters and setters omitted</span>
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonIgnoreProperties;

<span class="hljs-meta">@JsonIgnoreProperties(ignoreUnknown = true)</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">UnknownTypeOrderDTO</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">OrderTypeDTO</span> </span>{

}
</code></pre>
<p>Looking at <code>OrderTypeDTO</code> first, we have added an annotation that lists what class should map to what name.</p>
<p>In the <code>OrderDTO</code> class, we include an annotation that tells Jackson to map the <code>orderTypeDTO</code> to the class where the value of the <code>type</code> field matches the name specified in the <code>@JsonSubTypes</code> annotation.<br />We also state that the <code>type</code> field is an external property. That is, it's not a field on <code>OrderTypeDTO</code>, but rather on <code>OrderDTO</code>.<br />Finally, we state that if Jackson can't find a matching class based on the value of <code>type</code>, then use <code>UnknownTypeOrderDTO</code> instead. This is protect us from exceptions where the order endpoint might add a new type before we change our code. You can then have some code that appropriately deals with any orders that map to <code>UnknownTypeOrderDTO</code>.</p>
<h2 id="heading-type-field-is-on-the-subtype-object">Type field is on the Subtype Object</h2>
<p>Another way is when the type field is on the subtype object itself.<br />Let's change our example slightly such that the JSON we receive looks like this:</p>
<pre><code class="lang-json">[
  {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"1"</span>,
    <span class="hljs-attr">"quantity"</span>: <span class="hljs-number">1</span>,
    <span class="hljs-attr">"order"</span>: {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Playstation 5"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"onlineOrder"</span>,
      <span class="hljs-attr">"onlineCode"</span>: <span class="hljs-string">"123456"</span>,
      <span class="hljs-attr">"referralId"</span>: <span class="hljs-string">"6789"</span>,
      <span class="hljs-attr">"customerId"</span>: <span class="hljs-string">"5"</span>
    }
  },
  {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"2"</span>,
    <span class="hljs-attr">"quantity"</span>: <span class="hljs-number">3</span>,
    <span class="hljs-attr">"order"</span>: {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Xbox One"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"shopOrder"</span>,
      <span class="hljs-attr">"shopId"</span>: <span class="hljs-string">"2"</span>,
      <span class="hljs-attr">"paymentType"</span>: <span class="hljs-string">"card"</span>
    }
  }
]
</code></pre>
<p>In this case, our DTOs would look like this:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonTypeInfo;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderDTO</span> </span>{
    <span class="hljs-keyword">private</span> String id;
    <span class="hljs-keyword">private</span> <span class="hljs-keyword">int</span> quantity;
    <span class="hljs-meta">@JsonTypeInfo(
            use = JsonTypeInfo.Id.NAME,
            property = "type",
            defaultImpl = UnknownTypeOrderDTO.class
    )</span>
    <span class="hljs-keyword">private</span> OrderTypeDTO orderTypeDTO;

    <span class="hljs-comment">// getters and setters omitted</span>
}
</code></pre>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> com.fasterxml.jackson.annotation.JsonSubTypes;

<span class="hljs-meta">@JsonSubTypes({
        @JsonSubTypes.Type(value = OnlineTypeOrderDTO.class, name = "onlineOrder"),
        @JsonSubTypes.Type(value = ShopTypeOrderDTO.class, name = "shopOrder")
})</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">OrderTypeDTO</span> </span>{
    <span class="hljs-keyword">private</span> String type;
}
</code></pre>
<p>The <code>ShopTypeOrderDTO</code>, <code>OnlineTypeOrderDTO</code>, and <code>UnknownTypeOrderDTO</code> classes are exactly the same as before.</p>
<p>The difference here is that the <code>type</code> field has moved to the <code>OrderTypeDTO</code> class, and <code>include =</code> <a target="_blank" href="http://JsonTypeInfo.As"><code>JsonTypeInfo.As</code></a><code>.EXTERNAL_PROPERTY</code> was removed from the <code>@JsonTypeInfo</code> annotation.</p>
<h1 id="heading-conclusion">Conclusion</h1>
<p>In this post, we've expressed when and why Subtypes are useful, as well as learned how to use them when you have the type inside or outside of the subclass through practical examples.</p>
<p>Till next time!</p>
]]></content:encoded></item><item><title><![CDATA[13 Reasons To Move From Java 8 To Java 11]]></title><description><![CDATA[Despite Java 11 being released in September 2018, 64% of developers report that Java 8 remains the most often used release.
A further 27% of people said that there are "no features you need in later versions".
In this blog post, I will go over why I ...]]></description><link>https://stacktobasics.com/java-11</link><guid isPermaLink="true">https://stacktobasics.com/java-11</guid><category><![CDATA[Java]]></category><category><![CDATA[java8]]></category><category><![CDATA[java11]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Sat, 16 May 2020 15:40:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683215460061/f918794b-775d-410e-bd8e-04745f862fd1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Despite Java 11 being released in September 2018, <a target="_blank" href="https://snyk.io/blog/developers-dont-want-to-leave-java-8-as-64-hold-firm-on-their-preferred-release/">64% of developers report that Java 8 remains the most often used release</a>.</p>
<p>A further 27% of people said that there are "no features you need in later versions".</p>
<p>In this blog post, I will go over why I think this is wrong by showing some of the new exciting features post Java 8!</p>
<h1 id="heading-changes-to-the-release-model">Changes to the Release Model</h1>
<p>Before we get started, let's discuss briefly the changes to Java's release model.</p>
<p>In the past, Java hasn't had particularly fast release times. Java 6 came out in December 2006, Java 7 wasn't released until July 2011, and Java 8 wasn't released until March 2014! These were big bang changes and followed a waterfall pattern. If a feature didn't make it into the release you could be waiting quite a few years before you'd see it.</p>
<p>This is something Oracle has changed with Java 9 going forward. Releases now have a 6-month cadence, with a Long Term Support (LTS) release happening every 3 years. This is why you hear people talk about Java 11 rather than 9, 10, or 12. Java 11 was the first release with LTS since Java 8. The next release with LTS will be Java 17.</p>
<p>Whilst you probably won't migrate to the versions in between Java 11 and Java 17 (at least not on enterprise applications), you can definitely check out the JDKs in between and play around with the features!</p>
<h1 id="heading-java-9">Java 9</h1>
<h2 id="heading-no-more-java-ee">No More Java EE</h2>
<p>JavaEE is no longer part of the core JDK. JavaEE included features such as:</p>
<ul>
<li><p>XML Bindings</p>
</li>
<li><p>JPA</p>
</li>
<li><p>Dependency Injection</p>
</li>
</ul>
<p>This means that the JDK is smaller, and frees up Oracle to focus on features that improve the language, rather than the surrounding tools.</p>
<p>When you migrate from Java 8 to Java 11, most likely this will be your biggest pain point. You'll see errors such as <code>ClassNotDef</code> or <code>MethodNotFound</code>. There are ways to get around <a target="_blank" href="https://www.jesperdj.com/2018/09/30/jaxb-on-java-9-10-11-and-beyond/">this</a> without making many changes, but it's still a pain.</p>
<p>So what has happened to all of the Java EE features?<br />Eclipse is now handling them! JavaEE has been rebranded as Jakarta EE, and can be read about <a target="_blank" href="https://jakarta.ee/">here</a>. This is a good thing, as Eclipse is focusing on making Java a Cloud Native platform, something that Oracle couldn't keep up with.</p>
<h2 id="heading-a-better-http-client">A Better HTTP Client</h2>
<p>Not many people use the default <code>HttpURLConnection</code> API in Java. Most people will use Apache HttpClient, Jetty, or RestTemplate. This is because <code>HttpUrlConnection</code> isn't very user-friendly.</p>
<p>However, in Java 9, a new HTTP client was included which is much easier to use.</p>
<p>The new API has 3 core classes:</p>
<ul>
<li><p><code>HttpRequest</code></p>
</li>
<li><p><code>HttpClient</code></p>
</li>
<li><p><code>HttpResponse</code></p>
</li>
</ul>
<p>The new HTTP client supports:</p>
<ul>
<li><p>both synchronous and asynchronous requests</p>
</li>
<li><p>HTTP/2 and HTTP/1.1 compatibility</p>
</li>
</ul>
<p>HTTP/2 is a big step up from HTTP/1.1, which was last released in 1997. HTTP/3 is getting more support now as well, so hopefully we should see support for this in Java in the future too.</p>
<p>Here's an example of setting up an HTTP client using HTTP2:</p>
<pre><code class="lang-java">HttpClient client = HttpClient.newBuilder()
      .version(Version.HTTP_2)
      .build();
</code></pre>
<p>and to send a request asynchronously:</p>
<pre><code class="lang-java">HttpRequest request = HttpRequest.newBuilder()
      .uri(URI.create(<span class="hljs-string">"http://openjdk.java.net/"</span>))
      .build();
client.sendAsync(request, BodyHandlers.ofString())
      .thenApply(HttpResponse::body)
      .thenAccept(System.out::println)
      .join();
</code></pre>
<p>Simple, right?</p>
<h2 id="heading-finally-a-repl">Finally a REPL</h2>
<p>REPL = <strong>R</strong>ead <strong>E</strong>val <strong>P</strong>rint <strong>L</strong>oop<br />A lot of other languages have REPLs and now finally Java does too, with its REPL called jShell. If you don't know what a REPL is, Python is a good example. You can type <code>python</code> into your command prompt to start the language shell and execute each line you write.</p>
<p>The Java REPL is most useful for seeing the output of small amounts of code, or playing with a new feature.</p>
<p>Why is jShell easier than creating your own class and experimenting with an IDE?</p>
<ul>
<li><p>you don't have to write complete Java code</p>
</li>
<li><p>You don't need to declare classes if you don't want to</p>
</li>
<li><p>you don't need to include sem-colons</p>
</li>
<li><p>You can define variables, methods, classes, imports, and expressions, and you can also completely overwrite any of these.</p>
</li>
</ul>
<p>It's also really easy to use:</p>
<pre><code class="lang-bash">&gt; jshell
|  Welcome to JShell -- Version 11
|  For an introduction <span class="hljs-built_in">type</span>: /<span class="hljs-built_in">help</span> intro
jshell&gt; String myStr = <span class="hljs-string">"hello stack to basics!"</span>
myStr ==&gt; <span class="hljs-string">"hello stack to basics!"</span>

jshell&gt; myStr
myStr ==&gt; <span class="hljs-string">"hello stack to basics!"</span>

jshell&gt; myStr + <span class="hljs-string">" How are you?"</span>
<span class="hljs-variable">$3</span> ==&gt; <span class="hljs-string">"hello stack to basics! How are you?"</span>

jshell&gt; <span class="hljs-variable">$3</span>
<span class="hljs-variable">$3</span> ==&gt; <span class="hljs-string">"hello stack to basics! How are you?"</span>
</code></pre>
<p>This article <a target="_blank" href="https://developers.redhat.com/blog/2019/04/05/10-things-developers-need-to-know-about-jshell/">here</a> shows some of the things you can do with jShell.</p>
<h2 id="heading-emojis4j">Emojis4J</h2>
<p>Unicode 7 is here in Java, which means you can finally use emojis! (This feature is not really called Emojis4J, but I wish it was).<br />We have to be careful here though. Lets look at an a exmaple:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Emojis4J</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        String bear = <span class="hljs-string">"🐻"</span>;
        <span class="hljs-keyword">char</span> bearChar = bear.charAt(<span class="hljs-number">0</span>);

        String aString = <span class="hljs-string">"a"</span>;
        <span class="hljs-keyword">char</span> aChar = aString.charAt(<span class="hljs-number">0</span>);

        System.out.println(<span class="hljs-string">"bear: "</span> + bear)
        System.out.println(<span class="hljs-string">"bearChar: "</span> + aChar);
        System.out.println(<span class="hljs-string">"a: "</span> + bearChar);
    }
}
</code></pre>
<p>This prints out:</p>
<pre><code class="lang-plaintext">bear: 🐻
bearChar: ?
a: a
</code></pre>
<p>The bear char didn't print properly. Why not?</p>
<p>This is because emojis are 5 digit hex numbers. Each hex number needs 4 bits, therefore a 5 digit hex number needs 5x4=20 bits, with the bear emoji represented by <code>\u1f43b</code>. A java character is 2 bytes=2x8 bits = 16 bits. Not big enough to hold an emoji!<br />To get around this you can represent emojis with two 16 bit hexadecimal values. The bear is represented with the string <code>\ud83d\udc3b</code>.<br />These are called surrogate pairs, and are used to solve the problem in languages where chars are not big enough to hold some unicode characters.<br />We can convert the bear to 2 chars properly like so:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Emojis4J</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        String bear = <span class="hljs-string">"🐻"</span>;
        <span class="hljs-keyword">int</span> bearCodepoint = bear.codePointAt(bear.offsetByCodePoints(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>));
        <span class="hljs-keyword">char</span>[] bearSurrogates = {Character.highSurrogate(bearCodepoint),
                Character.lowSurrogate(bearCodepoint)};
        System.out.println(<span class="hljs-string">"Value of bearSurrogates: "</span> +
                String.valueOf(bearSurrogates));
    }
}
</code></pre>
<p>This prints:</p>
<pre><code class="lang-plaintext">Value of bearSurrogates: 🐻
</code></pre>
<p>That's better.</p>
<h2 id="heading-smarter-strings">Smarter Strings</h2>
<p>In Java strings are stored as an array of chars, with each char taking 2 bytes. However, <a target="_blank" href="https://openjdk.java.net/jeps/254">research</a> shows two things:</p>
<ul>
<li><p>strings are a major component of heap usage</p>
</li>
<li><p>most String objects contain only Latin-1 characters.</p>
</li>
</ul>
<p>Latin-1 characters only require 1 byte.</p>
<p>This feature adds an encoding flag to Strings which, based on the content of the string, will use either two bytes per character or one byte.</p>
<p>This can save you a lot of space!</p>
<h2 id="heading-factory-methods-for-collections">Factory methods for collections</h2>
<p>Finally, java offers a standard way to instantiate collections.<br />We also now have immutable collections which implement the Collection interface but don't implement any mutable methods.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">JavaCollections</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-comment">//Immutable Collections:</span>
        <span class="hljs-comment">// Empty List</span>
        List.of();

        <span class="hljs-comment">// Easy initialisation of list, array-based List </span>
        <span class="hljs-comment">// implementation (if more than 2 elements, check out the docs )</span>
        <span class="hljs-keyword">final</span> List&lt;String&gt; immutableList = List.of(<span class="hljs-string">"String1"</span>, <span class="hljs-string">"String2"</span>);
        <span class="hljs-comment">// The line below would throw the exception: Exception in thread </span>
        <span class="hljs-comment">// "main" java.lang.UnsupportedOperationException:</span>
        <span class="hljs-comment">// immutableList.add("String3");</span>

        <span class="hljs-comment">// Empty map</span>
        Map.of();

        <span class="hljs-comment">// Easy initialisation of map, array-based Map implementation</span>
        Map.of(<span class="hljs-string">"My key1"</span>, <span class="hljs-number">1</span>, <span class="hljs-string">"My Key2"</span>, <span class="hljs-number">2</span>);

        <span class="hljs-comment">// Mutable Collections</span>
        <span class="hljs-comment">// Not great, this way might end up creating up to 3 arrays</span>
        List&lt;String&gt; mutableList = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;(List.of(<span class="hljs-string">"String1"</span>));

        <span class="hljs-comment">// This will work</span>
        mutableList.add(<span class="hljs-string">"String4"</span>);
    }
}
</code></pre>
<h2 id="heading-experimenting-with-ahead-of-time-compilation">Experimenting with Ahead-of-Time compilation</h2>
<p>Another new feature of Java is Ahead-of-Time compilation.</p>
<p>To understand why this is an exciting addition, let's look at the way most JVMs roughly work:</p>
<ul>
<li><p>byte code is executed by the JVM</p>
</li>
<li><p>The JVM will either:</p>
<ul>
<li><p>interpret the byte code</p>
</li>
<li><p>compile it to machine code using what is known as the Just-In-Time (JIT) compiler</p>
<ul>
<li>with HotSpot JVMs they'll only use JIT if some piece of code is used enough</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>This is great, but it can mean that for large programs the JIT can take a long time to "warm up", and code may never be compiled at all (so it will execute slower).</p>
<p>Ahead-of-Time compilation allows us to compile Java classes to native code prior to launching the JVM. This would mean that our program should run a lot faster, as there is no interpretation or compilation happening whilst the program is running.</p>
<p>As an example, let's try and compile the following class:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassForAOT</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        System.out.println(<span class="hljs-string">"Hello from StackToBasics"</span>);
    }
}
</code></pre>
<p>We can compile this using AOT like so:</p>
<pre><code class="lang-plaintext">&gt;javac MyClassForAOT.java
&gt;jaotc --output MyClassForAOT.so MyClassForAOT.class
&gt;java -XX:AOTLibrary=./MyClassForAOT.so MyClassForAOT
Hello from StackToBasics
</code></pre>
<p>AOT compilation could be particularly useful for cloud service, especially when using AWS Lambdas or Google's Cloud Functions, where faster start-up time and having a smaller code footprint is important.</p>
<p>A lot more can be said on AOT compilation, but that shall be saved for a separate post 😃</p>
<h1 id="heading-java-10">Java 10</h1>
<h2 id="heading-finally-the-var-keyword">Finally, the var keyword</h2>
<p>Type inference is now extended to the declaration of local variables with initialisers.</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TypeInference</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-keyword">var</span> hello = <span class="hljs-string">"Hello Stack to Basics!"</span>;
        <span class="hljs-comment">// hello = 5; This will not work, hello's type has been inferred as string, it cannot be changed to int</span>
        System.out.println(hello);
    }
}
</code></pre>
<p>A misconception is that type inference == dynamically typed. This is not true. Each variable still requires a type, it is just that the type can be inferred rather than explicitly stated. Java is still a statically-typed language.</p>
<p>Remember that type inference already existed in Java, when using lambdas. We can see below that we did not need to declare the type for <code>b</code>, rather it was inferred.</p>
<pre><code class="lang-java"><span class="hljs-keyword">int</span> maxWeight = blocks.steam()
    .filter(b -&gt; b.getColor() == BLUE)
    .mapToInt(Block::getWeight)
    .max();
</code></pre>
<h1 id="heading-dogfooding-their-own-language">Dogfooding Their Own Language</h1>
<p>Before Java 9, the JIT compiler was written in C++. Not just C++, but a specific dialect of C++, which made it difficult for developers to make improvements.</p>
<p>Now they've finally switched to a different language and they're using... you guessed it, Java.</p>
<p>This is still in the early experimental stages, but wouldn't it be great to have a JIT compiler that developers can improve?</p>
<h2 id="heading-var-for-lambdas">Var for Lambdas</h2>
<p>Following on from local variable type inference, we can now use <code>var</code> in lambdas:</p>
<pre><code class="lang-java">(<span class="hljs-keyword">var</span> s1, <span class="hljs-keyword">var</span>  s2) -&gt; s1 + s2
</code></pre>
<p>Which is equivalent to the following:</p>
<pre><code class="lang-java">(String s1, String s2) -&gt; s1 + s2 <span class="hljs-comment">//or</span>
(s1, s2) -&gt; s1 + s2
</code></pre>
<p>Why bother with <code>var</code> for lambdas? Well, we can now do things like this which we couldn't before (without explicitly declaring <code>s1</code> and <code>s2</code> as strings):</p>
<pre><code class="lang-java">(<span class="hljs-meta">@Nonnull</span> <span class="hljs-keyword">var</span> s1, <span class="hljs-meta">@Nullable</span> <span class="hljs-keyword">var</span> s2) -&gt; s1 + s2
</code></pre>
<h2 id="heading-emojis4j2">Emojis4J2</h2>
<p>There are 56 more emojis!<br />You can now display the bitcoin symbol. How exciting.</p>
<h2 id="heading-launching-single-file-programs">Launching Single-File Programs</h2>
<p>You can now run single-file java programs without compiling them, with any arguments placed after the file name being treated as program arguments. As example, lets say we have the following class:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Factorial</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-keyword">if</span>(args.length != <span class="hljs-number">1</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"Please provide 1 number"</span>);

        <span class="hljs-keyword">int</span> number = Integer.parseInt(args[<span class="hljs-number">0</span>]);
        System.out.println(<span class="hljs-string">"Answer: "</span> + calculateFactorial(number));
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">int</span> <span class="hljs-title">calculateFactorial</span><span class="hljs-params">(<span class="hljs-keyword">int</span> number)</span> </span>{
        <span class="hljs-keyword">if</span>(number &lt; <span class="hljs-number">0</span>) <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> RuntimeException(<span class="hljs-string">"number must be positive"</span>);

        <span class="hljs-keyword">if</span>(number == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
        <span class="hljs-keyword">if</span>(number == <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
        <span class="hljs-keyword">if</span>(number == <span class="hljs-number">2</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">2</span>;
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> calculateFactorial(number-<span class="hljs-number">1</span>) * number;
    }
}
</code></pre>
<p>Normally we would have to compile this to byte code and then run it, like so:</p>
<pre><code class="lang-plaintext">&gt;javac Factorial.java
&gt;java Factorial 5
Answer: 120
</code></pre>
<p>But now, we can skip out the first step:</p>
<pre><code class="lang-plaintext">&gt;java Factorial.java 5
Answer: 120
</code></pre>
<h2 id="heading-epsilon-the-garbage-collector-for-aws-lambda-and-google-cloud-functions">Epsilon, The Garbage Collector for AWS Lambda and Google Cloud Functions?</h2>
<p>A new gabage collector has been added! ... Which doesn't collect any garbage.</p>
<p>It handles memory allocation but does not implement any actual memory reclamation mechanism. Once the heap is exhausted, the JVM will shut down.</p>
<p>The main usages of this will most likely be for:</p>
<ul>
<li><p>testing how your program uses memory</p>
</li>
<li><p>(Extremely) short-lived jobs, such as lambdas.</p>
</li>
</ul>
<h1 id="heading-conclusion">Conclusion</h1>
<p>This wraps up some of my favourite features of Java 9 to 11. I'm excited for the future of Java, and it's great to see some well-requested features finally making it into the JDK. Hopefully, this blog convinces you to explore some of these new features and take the plunge to upgrade to Java 11 if you haven't already.</p>
<p>Happy Coding!</p>
]]></content:encoded></item><item><title><![CDATA[Mockito Cheatsheet]]></title><description><![CDATA[Mockito is a great framework for testing in java. I use it all the time and have done for many years now. It works hand in hand with dependnecy injection, a topic I covered in my last blog "Spring for Humans". However I sometimes find it's a victim o...]]></description><link>https://stacktobasics.com/mockito-cheatsheet</link><guid isPermaLink="true">https://stacktobasics.com/mockito-cheatsheet</guid><category><![CDATA[Java]]></category><category><![CDATA[mockito]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Mon, 04 May 2020 15:53:25 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683214541713/5cd2b270-6954-49eb-a4e8-d9c268d3a82e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Mockito is a great framework for testing in java. I use it all the time and have done for many years now. It works hand in hand with dependnecy injection, a topic I covered in my last blog <a target="_blank" href="https://stacktobasics.com/spring-for-humans">"Spring for Humans"</a>. However I sometimes find it's a victim of it's own success - there's a lot you can do with it so it's easy to forget how to do things!</p>
<p>So here's a cheat sheet which covers most of the features I use in Mockito.</p>
<h1 id="heading-setting-up-mockito">Setting Up Mockito</h1>
<h2 id="heading-using-junit-andlt5-annotations">Using JUnit &lt;5 Annotations</h2>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.junit.MockitoJUnitRunner;
<span class="hljs-keyword">import</span> org.junit.runner.RunWith;

<span class="hljs-meta">@RunWith(MockJUnitRunner.class)</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{
    ...
}
</code></pre>
<h2 id="heading-using-junit-5-annotations">Using JUnit 5 Annotations</h2>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.junit.jupiter.MockitoExtension;
<span class="hljs-keyword">import</span> org.junit.jupiter.api.extension.ExtendWith;

<span class="hljs-meta">@ExtendWith(MockitoExtension.class)</span>
<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{
    ...
}
</code></pre>
<h2 id="heading-using-initmocks">Using initMocks</h2>
<p>Useful if you need more than one RunWith/Extend (e.g. <code>SpringJunit4ClassRunner</code>)</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.MockitoAnnotations;
<span class="hljs-keyword">import</span> org.junit.Before;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{
    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">before</span><span class="hljs-params">()</span> throw Exception </span>{
        Mockito.initMocks(<span class="hljs-keyword">this</span>);
    }
    ...
}
</code></pre>
<h1 id="heading-creating-mocks-and-spies">Creating Mocks and Spies</h1>
<p>An easy way I use to remember the difference between mocks and spies is:</p>
<ul>
<li><p>Mock: By default, all methods are stubbed unless you say otherwise.</p>
</li>
<li><p>Spy: By default, all methods use real implementation unless you say otherwise.</p>
</li>
</ul>
<h2 id="heading-creating-a-mock">Creating a Mock</h2>
<h3 id="heading-use-annotations-on-fields">Use Annotations on Fields</h3>
<p>There seems to be a consensus that this is the cleanest way to mock an object (which goes against my brain's aversion for field injection, but that's a story for another day).</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.MockitoAnnotations;
<span class="hljs-keyword">import</span> org.junit.Before;
<span class="hljs-keyword">import</span> org.mockito.Mock;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{

    <span class="hljs-meta">@Mock</span>
    <span class="hljs-keyword">private</span> MyClass myObject;

    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">before</span><span class="hljs-params">()</span> throw Exception </span>{
        Mockito.initMocks(<span class="hljs-keyword">this</span>);
        <span class="hljs-comment">// myObject is now a mock</span>
    }
    ...
}
</code></pre>
<h3 id="heading-create-from-method">Create from Method</h3>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.MockitoAnnotations;
<span class="hljs-keyword">import</span> org.junit.Before;
<span class="hljs-keyword">import</span> org.mockito.Mock;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{

    <span class="hljs-keyword">private</span> MyClass myObject;

    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">before</span><span class="hljs-params">()</span> throw Exception </span>{
        Mockito.initMocks(<span class="hljs-keyword">this</span>);
        myObject = Mockito.mock(MyClass.class);
    }
    ...
}
</code></pre>
<h3 id="heading-create-from-method-using-initialised-object">Create from Method using Initialised Object</h3>
<p>Useful if you want a mock to be created from an object which has been constructed using a constructor.</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.MockitoAnnotations;
<span class="hljs-keyword">import</span> org.junit.Before;
<span class="hljs-keyword">import</span> org.mockito.Mock;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{

    <span class="hljs-keyword">private</span> Person person = Person(<span class="hljs-string">"name"</span>, <span class="hljs-number">18</span>);

    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">before</span><span class="hljs-params">()</span> throw Exception </span>{
        Mockito.initMocks(<span class="hljs-keyword">this</span>);
        person = Mockito.mock(person);
    }
    ...
}
</code></pre>
<h2 id="heading-creating-a-spy">Creating a Spy</h2>
<p>This is very similar to mocks.</p>
<h3 id="heading-use-annotations-on-fields-1">Use Annotations on Fields</h3>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.MockitoAnnotations;
<span class="hljs-keyword">import</span> org.junit.Before;
<span class="hljs-keyword">import</span> org.mockito.Spy;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{

    <span class="hljs-meta">@Spy</span>
    <span class="hljs-keyword">private</span> MyClass myObject;

    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">before</span><span class="hljs-params">()</span> throw Exception </span>{
        Mockito.initMocks(<span class="hljs-keyword">this</span>);
        <span class="hljs-comment">// myObject is now a spy</span>
    }
    ...
}
</code></pre>
<h3 id="heading-create-from-method-1">Create from Method</h3>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.MockitoAnnotations;
<span class="hljs-keyword">import</span> org.junit.Before;
<span class="hljs-keyword">import</span> org.mockito.Spy;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{

    <span class="hljs-keyword">private</span> MyClass myObject;

    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">before</span><span class="hljs-params">()</span> throw Exception </span>{
        Mockito.initMocks(<span class="hljs-keyword">this</span>);
        myObject = Mockito.spy(MyClass.class);
    }
    ...
}
</code></pre>
<h3 id="heading-create-from-method-using-initialised-object-1">Create from method using Initialised Object</h3>
<p>Useful if you want a spy to be created from an object which has been constructed using a constructor.</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.mockito.MockitoAnnotations;
<span class="hljs-keyword">import</span> org.junit.Before;
<span class="hljs-keyword">import</span> org.mockito.Spy;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyClassTest</span> </span>{

    <span class="hljs-keyword">private</span> Person person = Person(<span class="hljs-string">"name"</span>, <span class="hljs-number">18</span>);

    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">before</span><span class="hljs-params">()</span> throw Exception </span>{
        Mockito.initMocks(<span class="hljs-keyword">this</span>);
        person = Mockito.spy(person);
    }
    ...
}
</code></pre>
<h1 id="heading-injecting-mocks">Injecting Mocks</h1>
<h2 id="heading-using-injectmocks-annotation">Using @InjectMocks Annotation</h2>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InjectTest</span> </span>{

    <span class="hljs-meta">@Mock</span>
    <span class="hljs-keyword">private</span> BlogRepository blogRepository;
    <span class="hljs-meta">@InjectMocks</span>
    <span class="hljs-keyword">private</span> BlogPostService blogPostService;

    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{
        MockitoAnnotations.initMocks(<span class="hljs-keyword">this</span>);
        System.out.println(blogPostService);
    }
}
</code></pre>
<h2 id="heading-using-constructor">Using Constructor</h2>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">InjectTest</span> </span>{

    <span class="hljs-meta">@Mock</span>
    <span class="hljs-keyword">private</span> BlogRepository blogRepository;
    <span class="hljs-meta">@InjectMocks</span>
    <span class="hljs-keyword">private</span> BlogPostService blogPostService;

    <span class="hljs-meta">@Before</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setup</span><span class="hljs-params">()</span> </span>{
        MockitoAnnotations.initMocks(<span class="hljs-keyword">this</span>);
        blogPostService = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);
    }
}
</code></pre>
<h1 id="heading-stubbing-a-method">Stubbing a method</h1>
<h2 id="heading-returning-from-a-stubbed-method">Returning from a stubbed Method</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getAllBlogPosts_withBlogPostsInDb_returnsBlogPosts</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    List&lt;BlogPost&gt; expected = Arrays.asList(<span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for humans"</span>), 
            <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Mockito Cheatsheet"</span>));
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    Mockito.when(blogRepository.getAllBlogPosts()).thenReturn(expected);
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    List&lt;BlogPost&gt; actual = service.getAllBlogPosts();

    <span class="hljs-comment">// assert</span>
    assertEquals(expected, actual);
}
</code></pre>
<h2 id="heading-providing-an-alternative-implementation-for-a-method">Providing an Alternative Implementation for a Method</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostById_withExistingBlogPostInDb_returnsBlogPost</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    BlogPost expected = <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for Humans"</span>);
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    Mockito.when(blogRepository.getBlogPostById(Mockito.anyInt())).thenAnswer(invocationOnMock -&gt; {
        <span class="hljs-keyword">int</span> id = invocationOnMock.getArgument(<span class="hljs-number">0</span>);
        <span class="hljs-keyword">if</span>(id == <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span> Optional.of(expected);
        <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> Optional.empty();
    });
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    BlogPost actual = service.getBlogPostById(<span class="hljs-number">1</span>).get();

    <span class="hljs-comment">// assert</span>
    assertEquals(expected, actual);
}
</code></pre>
<h2 id="heading-throwing-an-exception-from-a-method">Throwing an Exception from a Method</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test(expected = IllegalArgumentException.class)</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostById_withMissingBlogPost_throwsException</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    BlogPost expected = <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for Humans"</span>);
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    Mockito.when(blogRepository.getBlogPostById(<span class="hljs-number">1</span>)).thenThrow(<span class="hljs-keyword">new</span> IllegalArgumentException());
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    BlogPost actual = service.getBlogPostById(<span class="hljs-number">1</span>).get();

    <span class="hljs-comment">// assert</span>
    <span class="hljs-comment">// Test will pass if exception of type IllegalArgumentException is thrown</span>
}
</code></pre>
<h2 id="heading-calling-the-real-implementation">Calling the Real Implementation</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostById_withMissingBlogPost_returnsEmpty</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    Mockito.when(blogRepository.getBlogPostById(<span class="hljs-number">1</span>)).thenCallRealMethod();
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    Optional&lt;BlogPost&gt; actual = service.getBlogPostById(<span class="hljs-number">1</span>);

    <span class="hljs-comment">// assert</span>
    assertFalse(actual.isPresent());
}
</code></pre>
<h2 id="heading-using-deep-stubs">Using Deep Stubs</h2>
<p>Deep stubs are used for methods which are chained, but you don't care about the intermediate values. These should be used sparingly, as discussed in the <a target="_blank" href="https://www.javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/Mockito.html#RETURNS_DEEP_STUBS">Mockito documentation</a>.</p>
<pre><code class="lang-java"><span class="hljs-comment">// Without deep stubs</span>
<span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">checkIfDriverIsPostgres_withPostgresDriver_returnsTrue</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    Configuration configMock = Mockito.mock(Configuration.class);
    RepositoryConfiguration repositoryConfigMock = Mockito.mock(RepositoryConfiguration.class);
    Mockito.when(configMock.getRepositoryConfiguration()).thenReturn(repositoryConfigMock);
    DatabaseConfiguration databaseConfigMock = Mockito.mock(DatabaseConfiguration.class);
    Mockito.when(repositoryConfigMock.getDatabaseConfiguration()).thenReturn(databaseConfigMock);
    DatabaseConfiguration.DriverConfiguration driverConfigMock = Mockito.mock(DatabaseConfiguration.DriverConfiguration.class);
    Mockito.when(databaseConfigMock.getDriverConfiguration()).thenReturn(driverConfigMock);
    Mockito.when(driverConfigMock.getDriverType()).thenReturn("postgres");

    BlogRepository blogRepository = <span class="hljs-keyword">new</span> BlogRepository(configMock);

    <span class="hljs-comment">// act</span>
    <span class="hljs-keyword">boolean</span> actual = blogRepository.checkIfDriverIsPostgres();

    <span class="hljs-comment">// assert</span>
    Assert.assertTrue(actual);
}

<span class="hljs-comment">// With deep stubs</span>
<span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">usingDeepStubs_checkIfDriverIsPostgres_withPostgresDriver_returnsTrue</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    Configuration configMock = Mockito.mock(Configuration.class, Mockito.RETURNS_DEEP_STUBS);
    Mockito.when(configMock.getRepositoryConfiguration()
            .getDatabaseConfiguration().getDriverConfiguration().getDriverType()).thenReturn("postgres");

    BlogRepository blogRepository = <span class="hljs-keyword">new</span> BlogRepository(configMock);

    <span class="hljs-comment">// act</span>
    <span class="hljs-keyword">boolean</span> actual = blogRepository.checkIfDriverIsPostgres();

    <span class="hljs-comment">// assert</span>
    Assert.assertTrue(actual);
}
</code></pre>
<h1 id="heading-stubbing-a-void-method">Stubbing a Void Method</h1>
<h2 id="heading-providing-an-alternative-implementation-for-a-method-1">Providing an Alternative Implementation for a Method</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getAllBlogPosts_withBlogPostsInDb_callsVerifyConnectionToDatabaseIsAlive</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    Configuration configMock = Mockito.mock(Configuration.class);
    BlogRepository blogRepository = <span class="hljs-keyword">new</span> BlogRepository(configMock);

    AtomicBoolean verifyMethodCalled = <span class="hljs-keyword">new</span> AtomicBoolean(<span class="hljs-keyword">false</span>);
    Mockito.doAnswer(invocationOnMock -&gt; {
        <span class="hljs-comment">// We can do whatever we want here, and it will be executed when</span>
        <span class="hljs-comment">// verifyConnectionToDatabaseIsAlive</span>
        <span class="hljs-comment">// If the method had any arguments, we can capture them here</span>
        verifyMethodCalled.set(<span class="hljs-keyword">true</span>);
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
    }).when(configMock).verifyConnectionToDatabaseIsAlive();

    <span class="hljs-comment">// act</span>
    blogRepository.getAllBlogPosts();

    <span class="hljs-comment">// assert</span>
    assertTrue(verifyMethodCalled.get());
}
</code></pre>
<h2 id="heading-throwing-an-exception-from-a-method-1">Throwing an Exception from a Method</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test(expected = DatabaseDownException.class)</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getAllBlogPosts_withConnectionToDbDown_throwsException</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    Configuration configMock = Mockito.mock(Configuration.class);
    BlogRepository blogRepository = <span class="hljs-keyword">new</span> BlogRepository(configMock);

    AtomicBoolean verifyMethodCalled = <span class="hljs-keyword">new</span> AtomicBoolean(<span class="hljs-keyword">false</span>);
    Mockito.doThrow(<span class="hljs-keyword">new</span> DatabaseDownException()).when(configMock).verifyConnectionToDatabaseIsAlive();

    <span class="hljs-comment">// act</span>
    blogRepository.getAllBlogPosts();

    <span class="hljs-comment">// assert</span>
    <span class="hljs-comment">// Test will pass if exception of type DatabaseDownException is thrown</span>
}
</code></pre>
<h2 id="heading-calling-the-real-implementation-1">Calling the Real Implementation</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getAllBlogPosts_withBlogPostsInDb_returnsBlogPosts</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    Configuration configMock = Mockito.mock(Configuration.class);
    BlogRepository blogRepository = <span class="hljs-keyword">new</span> BlogRepository(configMock);

    AtomicBoolean verifyMethodCalled = <span class="hljs-keyword">new</span> AtomicBoolean(<span class="hljs-keyword">false</span>);
    Mockito.doCallRealMethod().when(configMock).verifyConnectionToDatabaseIsAlive();

    <span class="hljs-comment">// act</span>
    blogRepository.getAllBlogPosts();

    <span class="hljs-comment">// assert</span>
    <span class="hljs-comment">// Test will pass if exception of type DatabaseDownException is thrown</span>
}
</code></pre>
<h1 id="heading-matchers">Matchers</h1>
<h2 id="heading-using-a-real-parameter">Using a Real Parameter</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostByAuthorAndAfterDate_withExistingBlogPostsInDb_returnsBlogPosts</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    List&lt;BlogPost&gt; expected = Collections.singletonList(<span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for Humans"</span>));
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    Date date = <span class="hljs-keyword">new</span> Date();
    String author = <span class="hljs-string">"Shane"</span>;
    Mockito.when(blogRepository.getBlogPostsByAuthorAndAfterDate(author, date)).thenReturn(expected);
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    List&lt;BlogPost&gt; actual = service.getBlogPostsByAuthorAndAfterDate(author, date);

    <span class="hljs-comment">// assert</span>
    assertEquals(expected, actual);
}
</code></pre>
<h2 id="heading-using-any">Using any()</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostByAuthorAndAfterDate_withoutMatchingBlogPostsInDb_returnsEmptyList</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    Mockito.when(blogRepository.getBlogPostsByAuthorAndAfterDate(Mockito.any(), Mockito.any())).thenReturn(Collections.emptyList());
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    List&lt;BlogPost&gt; actual = service.getBlogPostsByAuthorAndAfterDate(<span class="hljs-string">"any author"</span>, <span class="hljs-keyword">new</span> Date());

    <span class="hljs-comment">// assert</span>
    assertTrue(actual.isEmpty());
}
</code></pre>
<h2 id="heading-using-anyclass">Using anyClass()</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostByAuthorAndAfterDate_withMatchingAuthorAndDate_returnsPostsSortedByDate</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    BlogPost first = <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for Humans"</span>, <span class="hljs-string">"Shane"</span>, <span class="hljs-keyword">new</span> Date(<span class="hljs-number">5</span>));
    BlogPost second = <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Mockito Cheatsheet"</span>, <span class="hljs-string">"Shane"</span>, <span class="hljs-keyword">new</span> Date(<span class="hljs-number">6</span>));
    BlogPost third = <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"?"</span>, <span class="hljs-string">"Shane"</span>, <span class="hljs-keyword">new</span> Date(<span class="hljs-number">7</span>));
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    // Good <span class="hljs-keyword">for</span> overloaded methods, you can specify type of params so the call isn't ambiguous.
    Mockito.when(blogRepository.getBlogPostsByAuthorAndAfterDate(Mockito.any(String.class), Mockito.any(Date.class)))
            .thenReturn(Arrays.asList(second, third, first));
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    List&lt;BlogPost&gt; expected = Arrays.asList(first, second, third);

    <span class="hljs-comment">// act</span>
    List&lt;BlogPost&gt; actual = service.getBlogPostsByAuthorAndAfterDate(<span class="hljs-string">"any author"</span>, <span class="hljs-keyword">new</span> Date());

    <span class="hljs-comment">// assert</span>
    assertEquals(expected.get(<span class="hljs-number">0</span>), actual.get(<span class="hljs-number">0</span>));
    assertEquals(expected.get(<span class="hljs-number">1</span>), actual.get(<span class="hljs-number">1</span>));
    assertEquals(expected.get(<span class="hljs-number">2</span>), actual.get(<span class="hljs-number">2</span>));
}
</code></pre>
<h2 id="heading-using-eq">Using eq()</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostByAuthorAndAfterDate_withMatchingAuthorButFutureDate_returnsEmptyList</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    List&lt;BlogPost&gt; expected = Collections.singletonList(<span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for Humans"</span>));
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    Date date = <span class="hljs-keyword">new</span> Date();
    Mockito.when(blogRepository.getBlogPostsByAuthorAndAfterDate(Mockito.any(), Mockito.eq(date))).thenReturn(expected);
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    List&lt;BlogPost&gt; actual = service.getBlogPostsByAuthorAndAfterDate(<span class="hljs-string">"any author"</span>, date);

    <span class="hljs-comment">// assert</span>
    assertTrue(actual.isEmpty());
}
</code></pre>
<h2 id="heading-using-custom-matcher">Using Custom Matcher</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">checkIfBlogPostHasBeenSaved_withBlogPost_returnsTrue</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    BlogPost post = <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for Humans"</span>, <span class="hljs-string">"Shane"</span>, <span class="hljs-keyword">new</span> Date(<span class="hljs-number">5</span>));
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);

    ArgumentMatcher&lt;BlogPost&gt; blogPostMatcher = passedBlogPost -&gt;
            "Shane".equals(passedBlogPost.getAuthor());

    Mockito.when(blogRepository.checkIfBlogPostHasBeenSaved(Mockito.argThat(blogPostMatcher)))
            .thenReturn(<span class="hljs-keyword">true</span>);
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    <span class="hljs-keyword">boolean</span> actual = service.checkIfBlogPostHasBeenSaved(post);

    <span class="hljs-comment">// assert</span>
    assertTrue(actual);
}
</code></pre>
<h1 id="heading-verify-a-stubbed-method-has-been-called">Verify a Stubbed Method Has Been Called</h1>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getAllBlogPosts_withBlogPost_verifiesThatRepositoryWasCalled</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    List&lt;BlogPost&gt; expected = Arrays.asList(<span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for humans"</span>),
            <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Mockito Cheatsheet"</span>));
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    Mockito.when(blogRepository.getAllBlogPosts()).thenReturn(expected);
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    service.getAllBlogPosts();

    <span class="hljs-comment">// assert</span>
    Mockito.verify(blogRepository).getAllBlogPosts();
}
</code></pre>
<h1 id="heading-verify-a-stubbed-method-hasnt-been-called">Verify a Stubbed Method Hasn't Been Called</h1>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostById_withBlogPost_verifiesThatCorrectMethodWasCalled</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    BlogPost expoected = <span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for humans"</span>);
    Mockito.when(blogRepository.getBlogPostById(<span class="hljs-number">1</span>)).thenReturn(Optional.of(expoected));
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);

    <span class="hljs-comment">// act</span>
    service.getBlogPostById(<span class="hljs-number">1</span>);

    <span class="hljs-comment">// assert</span>
    Mockito.verify(blogRepository, Mockito.never()).getBlogPostById(<span class="hljs-number">100</span>);
    Mockito.verify(blogRepository).getBlogPostById(<span class="hljs-number">1</span>);
}
</code></pre>
<h2 id="heading-capturing-arguments">Capturing Arguments</h2>
<pre><code class="lang-java"><span class="hljs-meta">@Test</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostById_withBlogPost_verifiesCorrectArgumentPassed</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">// arrange</span>
    BlogRepository blogRepository = Mockito.mock(BlogRepository.class);
    <span class="hljs-keyword">int</span> expected = <span class="hljs-number">1</span>;
    Mockito.when(blogRepository.getBlogPostById(expected))
            .thenReturn(Optional.of(<span class="hljs-keyword">new</span> BlogPost(<span class="hljs-string">"Spring for humans"</span>)));
    BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(blogRepository);
    ArgumentCaptor&lt;Integer&gt; captor = ArgumentCaptor.forClass(Integer.class);

    // act
    service.getBlogPostById(expected);
    Mockito.verify(blogRepository).getBlogPostById(captor.capture());
    <span class="hljs-keyword">int</span> actual = captor.getValue();

    // <span class="hljs-function"><span class="hljs-keyword">assert</span>
    <span class="hljs-title">assertEquals</span><span class="hljs-params">(expected, actual)</span></span>;
}
</code></pre>
<h1 id="heading-conclusion">Conclusion</h1>
<p>Hopefully the snippets laid out here will help you quickly set up your mockito tests. The full examples can be seen <a target="_blank" href="https://github.com/Zinbo/blog-examples">here on GitHub</a>.</p>
<p>Happy Coding!</p>
]]></content:encoded></item><item><title><![CDATA[Spring For Humans]]></title><description><![CDATA[I remember the first week of my first ever development job, my team lead came to me and said "We use Spring and Hibernate here, you'll need to learn them". I looked at him blankly, but thought "Hey, I'm sure if I Google this I'll figure it out in no ...]]></description><link>https://stacktobasics.com/spring-for-humans</link><guid isPermaLink="true">https://stacktobasics.com/spring-for-humans</guid><category><![CDATA[Springboot]]></category><category><![CDATA[Spring]]></category><category><![CDATA[Spring framework]]></category><category><![CDATA[Java]]></category><category><![CDATA[Beginner Developers]]></category><dc:creator><![CDATA[Shane Jennings]]></dc:creator><pubDate>Sun, 26 Apr 2020 15:51:48 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1683101110282/543152bf-f703-4deb-b03c-0fde4372e4ca.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I remember the first week of my first ever development job, my team lead came to me and said "We use Spring and Hibernate here, you'll need to learn them". I looked at him blankly, but thought "Hey, I'm sure if I Google this I'll figure it out in no time, I'm a University student, I'm smart right?".</p>
<p>I tried googling for Spring and came across blog post after blog post - I saw lots of mentions of words like "Dependency Injection" and "Inversion of Control". I also saw reams of XML which made no sense to me.</p>
<p>What I realised was that the people who were writing these blog posts were undeniably smart - but could not explain concepts to people who didn't understand even the basics of the concept.</p>
<p>So this is my goal for this blog post: explain Spring to you like a human. I want to answer the questions I had when I first started looking at Spring.</p>
<h2 id="heading-what-is-spring-anyway">What is Spring Anyway?</h2>
<p>When people say "Spring" (assuming they are talking about software development and not DIY or Slinkys) they usually mean the <a target="_blank" href="https://spring.io/projects/spring-framework">Spring Framework</a>. Over the past few years it's possible that people may now refer more to <a target="_blank" href="https://spring.io/projects/spring-boot">Spring Boot</a> when they say "Spring", but let's start with the basics first.</p>
<p>The Spring framework "provides a comprehensive programming and configuration model for modern Java-based enterprise applications". So what does that mean? Basically, the Spring framework allows you to create an application without having to worry about how it's all tied together. The Spring framework is comprised of a few different parts:</p>
<ul>
<li><p>Core</p>
</li>
<li><p>Testing</p>
</li>
<li><p>Data Access</p>
</li>
<li><p>Web Framework</p>
</li>
<li><p>Integration</p>
</li>
<li><p>Language Support</p>
</li>
</ul>
<p>In this post, we'll focus on Core, and specifically dependency management.</p>
<h2 id="heading-spring-core">Spring Core</h2>
<h3 id="heading-dependency-injection">Dependency Injection</h3>
<p>A big part of Spring Core is this idea of dependency injection. Dependency injection is a design pattern that makes a class independent of its dependencies (its variables).</p>
<p>Let's look at an example:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BlogPostService</span> </span>{

    <span class="hljs-keyword">private</span> PostgresDatabaseRepository databaseRepository 
        = <span class="hljs-keyword">new</span> PostgresDatabaseRepository();

    <span class="hljs-function"><span class="hljs-keyword">public</span> BlogPost <span class="hljs-title">getBlogPostById</span><span class="hljs-params">(String id)</span> </span>{
        <span class="hljs-keyword">return</span> databaseRepository.getBlogPostById(id);
    }
}


<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostgresDatabaseRepository</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PostgresDatabaseRepository</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-comment">// connect to our database here</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> BlogPost <span class="hljs-title">getBlogPostById</span><span class="hljs-params">(String id)</span> </span>{
        <span class="hljs-comment">// We would go to our database here and</span>
        <span class="hljs-comment">// get our blog post</span>
        BlogPost blogPost = <span class="hljs-keyword">new</span> BlogPost();
        blogPost.setId(id);
        blogPost.setContent(<span class="hljs-string">"my blog post content"</span>);
        blogPost.setName(<span class="hljs-string">"Spring for Humans"</span>);
        <span class="hljs-keyword">return</span> blogPost;
    }
}


<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BlogPost</span> </span>{
    <span class="hljs-keyword">private</span> String id;
    <span class="hljs-keyword">private</span> String name;
    <span class="hljs-keyword">private</span> String content;

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getName</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> name;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setName</span><span class="hljs-params">(String name)</span> </span>{
        <span class="hljs-keyword">this</span>.name = name;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getContent</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> content;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setContent</span><span class="hljs-params">(String content)</span> </span>{
        <span class="hljs-keyword">this</span>.content = content;
    }
}
</code></pre>
<p>Here we have a class BlogPostService which gets and returns a blog post by id from the database, by using DatabaseService. We have a few problems here:</p>
<ol>
<li><p>We can't unit test BlogPostService independently of PostgresDatabaseRepository</p>
</li>
<li><p>BlogPostService is coupled to PostgresDatabaseRepository - what if we wanted to use MySQLDatabaseRepository instead? We would need to change our implementation of BlogPostService, which could then break our implementation and our test. You might think this is fine for one class, but what if we have 50 service classes that all need to change to use MySqlDatabaseRepository?</p>
</li>
</ol>
<p>In fact, our implementation here breaks the "D" in the <a target="_blank" href="https://en.wikipedia.org/wiki/SOLID">SOLID principles</a>, where D is the Dependency inversion principle. This states that one should "depend upon abstractions, not concretions". This sounds fancy, but all it means is that our BlogPostService shouldn't depend on the class PostgresDatabaseRepository, which is a "concretion".</p>
<p>So what can we do to get around these problems? Dependency Injection to the rescue!</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BlogPostService</span> </span>{

    <span class="hljs-keyword">private</span> DatabaseRepository databaseRepository;

    <span class="hljs-comment">// here BlogPostService's dependency is injected in</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">BlogPostService</span><span class="hljs-params">(DatabaseRepository databaseRepository)</span> </span>{
        <span class="hljs-keyword">this</span>.databaseRepository = databaseRepository;
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> BlogPost <span class="hljs-title">getBlogPostById</span><span class="hljs-params">(String id)</span> </span>{
        <span class="hljs-keyword">return</span> databaseRepository.getBlogPostById(id);
    }
}


<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">PostgresDatabaseRepository</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DatabaseRepository</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">PostgresDatabaseRepository</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-comment">// connect to our database here</span>
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> BlogPost <span class="hljs-title">getBlogPostById</span><span class="hljs-params">(String id)</span> </span>{
        <span class="hljs-comment">// We would go to our database here and</span>
        <span class="hljs-comment">// get our blog post</span>
        BlogPost blogPost = <span class="hljs-keyword">new</span> BlogPost();
        blogPost.setId(id);
        blogPost.setContent(<span class="hljs-string">"my blog post content"</span>);
        blogPost.setName(<span class="hljs-string">"Spring for Humans"</span>);
        <span class="hljs-keyword">return</span> blogPost;
    }
}


<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">interface</span> <span class="hljs-title">DatabaseRepository</span> </span>{
    <span class="hljs-function">BlogPost <span class="hljs-title">getBlogPostById</span><span class="hljs-params">(String id)</span></span>;
}
</code></pre>
<p>Now we can easily unit test our BlogPostService like so:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">BlogPostServiceTest</span> </span>{

    <span class="hljs-meta">@Test</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">getBlogPostById_withValidId_returnsBlogPost</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-comment">// arrange</span>
        BlogPost expected = <span class="hljs-keyword">new</span> BlogPost();
        String id = <span class="hljs-string">"123"</span>;
        expected.setId(id);
        BlogPostService service = <span class="hljs-keyword">new</span> BlogPostService(
            <span class="hljs-keyword">new</span> MockDatabaseRepository(expected));

        <span class="hljs-comment">// act</span>
        BlogPost actual = service.getBlogPostById(id);

        <span class="hljs-comment">// assert</span>
        Assert.assertEquals(expected, actual);
    }

    <span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MockDatabaseRepository</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">DatabaseRepository</span> </span>{

        <span class="hljs-keyword">private</span> BlogPost expected;

        <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MockDatabaseRepository</span><span class="hljs-params">(BlogPost expected)</span> </span>{
            <span class="hljs-keyword">super</span>();
            <span class="hljs-keyword">this</span>.expected = expected;
        }

        <span class="hljs-function"><span class="hljs-keyword">public</span> BlogPost <span class="hljs-title">getBlogPostById</span><span class="hljs-params">(String id)</span> </span>{
            <span class="hljs-keyword">if</span>(id.equals(expected.getId())) <span class="hljs-keyword">return</span> expected;
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
        }
    }
}
</code></pre>
<p>Also, if we have a class MySqlDatabaseRepository which also implements DatabaseRepository then we can pass that class instead to the BlogService constructor. Success!</p>
<p>That's great and all, but if BlogPostService is no longer responsible for managing it's dependencies, then who is?</p>
<p>Well, we could always manage our dependencies ourselves like so:</p>
<pre><code class="lang-java"><span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        BlogPostService blogPostService = DependencyContainer.getBlogPostService();
        blogPostService.getBlogPostById(<span class="hljs-string">"123"</span>);
    }
}

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DependencyContainer</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> BlogPostService <span class="hljs-title">getBlogPostService</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> BlogPostService(getDatabaseRepository());
    }

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> DatabaseRepository <span class="hljs-title">getDatabaseRepository</span><span class="hljs-params">()</span> </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> PostgresDatabaseRepository();
    }
}
</code></pre>
<p>This works, however, it can get unwieldy. Especially if we have many dependencies and we want our dependencies to be Singletons (which we will do for classes like PostgresDatabaseRepository, we only need 1).</p>
<p>So what can we do?</p>
<h3 id="heading-spring-to-the-rescue">Spring to the Rescue</h3>
<p>This is where the beauty of Spring comes in. Spring Core contains what is called the "Inversion of Control container" (more on the term "Inversion of Control" later). This container is represented by the org.springframework.context.ApplicationContext interface and is responsible for instantiating, configuring, and assembling our dependencies. It does this by reading metadata in the form of XML, Java annotations, or Java code. I would strongly recommend <strong>not</strong> using XML - it's outdated and much harder to read and debug.</p>
<p>So let's see how we can do the above with Spring.</p>
<p>First, let's add a pom.xml to include our Spring <a target="_blank" href="http://maven.apache.org/guides/getting-started/maven-in-five-minutes.html">maven</a> dependencies:</p>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;?xml version="1.0" encoding="UTF-8"?&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0"</span>
         <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span>
         <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>com.stacktobasics<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>Spring-For-Humans<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>1.0-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">dependencies</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-core<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>4.3.10.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-context<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>4.3.10.RELEASE<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">dependency</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.glassfish.hk2.external<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>javax.inject<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>2.5.0-b32<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">dependency</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">dependencies</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">project</span>&gt;</span>
</code></pre>
<p>Next, lets add our Spring configuration class:</p>
<pre><code class="lang-java"><span class="hljs-keyword">import</span> org.springframework.context.annotation.AnnotationConfigApplicationContext;
<span class="hljs-keyword">import</span> springforhumans.withspring.SpringConfiguration;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-comment">// Set up Spring</span>
        AnnotationConfigApplicationContext context = <span class="hljs-keyword">new</span> AnnotationConfigApplicationContext();
        context.register(SpringConfiguration.class);
        context.refresh();

        BlogPostService blogPostService = context.getBean(BlogPostService.class);
        BlogPost blogpost = blogPostService.getBlogPostById("<span class="hljs-number">123</span><span class="hljs-string">");
        System.out.println(String.format("</span>Blog post id=<span class="hljs-string">'%s'</span> name=<span class="hljs-string">'%s'</span> content=<span class="hljs-string">'%s'</span><span class="hljs-string">", blogpost.getId(), blogpost.getName(), blogpost.getContent()));
    }
}

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springforhumans.BlogPostService;
import springforhumans.DatabaseRepository;
import springforhumans.PostgresDatabaseRepository;

@Configuration
public class SpringConfiguration {

    @Bean
    public BlogPostService blogPostService(DatabaseRepository databaseRepository) {
        return new BlogPostService(databaseRepository);
    }

    @Bean
    public DatabaseRepository databaseRepository() {
        return new PostgresDatabaseRepository();
    }
}


public class BlogPostService {

    private DatabaseRepository databaseRepository;

    public BlogPostService(DatabaseRepository databaseRepository) {

        this.databaseRepository = databaseRepository;
    }

    public BlogPost getBlogPostById(String id) {
        return databaseRepository.getBlogPostById(id);
    }
}

/** All other classes have same implementation **/</span>
</code></pre>
<p>Let's unpack what's going on here:</p>
<ul>
<li><p>We create a configuration class called SpringConfiguration which has the annotation @Configuration. This tells Spring that it is a dependency configuration class.</p>
</li>
<li><p>The SpringConfiguration sets up our beans - don't be worried about this term, bean just means a <a target="_blank" href="https://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html">standard Java object</a>.</p>
</li>
<li><p>Spring automatically knows which DatabaseRepository implementation to pass into the blogPostService() method, as we already defined our DatabaseRepository bean - Spring intelligently figures out the dependency wiring order.</p>
</li>
<li><p>we can get our beans by doing context.getBean(MyClass.class)</p>
</li>
<li><p>Spring creates each of these beans as singletons by default, meaning that each time you call context.getBean(MyClass.class) you'll get the same object of that class.</p>
</li>
</ul>
<p>Hmmm, this doesn't look like much less work than our implementation above. Let's look at another way of doing this with Spring:</p>
<pre><code class="lang-java"><span class="hljs-keyword">package</span> springforhumans;

<span class="hljs-keyword">import</span> org.springframework.context.annotation.AnnotationConfigApplicationContext;

<span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Main</span> </span>{

    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
        <span class="hljs-comment">// Set up Spring</span>
        AnnotationConfigApplicationContext context = <span class="hljs-keyword">new</span> AnnotationConfigApplicationContext(<span class="hljs-string">"springforhumans"</span>);

        BlogPostService blogPostService = context.getBean(BlogPostService.class);
        BlogPost blogpost = blogPostService.getBlogPostById("<span class="hljs-number">123</span><span class="hljs-string">");
        System.out.println(String.format("</span>Blog post id=<span class="hljs-string">'%s'</span> name=<span class="hljs-string">'%s'</span> content=<span class="hljs-string">'%s'</span><span class="hljs-string">", blogpost.getId(), blogpost.getName(), blogpost.getContent()));
    }
}

@Component
public class BlogPostService {

    private DatabaseRepository databaseRepository;

    public BlogPostService(DatabaseRepository databaseRepository) {

        this.databaseRepository = databaseRepository;
    }

    public BlogPost getBlogPostById(String id) {
        return databaseRepository.getBlogPostById(id);
    }
}

@Component
public class PostgresDatabaseRepository implements DatabaseRepository {

    public PostgresDatabaseRepository() {
        // connect to our database here
    }

    public BlogPost getBlogPostById(String id) {
        // We would go to our database here and
        // get our blog post
        BlogPost blogPost = new BlogPost();
        blogPost.setId(id);
        blogPost.setContent("</span>my blog post content<span class="hljs-string">");
        blogPost.setName("</span>Spring <span class="hljs-keyword">for</span> Humans<span class="hljs-string">");
        return blogPost;
    }
}</span>
</code></pre>
<p>This looks better! Let's again unpack what's happening here:</p>
<ul>
<li><p>We got rid of the SpringConfiguration class.</p>
</li>
<li><p>We instead specified our base package to the AnnotationConfigApplicationContext constructor. This scans for any class which contains the @Component annotation.</p>
</li>
<li><p>We added @Component to our classes.</p>
</li>
</ul>
<p>Now in this example, it doesn't look like we've saved ourselves much effort by using Spring - however, the benefit is more obvious when we have more dependencies to manage - trying to manage our dependencies can quickly become a mess.</p>
<p>It is important to note the price that comes with using @Component. Our classes now have a dependency on Spring, as they import the @Component annotation, which they did not have to do when we were using the SpringConfiguration class. Realistically you're not likely to switch to a different dependency Injection container in the same project, but it's important to be aware of nonetheless.</p>
<p>It is also important to note that using base package scanning with @Component and using SpringConfiguration are not mutually exclusive. We can use both in the same project and I regularly do. SpringConfiguration is great if your bean requires a more complex setup, which often happens when setting up Database connectors.</p>
<h3 id="heading-when-should-i-not-use-dependency-injection">When should I not use dependency injection?</h3>
<p>I'm sure you'll agree that dependency injection looks great - let's use it everywhere! Don't do that. Like everything dependency injection has its place. Most often dependency injection is suitable for classes that are:</p>
<ul>
<li><p>stateless</p>
</li>
<li><p>will be instantiated as a singleton (but they don't have to be, see <a target="_blank" href="https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#beans-factory-scopes">here</a> for details.)</p>
</li>
<li><p>Will be reused</p>
</li>
<li><p>services, database connectors, and infrastructure connectors (e.g. REST Controllers).</p>
</li>
</ul>
<p>A bad candidate for dependency injection is our BlogPost class - objects of BlogPost will contain a unique state for each blog post and will not be singletons. It would not make sense to try to inject BlogPosts into other classes.</p>
<h3 id="heading-dependency-injection-vs-inversion-of-control">Dependency Injection Vs Inversion of Control</h3>
<p>You might have heard of the term "dependency Injection" (DI) and "inversion of Control" (IoC) floating around. Often people say that they are the same thing, however, I think of them as different concepts.</p>
<p>In terms of Spring:</p>
<ul>
<li><p><strong>IoC</strong> - the container which inhibits the ability to use dependency injection. For me, the ApplicationContext <strong>is</strong> the IoC Container, which follows the pattern of the framework calling the application, rather than the application calling the framework (so the application is not dependent on a framework).</p>
</li>
<li><p><strong>DI</strong> - The act of a class allowing its dependencies to be injected, rather than having the responsibility to instantiate its dependencies.</p>
</li>
</ul>
<h2 id="heading-why-would-i-need-spring">Why would I need Spring?</h2>
<p>Hopefully, I have already answered this question!</p>
<p>Spring is great for managing dependencies as already discussed. However, the Spring framework contains many more features than just dependency injection. Remember the list of Spring Framework features I listed in <a class="post-section-overview" href="#what-is-spring-anyway">What is Spring anyway?</a>. The beauty of Spring is that the more you use its features the more benefits you get as, unsurprisingly, the creators of Spring have made sure that their projects play together nicely.</p>
<p>And it's not just Spring Core. Spring has a multitude of different projects. Some of these include:</p>
<ul>
<li><p><a target="_blank" href="https://spring.io/projects/spring-boot">Spring Boot</a>: Easily create standalone Spring applications that are wired together automatically, by using the @SpringBootApplication annotation. Often used to create web applications but can also be used as stand-alone publishers, subscribers, and more.</p>
</li>
<li><p><a target="_blank" href="https://spring.io/projects/spring-data">Spring Data</a>: Easily create repository interfaces for your ORM objects, including automatically deriving queries from your method names.</p>
</li>
<li><p><a target="_blank" href="https://spring.io/projects/spring-cloud">Spring Cloud</a>: Provides tools for building cloud-ready applications.</p>
</li>
</ul>
<p>This is just to name a few, more can be seen on the <a target="_blank" href="https://spring.io/projects">Spring website</a>.</p>
<h2 id="heading-what-are-some-alternatives-to-spring">What are some alternatives to Spring?</h2>
<p>Spring is a big topic, but let's list some alternatives to the Spring Framework and Spring Boot.</p>
<p>Alternatives to Spring Framework (DI):</p>
<ul>
<li><p><a target="_blank" href="https://github.com/google/guice">Guice</a></p>
</li>
<li><p><a target="_blank" href="https://dagger.dev/">Dagger</a></p>
</li>
</ul>
<p>Alternatives to Spring Boot:</p>
<ul>
<li><p><a target="_blank" href="https://micronaut.io/">Micronaut</a></p>
</li>
<li><p><a target="_blank" href="https://www.dropwizard.io/en/latest/">Dropwizard</a></p>
</li>
<li><p><a target="_blank" href="https://quarkus.io/">Quarkus</a></p>
</li>
</ul>
<p>I think the one to look out for is Micronaut - It's getting a lot of traction in the Java community due to its compatibility with <a target="_blank" href="https://www.graalvm.org/">GraalVM</a> and its lack of use of Reflection.</p>
<h2 id="heading-what-youve-learned">What you've learned</h2>
<p>Well done for sticking to the end!</p>
<p>Spring can appear overwhelming and confusing, and indeed it is a big subject. Much bigger than this blog post has room for. However, like with anything, start small and build your knowledge over time.</p>
<p>I would recommend getting your head around using Spring for dependency injection by trying the examples laid out here in this blog yourself and then giving Spring Boot a go. Spring's guide on creating a <a target="_blank" href="https://spring.io/guides/gs/rest-service/">RESTful web service</a> is a good place to start.</p>
<p>Happy Coding!</p>
]]></content:encoded></item></channel></rss>