DEV Community: ajcwebdevThe latest articles on DEV Community by ajcwebdev (@ajcwebdev).
https://dev.to/ajcwebdev
https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F350465%2F210bfd0e-0eac-4dec-89dd-f95f9d19cc04.pngDEV Community: ajcwebdev
https://dev.to/ajcwebdev
enA First Look at SolidStartajcwebdevWed, 23 Nov 2022 19:22:49 +0000
https://dev.to/ajcwebdev/a-first-look-at-solidstart-2hm7
https://dev.to/ajcwebdev/a-first-look-at-solidstart-2hm7<h2>
Outline
</h2>
<ul>
<li>
Introduction
<ul>
<li>A History of SolidJS and How it Compares to React</li>
<li>SolidJS Benchmark Performance</li>
<li>SolidStart Motivations</li>
</ul>
</li>
<li>
Create Client Rendered Solid Project
<ul>
<li>TypeScript and Vite Project Configuration</li>
<li>HTML Entry, CSS Styling, and Render Function</li>
<li>Start Development Server</li>
</ul>
</li>
<li>
Migrate Project to SolidStart
<ul>
<li>SolidStart Scripts and Vite Configuration</li>
<li>Index Route, Root, and Entry Points</li>
</ul>
</li>
<li>
Components and Reactive Primitives
<ul>
<li>Create Signal</li>
<li>Create Effect</li>
<li>Create Route Data</li>
</ul>
</li>
<li>API Routes</li>
<li>
Deployment Adapters
<ul>
<li>Deploy to Netlify</li>
<li>Deploy to Vercel</li>
<li>Deploy to Cloudflare</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong><em>All of this project's code can be found in the <a href="proxy.php?url=https://github.com/ajcwebdev/a-first-look/tree/main/frontend/solidstart/" rel="noopener noreferrer">First Look monorepo</a> on my GitHub.</em></strong></p>
</blockquote>
<h2>
Introduction
</h2>
<p>On <a href="proxy.php?url=https://twitter.com/solid_js/status/1590432218308292608/" rel="noopener noreferrer">November 9, 2022</a>, Ryan Carniato published <a href="proxy.php?url=https://www.solidjs.com/blog/introducing-solidstart" rel="noopener noreferrer">Introducing SolidStart: The SolidJS Framework</a> to announce the beta launch of SolidStart, the eagerly awaited metaframework for SolidJS. SolidStart provides a first-party project starter and metaframework for SolidJS. It prescribes a recommended way to organize, architect, and deploy SolidJS applications.</p>
<p>Vite is included for the build tool along with extra Solid packages for common functionality and a CLI for generating templated projects. These packages enable features such as routing, MetaTags, different rendering modes, TypeScript support, and deployment adapters. In addition to <a href="proxy.php?url=https://github.com/solidjs/solid-start/tree/main/packages/start-node" rel="noopener noreferrer">Node</a> and <a href="proxy.php?url=https://github.com/solidjs/solid-start/tree/main/packages/start-static" rel="noopener noreferrer">static hosting</a>, adapters currently exist for the following platforms:</p>
<ul>
<li><a href="proxy.php?url=https://github.com/solidjs/solid-start/tree/main/packages/start-netlify" rel="noopener noreferrer">Netlify Functions and Edge</a></li>
<li><a href="proxy.php?url=https://github.com/solidjs/solid-start/tree/main/packages/start-vercel" rel="noopener noreferrer">Vercel Functions and Edge</a></li>
<li>Cloudflare <a href="proxy.php?url=https://github.com/solidjs/solid-start/tree/main/packages/start-cloudflare-workers" rel="noopener noreferrer">Workers</a> and <a href="proxy.php?url=https://github.com/solidjs/solid-start/tree/main/packages/start-cloudflare-pages" rel="noopener noreferrer">Pages</a>
</li>
<li><a href="proxy.php?url=https://github.com/solidjs/solid-start/tree/main/packages/start-deno" rel="noopener noreferrer">Deno Deploy</a></li>
</ul>
<h3>
A History of SolidJS and How it Compares to React
</h3>
<p>Before diving into SolidStart, it's worth taking a moment to outline the history and motivation behind the creation of Solid. Branded as "a reactive JavaScript library for building user interfaces," <a href="proxy.php?url=https://github.com/solidjs/solid/tree/a194f02e3df560f0e75d96a96f9df6631285199e" rel="noopener noreferrer">Ryan open sourced the framework on April 24, 2018</a>. It was designed as a spiritual successor to the reactive programming model exemplified by KnockoutJS.</p>
<blockquote>
<p><em>React wasn’t the first JavaScript “Just a Render Library”. I attribute that honor to a much older library, KnockoutJS. Knockout didn’t have components but identified an application was built from 3 parts, <code>Model</code>, <code>ViewModel</code>, and <code>View</code>, and only cared about how you organized the latter. The <code>ViewModel</code> was a revolutionary concept.</em></p>
<p><em><code>ViewModels</code> are instances much like components. But what set Knockout apart was <code>ViewModels</code> could be anything you wanted; an object, a function, a class. There were no lifecycle functions. You could bring your own models and organize your application as you saw fit. Without best practices it could be a complete mess.</em></p>
<p><em>But it was truly “Just a Render Library.” Those boundaries haven’t changed in over a decade... As <code>Controllers</code> transformed to <code>Routers</code>, <code>Models</code> to <code>Stores</code>, and <code>ViewModels</code>/<code>Views</code> got wrapped together as <code>Components</code>, the anatomy of a Component (even in a smaller library like React) is still 3 main parts:</em></p>
<ul>
<li><em>Container</em></li>
<li><em>Change (Local State) Manager</em></li>
<li><em>Renderer</em></li>
</ul>
<p><em>Ryan Carniato</em> - <strong><em><a href="proxy.php?url=https://ryansolid.medium.com/b-y-o-f-part-1-writing-a-js-framework-in-2018-b02a41026929" rel="noopener noreferrer">B.Y.O.F. Writing a JS Framework in 2018 (November 10, 2018)</a></em></strong></p>
</blockquote>
<p>As the 2010s progressed, Ryan believed the JavaScript world had moved on from using composable reactive primitives in favor of class components and lifecycle methods. Dissatisfied with this direction, Ryan aimed for Solid to be a more modern reactive framework inspired by Knockout but with features informed by newer component frameworks like Angular, React, and Vue.</p>
<p>Shortly after the framework's release, React introduced hooks. Their debut in <a href="proxy.php?url=https://www.youtube.com/watch?v=dpw9EHDh2bM" rel="noopener noreferrer"><em>React Today and Tomorrow and 90% Cleaner React With Hooks, October 26, 2018</em></a> became a pivotal moment for Solid. React hooks are functions that can access React state and lifecycle methods from functional components. These functions can be composed into more complex UIs much like Solid.</p>
<p>Within a few years, the majority of React developers would be developing with composable, functional patterns. This ensured that React developers would find Solid easily comprehensible. But despite the surface level similarities between Solid and React's syntax, Solid has a few key advantages over React due to its underlying implementation.</p>
<p>Solid removes the need for some of React's more complex hooks that patch over the leaky abstraction underlying React. <code>useCallback</code> exists to give React developers a mechanism for preventing rerenders. But in Solid, components only mount once and don't rerender so there is no need for an equivalent hook.</p>
<h3>
SolidJS Benchmark Performance
</h3>
<p>Solid is also one of the most performant JavaScript libraries. This is evidenced by the results of the <a href="proxy.php?url=https://github.com/krausest/js-framework-benchmark" rel="noopener noreferrer">JS Framework Benchmark</a>. A large, randomized table of entries is created and modified. Rendering duration is measured along with how long various operations take to complete (lower scores are better).</p>
<p>While admittedly a contrived example, the benchmark allows factoring multiple measurements into a single geometric mean representing comparative performance between frameworks. For a combination of all types of read and write operations, Solid ranks just below vanilla JavaScript:</p>
<div class="table-wrapper-paragraph"><table>
<thead>
<tr>
<th>Framework</th>
<th>Version</th>
<th>Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>Vanilla JS</td>
<td>N/A</td>
<td><strong>1.00</strong></td>
</tr>
<tr>
<td>Solid</td>
<td>1.5.4</td>
<td><strong>1.10</strong></td>
</tr>
<tr>
<td>Lit-html</td>
<td>1.1.0</td>
<td><strong>1.19</strong></td>
</tr>
<tr>
<td>Vue</td>
<td>3.2.37</td>
<td><strong>1.25</strong></td>
</tr>
<tr>
<td>Svelte</td>
<td>3.50.1</td>
<td><strong>1.30</strong></td>
</tr>
<tr>
<td>Preact</td>
<td>10.7.3</td>
<td><strong>1.43</strong></td>
</tr>
<tr>
<td>Angular</td>
<td>13.0.0</td>
<td><strong>1.58</strong></td>
</tr>
<tr>
<td>Marko</td>
<td>4.12.3</td>
<td><strong>1.70</strong></td>
</tr>
<tr>
<td>React</td>
<td>18.2.0</td>
<td><strong>1.73</strong></td>
</tr>
</tbody>
</table></div>
<p>Solid ties vanilla JS and ranks just below Svelte on lighthouse startup metrics:</p>
<div class="table-wrapper-paragraph"><table>
<thead>
<tr>
<th>Framework</th>
<th>Version</th>
<th>Mean</th>
</tr>
</thead>
<tbody>
<tr>
<td>Svelte</td>
<td>3.50.1</td>
<td><strong>1.03</strong></td>
</tr>
<tr>
<td>Solid</td>
<td>1.5.4</td>
<td><strong>1.04</strong></td>
</tr>
<tr>
<td>Vanilla JS</td>
<td>N/A</td>
<td><strong>1.04</strong></td>
</tr>
<tr>
<td>Preact</td>
<td>10.7.3</td>
<td><strong>1.06</strong></td>
</tr>
<tr>
<td>Lit-html</td>
<td>1.1.0</td>
<td><strong>1.07</strong></td>
</tr>
<tr>
<td>Marko</td>
<td>4.12.3</td>
<td><strong>1.18</strong></td>
</tr>
<tr>
<td>Vue</td>
<td>3.2.37</td>
<td><strong>1.27</strong></td>
</tr>
<tr>
<td>React</td>
<td>18.2.0</td>
<td><strong>1.69</strong></td>
</tr>
<tr>
<td>Angular</td>
<td>13.0.0</td>
<td><strong>1.77</strong></td>
</tr>
</tbody>
</table></div>
<h3>
SolidStart Motivations
</h3>
<p>SolidStart takes influence from other JavaScript metaframeworks including Next.js, Nuxt.js, and SvelteKit by introducing multiple build modes, routing conventions, opinionated project structures, and pre-configured deployment adapters. The framework can produce sites or applications that employ either:</p>
<ul>
<li><a href="proxy.php?url=https://www.patterns.dev/posts/static-rendering/" rel="noopener noreferrer">Static site generation (SSG)</a></li>
<li><a href="proxy.php?url=https://www.patterns.dev/posts/client-side-rendering/" rel="noopener noreferrer">Client-side rendering (CSR)</a></li>
<li><a href="proxy.php?url=https://www.patterns.dev/posts/server-side-rendering/" rel="noopener noreferrer">Server-side rendering (SSR)</a></li>
<li><a href="proxy.php?url=https://www.patterns.dev/react/server-side-rendering" rel="noopener noreferrer">Streaming SSR (SSSR)</a></li>
</ul>
<p>In the future, you'll also be able to choose:</p>
<ul>
<li><a href="proxy.php?url=https://github.com/solidjs/solid-start/issues/381" rel="noopener noreferrer">Some combination of all the above through route level controls</a></li>
<li><a href="proxy.php?url=https://github.com/solidjs/solid-start/issues/400" rel="noopener noreferrer">Islands through newly created conventions like Hybrid Routing and Minimal Hydration</a></li>
</ul>
<h2>
Create Client Rendered Solid Project
</h2>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>ajcwebdev-solidstart
<span class="nb">cd </span>ajcwebdev-solidstart
pnpm init
pnpm add solid-js @solidjs/meta @solidjs/router solid-start
pnpm add <span class="nt">-D</span> solid-start-node vite-plugin-solid vite undici typescript
</code></pre>
</div>
<p>Add <code>vite</code> scripts to <code>package.json</code> and set <code>type</code> to <code>module</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ajcwebdev-solidstart"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"An example SolidStart application deployed on Netlify, Vercel, and Cloudflare Pages"</span><span class="p">,</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module"</span><span class="p">,</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vite"</span><span class="p">,</span><span class="w">
</span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vite build"</span><span class="p">,</span><span class="w">
</span><span class="nl">"serve"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vite preview"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"keywords"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="s2">"SolidJS"</span><span class="p">,</span><span class="w"> </span><span class="s2">"SolidStart"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Netlify"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Vercel"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Cloudflare"</span><span class="w"> </span><span class="p">],</span><span class="w">
</span><span class="nl">"author"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Anthony Campolo"</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MIT"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p>Create a <code>.gitignore</code> file.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="s1">'node_modules\n.env\n.DS_Store\ndist\n.solid\nnetlify\n.netlify\n.vercel'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<p>There's only a handful of files needed for a working Solid project. These include a configuration file for Vite (<code>vite.config.ts</code>), an entry point for our JavaScript application (<code>src/root.tsx</code>), and an entry point for our HTML page (<code>index.html</code>).</p>
<p>Run the following commands:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src
<span class="nb">echo</span> <span class="o">></span> tsconfig.json <span class="c"># TypeScript Configuration</span>
<span class="nb">echo</span> <span class="o">></span> vite.config.ts <span class="c"># Vite Configuration</span>
<span class="nb">echo</span> <span class="o">></span> index.html <span class="c"># HTML entry point where JavaScript app loads</span>
<span class="nb">echo</span> <span class="o">></span> src/root.css <span class="c"># CSS stylesheet</span>
<span class="nb">echo</span> <span class="o">></span> src/root.tsx <span class="c"># Defines the document the app renders</span>
</code></pre>
</div>
<p>In addition to the required files we also created optional files for CSS styling and TypeScript configuration. Later in the tutorial when we migrate this project to SolidStart, we'll remove the HTML file and replace it with two files, <code>entry-server.tsx</code> and <code>entry-client.tsx</code>.</p>
<h3>
TypeScript and Vite Project Configuration
</h3>
<p>Copy/paste this impenetrable hunk of gibberish into your <code>tsconfig.json</code> and say a prayer to Microsoft.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"compilerOptions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"allowSyntheticDefaultImports"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"esModuleInterop"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"strict"</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="p">,</span><span class="w">
</span><span class="nl">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ESNext"</span><span class="p">,</span><span class="w">
</span><span class="nl">"module"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ESNext"</span><span class="p">,</span><span class="w">
</span><span class="nl">"moduleResolution"</span><span class="p">:</span><span class="w"> </span><span class="s2">"node"</span><span class="p">,</span><span class="w">
</span><span class="nl">"jsxImportSource"</span><span class="p">:</span><span class="w"> </span><span class="s2">"solid-js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"jsx"</span><span class="p">:</span><span class="w"> </span><span class="s2">"preserve"</span><span class="p">,</span><span class="w">
</span><span class="nl">"types"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"vite/client"</span><span class="p">],</span><span class="w">
</span><span class="nl">"baseUrl"</span><span class="p">:</span><span class="w"> </span><span class="s2">"./"</span><span class="p">,</span><span class="w">
</span><span class="nl">"paths"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"~/*"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"./src/*"</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p>The <code>vite.config.ts</code> file is where we'll add the Solid Plugin to define our Vite Configuration. Import <code>solidPlugin</code> from <code>vite-plugin-solid</code> and add it to the <code>plugins</code> array inside Vite's <code>defineConfig</code> helper.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// vite.config.ts</span>
<span class="k">import</span> <span class="nx">solidPlugin</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vite-plugin-solid</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vite</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nf">solidPlugin</span><span class="p">()]</span>
<span class="p">})</span>
</code></pre>
</div>
<h3>
HTML Entry, CSS Styling, and Render Function
</h3>
<p>The root Solid component will be imported as an ESM module from <code>/src/root.tsx</code> and set to the <code>src</code> attribute.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!-- index.html --></span>
<span class="cp"><!DOCTYPE html></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"utf-8"</span> <span class="nt">/></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1"</span> <span class="nt">/></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"description"</span> <span class="na">content=</span><span class="s">"An example SolidJS single-page application."</span> <span class="nt">/></span>
<span class="nt"><title></span>A First Look at SolidStart<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><noscript></span>You need to enable JavaScript to run this app.<span class="nt"></noscript></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"root"</span><span class="nt">></div></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/src/root.tsx"</span> <span class="na">type=</span><span class="s">"module"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre>
</div>
<p>Include the following CSS styles in <code>src/root.css</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight css"><code><span class="c">/* src/root.css */</span>
<span class="nt">body</span> <span class="p">{</span>
<span class="nl">background-color</span><span class="p">:</span> <span class="m">#282c34</span><span class="p">;</span>
<span class="nl">color</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
<span class="nl">font-family</span><span class="p">:</span> <span class="s2">'Helvetica Neue'</span><span class="p">,</span> <span class="n">Helvetica</span><span class="p">,</span> <span class="n">Arial</span><span class="p">,</span> <span class="nb">sans-serif</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">text-align</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">header</span> <span class="p">{</span>
<span class="nl">font-size</span><span class="p">:</span> <span class="n">calc</span><span class="p">(</span><span class="m">10px</span> <span class="err">+</span> <span class="m">2vmin</span><span class="p">);</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">a</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="m">#b318f0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
</div>
<p>To mount Solid and automatically create a reactive root, import <a href="proxy.php?url=https://www.solidjs.com/docs/latest/api#render" rel="noopener noreferrer"><code>render</code></a> from <code>solid-js/web</code> and pass in two arguments, a top-level component function and an element to mount on:</p>
<ul>
<li>The first argument returns the root component and must be passed a function.</li>
<li>The mounting container is passed for the second argument and wired up to the <code>root</code> div.
</li>
</ul>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/root.tsx</span>
<span class="cm">/* @refresh reload */</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">render</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-js/web</span><span class="dl">"</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">./root.css</span><span class="dl">"</span>
<span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>A First Look at SolidStart<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://github.com/solidjs/solid"</span><span class="p">></span>Learn Solid<span class="p"></</span><span class="nt">a</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">footer</span><span class="p">></span>
<span class="p"><</span><span class="nt">span</span><span class="p">></span>
Visit <span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://ajcwebdev.com"</span><span class="p">></span>ajcwebdev.com<span class="p"></</span><span class="nt">a</span><span class="p">></span> for more tutorials
<span class="p"></</span><span class="nt">span</span><span class="p">></span>
<span class="p"></</span><span class="nt">footer</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="nf">render</span><span class="p">(</span>
<span class="p">()</span> <span class="o">=></span> <span class="p"><</span><span class="nc">App</span> <span class="p">/>,</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">root</span><span class="dl">'</span><span class="p">)</span> <span class="k">as</span> <span class="nx">HTMLElement</span>
<span class="p">)</span>
</code></pre>
</div>
<h3>
Start Development Server
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm dev <span class="c"># or npm run dev | yarn dev</span>
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:5173" rel="noopener noreferrer"><code>localhost:5173</code></a> to view the running application in your browser. The page will reload if you make edits.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F01-solid-home-page-with-styling.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F01-solid-home-page-with-styling.webp" alt="01 - Solid homepage with styling on localhost 5173"></a></p>
<p>At this point, we could build and deploy a <code>dist</code> folder to any static host provider. Run <code>pnpm build</code> and <code>pnpm serve</code> to test serving your bundle on <a href="proxy.php?url=http://localhost:4173" rel="noopener noreferrer"><code>localhost:4173</code></a>. Instead of deploying this project, we'll continue to the next section and begin modifying our project to make it work with SolidStart.</p>
<h2>
Migrate Project to SolidStart
</h2>
<p>First, delete your existing HTML file and create a <code>routes</code> directory inside <code>src</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">rm</span> <span class="nt">-rf</span> index.html
<span class="nb">mkdir </span>src/routes
<span class="nb">echo</span> <span class="o">></span> src/entry-server.tsx <span class="c"># Server entry point</span>
<span class="nb">echo</span> <span class="o">></span> src/entry-client.tsx <span class="c"># Browser entry point</span>
<span class="nb">echo</span> <span class="o">></span> src/routes/index.tsx <span class="c"># Home route</span>
</code></pre>
</div>
<h3>
SolidStart Scripts and Vite Configuration
</h3>
<p>Replace the Vite scripts with the following SolidStart scripts.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"solid-start dev"</span><span class="p">,</span><span class="w">
</span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"solid-start build"</span><span class="p">,</span><span class="w">
</span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"solid-start start"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p>Remove <code>solidPlugin</code> from <code>vite-plugin-solid</code> and replace it with <code>solid</code> from <code>solid-start/vite</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// vite.config.ts</span>
<span class="k">import</span> <span class="nx">solid</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start/vite</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vite</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nf">solid</span><span class="p">()]</span>
<span class="p">})</span>
</code></pre>
</div>
<h3>
Index Route, Root, and Entry Points
</h3>
<p>Copy the <code>App</code> component from <code>src/root.tsx</code> and include it in <code>src/routes/index.tsx</code> with <code>export default</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/routes/index.tsx</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>A First Look at SolidStart<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://github.com/solidjs/solid"</span><span class="p">></span>Learn Solid<span class="p"></</span><span class="nt">a</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">footer</span><span class="p">></span>
<span class="p"><</span><span class="nt">span</span><span class="p">></span>
Visit <span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://ajcwebdev.com"</span><span class="p">></span>ajcwebdev.com<span class="p"></</span><span class="nt">a</span><span class="p">></span> for more tutorials
<span class="p"></</span><span class="nt">span</span><span class="p">></span>
<span class="p"></</span><span class="nt">footer</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://start.solidjs.com/api/root" rel="noopener noreferrer"><code>root.tsx</code></a> is the point where your code runs on both the server and client. It exports a <code>Root</code> component that is shared on the server and browser as an isomorphic entry point to your application. Since SolidStart is designed for file-system routing, routes are defined via a folder structure under the <code>/routes</code> folder. You can pass them into the <a href="proxy.php?url=https://start.solidjs.com/api/Routes" rel="noopener noreferrer"><code><Routes></code></a> component with the <a href="proxy.php?url=https://start.solidjs.com/api/FileRoutes" rel="noopener noreferrer"><code><FileRoutes></code></a> component.</p>
<p>This collects routes from the file-system in the <code>/routes</code> folder to be inserted into a parent <code><Routes></code> component. Since <code><FileRoutes></code> returns a route configuration, it must be placed directly between <code><Routes></code>, typically in the <code>root.tsx</code> file. <code><Routes></code> is a special <code>Switch</code> component that renders the correct <a href="proxy.php?url=https://start.solidjs.com/api/Route" rel="noopener noreferrer"><code><Route></code></a> child based on the users' location, and switches between them as the user navigates.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/root.tsx</span>
<span class="c1">// @refresh reload</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Suspense</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-js</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Body</span><span class="p">,</span> <span class="nx">ErrorBoundary</span><span class="p">,</span> <span class="nx">FileRoutes</span><span class="p">,</span> <span class="nx">Head</span><span class="p">,</span> <span class="nx">Html</span><span class="p">,</span> <span class="nx">Meta</span><span class="p">,</span> <span class="nx">Routes</span><span class="p">,</span> <span class="nx">Scripts</span><span class="p">,</span> <span class="nx">Title</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start</span><span class="dl">"</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">./root.css</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Root</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nc">Html</span> <span class="na">lang</span><span class="p">=</span><span class="s">"en"</span><span class="p">></span>
<span class="p"><</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nc">Title</span><span class="p">></span>A First Look at SolidStart<span class="p"></</span><span class="nc">Title</span><span class="p">></span>
<span class="p"><</span><span class="nc">Meta</span> <span class="na">charset</span><span class="p">=</span><span class="s">"utf-8"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"viewport"</span> <span class="na">content</span><span class="p">=</span><span class="s">"width=device-width, initial-scale=1"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"description"</span> <span class="na">content</span><span class="p">=</span><span class="s">"An example SolidStart application deployed on Netlify, Vercel, and Cloudflare Pages."</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nc">Body</span><span class="p">></span>
<span class="p"><</span><span class="nc">ErrorBoundary</span><span class="p">></span>
<span class="p"><</span><span class="nc">Suspense</span> <span class="na">fallback</span><span class="p">=</span><span class="si">{</span><span class="p"><</span><span class="nt">div</span><span class="p">></span>Loading...<span class="p"></</span><span class="nt">div</span><span class="p">></span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nc">Routes</span><span class="p">></span>
<span class="p"><</span><span class="nc">FileRoutes</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Routes</span><span class="p">></span>
<span class="p"></</span><span class="nc">Suspense</span><span class="p">></span>
<span class="p"></</span><span class="nc">ErrorBoundary</span><span class="p">></span>
<span class="p"><</span><span class="nc">Scripts</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Body</span><span class="p">></span>
<span class="p"></</span><span class="nc">Html</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://start.solidjs.com/api/entry-client" rel="noopener noreferrer"><code>entry-client.tsx</code></a> starts the application in the browser by passing <code><StartClient></code> to a <code>mount</code> function. <code>mount</code> is an alias over Solid's <code>hydrate</code> and <code>render</code> methods. It ensures that the client always starts up properly whether you are using SolidStart for client-only rendering or server-side rendering.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/entry-client.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">mount</span><span class="p">,</span> <span class="nx">StartClient</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start/entry-client</span><span class="dl">"</span>
<span class="nf">mount</span><span class="p">(()</span> <span class="o">=></span> <span class="p"><</span><span class="nc">StartClient</span> <span class="p">/>,</span> <span class="nb">document</span><span class="p">)</span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://start.solidjs.com/api/entry-server" rel="noopener noreferrer"><code>entry-server.tsx</code></a> starts the application on the server by passing <code><StartServer></code> to a render function called <code>renderAsync</code>. <code>createHandler</code> enables a mechanism for introducing middleware into server rendering. <code><StartServer></code> wraps the application root and includes Context providers for Routing and MetaData. It takes the <code>Event</code> object originating from the underlying runtime and includes information such as the <code>request</code>, <code>responseHeaders</code> and status codes.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/entry-server.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">StartServer</span><span class="p">,</span> <span class="nx">createHandler</span><span class="p">,</span> <span class="nx">renderAsync</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start/entry-server</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">createHandler</span><span class="p">(</span>
<span class="nf">renderAsync</span><span class="p">((</span><span class="nx">event</span><span class="p">)</span> <span class="o">=></span> <span class="p"><</span><span class="nc">StartServer</span> <span class="na">event</span><span class="p">=</span><span class="si">{</span><span class="nx">event</span><span class="si">}</span> <span class="p">/>)</span>
<span class="p">)</span>
</code></pre>
</div>
<p>While this example uses <code>renderAsync</code>, there are three different render functions provided by SolidStart. Each wraps Solid's rendering method and returns a unique output:</p>
<ul>
<li>
<a href="proxy.php?url=https://start.solidjs.com/api/entry-server#rendersynccodefn-options" rel="noopener noreferrer"><code>renderSync</code></a> calls <code>renderToString</code> to synchronously respond immediately and render the application to a string.</li>
<li>
<a href="proxy.php?url=https://start.solidjs.com/api/entry-server#renderasynccodefn-options" rel="noopener noreferrer"><code>renderAsync</code></a> calls <code>renderToStringAsync</code> to asynchronously respond when the page has fully been loaded and render the application to a promise.</li>
<li>
<a href="proxy.php?url=https://start.solidjs.com/api/entry-server#renderstreamcodefn-options" rel="noopener noreferrer"><code>renderStream</code></a> calls <code>renderToStream</code> to asynchronously respond as soon as it can and render the application to a <code>ReadableStream</code>.</li>
</ul>
<p>Check that everything still displays as expected.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm dev
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:3000" rel="noopener noreferrer">localhost:3000</a>.</p>
<h2>
Components and Reactive Primitives
</h2>
<p>Since this is a tutorial about a frontend framework it will be considered illegitimate until we build a counter. Create a <code>components</code> directory and then a file called <code>Counter.tsx</code> inside of it.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src/components
<span class="nb">echo</span> <span class="o">></span> src/components/Counter.tsx
</code></pre>
</div>
<p>There are two foundational building blocks at the core of Solid's fine grained reactivity that will enable us to craft this magnificent counter into existence:</p>
<ul>
<li>
<strong>Components</strong> contain stateless DOM elements within functions that accept <code>props</code> and return JSX elements.</li>
<li>
<strong>Reactive Primitives</strong> including Signals, Effects, and Memos track and broadcast the changing values that represent the state of the components over time.</li>
</ul>
<p>In this component we'll create a signal to track the changing value of the counter and an effect to modify the value with button clicks.</p>
<h3>
Create Signal
</h3>
<p><a href="proxy.php?url=https://www.solidjs.com/tutorial/introduction_signals" rel="noopener noreferrer"><strong>Signals</strong></a> contain values that change over time. They are tracked by the framework and update automatically by broadcasting to the rest of the interface. Use <a href="proxy.php?url=https://www.solidjs.com/docs/latest#createsignal" rel="noopener noreferrer"><code>createSignal</code></a> to initialize a value of <code>0</code> and set it to <code>count</code>. Increment the counter once every second by passing a <code>setCount(count() + 1)</code> function to a <code>setInterval()</code> method that executes every 1000 milliseconds.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/components/Counter.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createSignal</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-js</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Counter</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">createSignal</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="nf">setInterval</span><span class="p">(()</span> <span class="o">=></span> <span class="nf">setCount</span><span class="p">(</span><span class="nf">count</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span> <span class="mi">1000</span><span class="p">)</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>The count is now: <span class="si">{</span><span class="nf">count</span><span class="p">()</span><span class="si">}</span><span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Inside <code>src/routes/index.tsx</code>, import <code>Counter</code> from <code>../components/Counter</code>. Return a <code><Counter /></code> component in the return function of the <code>App</code> component.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/routes/index.tsx</span>
<span class="k">import</span> <span class="nx">Counter</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../components/Counter</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>A First Look at SolidStart<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://github.com/solidjs/solid"</span><span class="p">></span>Learn Solid<span class="p"></</span><span class="nt">a</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nc">Counter</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">footer</span><span class="p">></span>
<span class="p"><</span><span class="nt">span</span><span class="p">></span>
Visit <span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://ajcwebdev.com"</span><span class="p">></span>ajcwebdev.com<span class="p"></</span><span class="nt">a</span><span class="p">></span> for more tutorials
<span class="p"></</span><span class="nt">span</span><span class="p">></span>
<span class="p"></</span><span class="nt">footer</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F02-home-page-with-counter.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F02-home-page-with-counter.webp" alt="02 - Homepage with counter"></a></p>
<h3>
Create Effect
</h3>
<p>An <a href="proxy.php?url=https://www.solidjs.com/tutorial/introduction_effects" rel="noopener noreferrer"><strong>Effect</strong></a> is an example of an observer that runs a side effect depending on a signal. <a href="proxy.php?url=https://www.solidjs.com/docs/latest#createeffect" rel="noopener noreferrer"><code>createEffect</code></a> creates a new computation (for example to modify the DOM manually) and runs the given function in a tracking scope.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/components/Counter.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createSignal</span><span class="p">,</span> <span class="nx">createEffect</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-js</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Counter</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">createSignal</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="nf">createEffect</span><span class="p">(()</span> <span class="o">=></span> <span class="nf">count</span><span class="p">())</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="nf">setCount</span><span class="p">(</span><span class="nf">count</span><span class="p">()</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span><span class="si">}</span><span class="p">></span>
Click Me
<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>The count is now: <span class="si">{</span><span class="nf">count</span><span class="p">()</span><span class="si">}</span><span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>This automatically tracks the dependencies and reruns the function whenever the dependencies update.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F03-create-effect-button.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F03-create-effect-button.webp" alt="03 - Create effect button"></a></p>
<h3>
Create Route Data
</h3>
<p>Create a new file called <code>students.tsx</code> inside the <code>src/routes</code> directory. This will contain a third party API call to return a list of Harry Potter characters.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> src/routes/students.tsx
</code></pre>
</div>
<p><code>createRouteData</code> is a wrapper over <code>createResource</code> for handling async data fetching and refetching. With SolidStart's file system routing, components defined under <code>/routes</code> can utilize a <code>routeData</code> function which executes when navigation to that component begins. This hook returns the JSON parsed data from the loader function. We'll use the Fetch API to query Harry Potter information on Deno Deploy.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/routes/students.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useRouteData</span><span class="p">,</span> <span class="nx">createRouteData</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start</span><span class="dl">"</span>
<span class="nx">type</span> <span class="nx">Student</span> <span class="o">=</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span> <span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">routeData</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">createRouteData</span><span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://hogwarts.deno.dev/students</span><span class="dl">"</span><span class="p">)</span>
<span class="k">return </span><span class="p">(</span><span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nf">json</span><span class="p">())</span> <span class="k">as</span> <span class="nx">Student</span><span class="p">[]</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Page</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
</code></pre>
</div>
<p>This <code>routeData</code> function can be thought of like a "loader" function (what a brilliant idea, amazing no one thought of it before) which includes a <code>useRouteData</code> hook to access the returned data.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/routes/students.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useRouteData</span><span class="p">,</span> <span class="nx">createRouteData</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start</span><span class="dl">"</span>
<span class="nx">type</span> <span class="nx">Student</span> <span class="o">=</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span> <span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">routeData</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">createRouteData</span><span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://hogwarts.deno.dev/students</span><span class="dl">"</span><span class="p">)</span>
<span class="k">return </span><span class="p">(</span><span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nf">json</span><span class="p">())</span> <span class="k">as</span> <span class="nx">Student</span><span class="p">[]</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Page</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">students</span> <span class="o">=</span> <span class="nx">useRouteData</span><span class="o"><</span><span class="k">typeof</span> <span class="nx">routeData</span><span class="o">></span><span class="p">()</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Students<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">code</span><span class="p">></span><span class="si">{</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nf">students</span><span class="p">(),</span> <span class="kc">null</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span><span class="si">}</span><span class="p"></</span><span class="nt">code</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p><code>useRouteData</code> can use whatever data is returned from the <code>routeData</code> loader function.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F04-return-raw-json-student-data-to-page.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F04-return-raw-json-student-data-to-page.webp" alt="04 - Return student data to page as raw JSON"></a></p>
<p>The <code><For></code> component loops over an array of objects. The <code><For></code> component has only one prop, <code>each</code>, which is passed an array to loop over with a callback similar to JavaScript's map callback.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/routes/students.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useRouteData</span><span class="p">,</span> <span class="nx">createRouteData</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">For</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-js</span><span class="dl">"</span>
<span class="nx">type</span> <span class="nx">Student</span> <span class="o">=</span> <span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="nx">string</span><span class="p">;</span> <span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">routeData</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">createRouteData</span><span class="p">(</span><span class="k">async </span><span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="dl">"</span><span class="s2">https://hogwarts.deno.dev/students</span><span class="dl">"</span><span class="p">)</span>
<span class="k">return </span><span class="p">(</span><span class="k">await</span> <span class="nx">response</span><span class="p">.</span><span class="nf">json</span><span class="p">())</span> <span class="k">as</span> <span class="nx">Student</span><span class="p">[]</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Page</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">students</span> <span class="o">=</span> <span class="nx">useRouteData</span><span class="o"><</span><span class="k">typeof</span> <span class="nx">routeData</span><span class="o">></span><span class="p">()</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Students<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nc">For</span> <span class="na">each</span><span class="p">=</span><span class="si">{</span><span class="nf">students</span><span class="p">()</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">student</span> <span class="o">=></span> <span class="p"><</span><span class="nt">li</span><span class="p">></span><span class="si">{</span><span class="nx">student</span><span class="p">.</span><span class="nx">name</span><span class="si">}</span><span class="p"></</span><span class="nt">li</span><span class="p">></span><span class="si">}</span>
<span class="p"></</span><span class="nc">For</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F05-loop-over-json-response-and-display-data-with-for-component.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F11%2F20%2Fsolidstart%2F05-loop-over-json-response-and-display-data-with-for-component.webp" alt="05 - Loop over json response and display data with for component"></a></p>
<h2>
API Routes
</h2>
<p>API routes are similar to other routes except instead of exporting a default Solid component with a <code>routeData</code> function, they export functions that are named after the HTTP methods they handle such as <code>GET</code> or <code>POST</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src/routes/api
<span class="nb">echo</span> <span class="o">></span> src/routes/api/index.ts
</code></pre>
</div>
<p><a href="proxy.php?url=https://start.solidjs.com/api/json" rel="noopener noreferrer"><code>json</code></a> is a helper function to send JSON HTTP responses. Return a JSON object with the key set to <code>hello</code> and the value set to <code>world</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/routes/api/index.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">json</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">GET</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">json</span><span class="p">(</span>
<span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="dl">"</span><span class="s2">world</span><span class="dl">"</span> <span class="p">}</span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://127.0.0.1:3000/api" rel="noopener noreferrer">127.0.0.1:3000/api</a> or make a request with cURL.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl http://127.0.0.1:3000/api
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="nl">"hello"</span><span class="p">:</span><span class="s2">"world"</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<h2>
Deployment Adapters
</h2>
<p>Here is the directory and file structure for the final project before including any files related to deployment:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>.
├── src
│ ├── components
│ │ └── Counter.tsx
│ ├── routes
│ │ ├── api
│ │ │ └── index.ts
│ │ ├── index.tsx
│ │ └── students.tsx
│ ├── entry-client.tsx
│ ├── entry-server.tsx
│ ├── root.css
│ └── root.tsx
├── package.json
├── tsconfig.json
└── vite.config.ts
</code></pre>
</div>
<p>Push your project to a GitHub repository.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"mr solid"</span>
gh repo create ajcwebdev-solidstart <span class="se">\</span>
<span class="nt">--description</span><span class="o">=</span><span class="s2">"An example SolidJS application deployed on Netlify, Vercel, and Cloudflare Pages."</span> <span class="se">\</span>
<span class="nt">--remote</span><span class="o">=</span>upstream <span class="se">\</span>
<span class="nt">--source</span><span class="o">=</span><span class="nb">.</span> <span class="se">\</span>
<span class="nt">--public</span> <span class="se">\</span>
<span class="nt">--push</span>
</code></pre>
</div>
<p>If you only want to use a single deployment platform, select one of the next three options and push the changes to the main branch. I will deploy the project to all three by creating a different branch for each and specifying the correct branch on the deployment platform.</p>
<h3>
Deploy to Netlify
</h3>
<p>Import the <code>netlify</code> adapter from <code>solid-start-netlify</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// vite.config.ts</span>
<span class="c1">// @ts-ignore</span>
<span class="k">import</span> <span class="nx">netlify</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start-netlify</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">solid</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start/vite</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vite</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nf">solid</span><span class="p">({</span>
<span class="na">adapter</span><span class="p">:</span> <span class="nf">netlify</span><span class="p">({</span> <span class="na">edge</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="p">})]</span>
<span class="p">})</span>
</code></pre>
</div>
<p>Install <code>solid-start-netlify</code> and the Netlify CLI.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm add <span class="nt">-D</span> solid-start-netlify netlify-cli @types/node
</code></pre>
</div>
<p>Create a <code>netlify.toml</code> file for the build instructions.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> netlify.toml
</code></pre>
</div>
<p>Set the <code>command</code> to <code>pnpm build</code> and the <code>publish</code> directory to <code>netlify</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight toml"><code><span class="c"># netlify.toml</span>
<span class="nn">[build]</span>
<span class="py">command</span> <span class="p">=</span> <span class="s">"pnpm build"</span>
<span class="py">publish</span> <span class="p">=</span> <span class="s">"netlify"</span>
</code></pre>
</div>
<p>Connect the repository to your Netlify account through the Netlify dashboard or use the following commands with the Netlify CLI.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm ntl login
pnpm ntl init
</code></pre>
</div>
<p>The build commands will be automatically entered from the <code>netlify.toml</code> file.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm ntl deploy <span class="nt">--prod</span> <span class="nt">--build</span>
</code></pre>
</div>
<p>Open <a href="proxy.php?url=https://ajcwebdev-solidstart.netlify.app/" rel="noopener noreferrer">ajcwebdev-solidstart.netlify.app</a> to see a running example.</p>
<h3>
Deploy to Vercel
</h3>
<p>Install <code>solid-start-vercel</code> and the Vercel CLI.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm add <span class="nt">-D</span> solid-start-vercel vercel
</code></pre>
</div>
<p>Import the <code>vercel</code> adapter from <code>solid-start-vercel</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// vite.config.ts</span>
<span class="c1">// @ts-ignore</span>
<span class="k">import</span> <span class="nx">vercel</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start-vercel</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">solid</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start/vite</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vite</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nf">solid</span><span class="p">({</span>
<span class="na">adapter</span><span class="p">:</span> <span class="nf">vercel</span><span class="p">({</span> <span class="na">edge</span><span class="p">:</span> <span class="kc">true</span> <span class="p">})</span>
<span class="p">})]</span>
<span class="p">})</span>
</code></pre>
</div>
<p>Deploy the project with the Vercel CLI.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm vercel <span class="nt">--yes</span> <span class="nt">--prod</span>
</code></pre>
</div>
<p>Open <a href="proxy.php?url=https://ajcwebdev-solidstart.vercel.app/" rel="noopener noreferrer">ajcwebdev-solidstart.vercel.app</a>.</p>
<h3>
Deploy to Cloudflare
</h3>
<p>SolidStart includes two adapters for Cloudflare, one for Cloudflare Workers and another for Cloudflare Pages. It's important to note that the Cloudflare Pages adapter is also using Workers through <a href="proxy.php?url=https://blog.cloudflare.com/pages-full-stack-frameworks/#solidstart" rel="noopener noreferrer">Pages Functions</a>. Install <code>solid-start-cloudflare-pages</code> and the <code>wrangler</code> CLI.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm add <span class="nt">-D</span> solid-start-cloudflare-pages wrangler
</code></pre>
</div>
<p>Import the <code>cloudflare</code> adapter from <code>solid-start-cloudflare-pages</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// vite.config.ts</span>
<span class="c1">// @ts-ignore</span>
<span class="k">import</span> <span class="nx">cloudflare</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start-cloudflare-pages</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">solid</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">solid-start/vite</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">vite</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nf">solid</span><span class="p">({</span>
<span class="na">adapter</span><span class="p">:</span> <span class="nf">cloudflare</span><span class="p">({})</span>
<span class="p">})],</span>
<span class="p">})</span>
</code></pre>
</div>
<p>Build the project's assets and run a local Worker emulation on <a href="proxy.php?url=http://localhost:8788/" rel="noopener noreferrer">localhost:8788</a> with <code>wrangler pages dev</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm wrangler login
pnpm build
pnpm wrangler pages dev ./dist/public
</code></pre>
</div>
<p>Create a project with <a href="proxy.php?url=https://developers.cloudflare.com/workers/wrangler/commands/#project-create" rel="noopener noreferrer"><code>wrangler pages project create</code></a> and deploy the project with <a href="proxy.php?url=https://developers.cloudflare.com/workers/wrangler/commands/#publish-1" rel="noopener noreferrer"><code>wrangler pages publish</code></a>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm wrangler pages project create ajcwebdev-solidstart <span class="se">\</span>
<span class="nt">--production-branch</span> production
pnpm wrangler pages publish dist/public <span class="se">\</span>
<span class="nt">--project-name</span><span class="o">=</span>ajcwebdev-solidstart <span class="se">\</span>
<span class="nt">--branch</span><span class="o">=</span>production
</code></pre>
</div>
<p>Open <a href="proxy.php?url=https://ajcwebdev-solidstart.pages.dev/" rel="noopener noreferrer">ajcwebdev-solidstart.pages.dev</a>.</p>
solidjsnetlifyvercelcloudflareA First Look at create-t3-appajcwebdevSat, 13 Aug 2022 08:16:00 +0000
https://dev.to/ajcwebdev/a-first-look-at-create-t3-app-1i8f
https://dev.to/ajcwebdev/a-first-look-at-create-t3-app-1i8f<h2>
Outline
</h2>
<ul>
<li>
Introduction
<ul>
<li>Why the t3 Stack Was Created</li>
<li>History of the t3 Stack and Create Nex App</li>
</ul>
</li>
<li>
Create t3 App
<ul>
<li>Project Structure</li>
<li>Tailwind Styles</li>
</ul>
</li>
<li>
Provision PostgreSQL Database
<ul>
<li>Add Posts Model to Prisma Schema</li>
<li>Initialize Railway Database and Run Migration</li>
<li>Seed a Blog Post</li>
</ul>
</li>
<li>
Query Posts with tRPC
<ul>
<li>Create Post Router</li>
<li>Create App Router</li>
<li>Query Posts with useQuery</li>
</ul>
</li>
<li>
Add Cells for Declarative Data Fetching
<ul>
<li>Create Default Query Cell</li>
<li>Create Post Page</li>
<li>Create Posts Cell</li>
</ul>
</li>
<li>
Deployment
<ul>
<li>Deploy to Vercel</li>
<li>Deploy to Fly</li>
</ul>
</li>
<li>Resources, Articles, and Videos</li>
</ul>
<blockquote>
<p><strong><em>All of this project's code can be found in the <a href="proxy.php?url=https://github.com/ajcwebdev/a-first-look/tree/main/backend/t3/" rel="noopener noreferrer">First Look monorepo</a> on my GitHub.</em></strong></p>
</blockquote>
<h2>
Introduction
</h2>
<p><a href="proxy.php?url=https://create.t3.gg/" rel="noopener noreferrer"><code>create-t3-app</code></a> is a fullstack React framework and CLI that has emerged as an evolution of the T3 stack recommended on Theo Browne's website <a href="proxy.php?url=https://init.tips/" rel="noopener noreferrer"><code>init.tips</code></a>. It's described by its creators as "kind of a template," which is meant to stress that it is <a href="proxy.php?url=https://x.com/t3dotgg/status/1544394504685645824" rel="noopener noreferrer">"NOT a template"</a>.</p>
<h3>
Why the t3 Stack Was Created
</h3>
<p><code>ct3a</code>'s goal is to provide the quickest way to start a new fullstack, typesafe web application. To achieve this goal, the stack is architected around three foundational constituents that together can be bundled together and used to develop monolithic applications:</p>
<ul>
<li>Typed React frontend (<a href="proxy.php?url=https://www.typescriptlang.org/" rel="noopener noreferrer">TypeScript</a> and <a href="proxy.php?url=https://nextjs.org/" rel="noopener noreferrer">Next.js</a>)</li>
<li>Typed database client (<a href="proxy.php?url=https://www.prisma.io/" rel="noopener noreferrer">Prisma</a>)</li>
<li>Typed remote procedure calls (<a href="proxy.php?url=https://trpc.io/" rel="noopener noreferrer">tRPC</a>)</li>
</ul>
<p>Depending on your background and perspective that may sound like a ground breaking innovation, a completely obvious repackaging of techniques used over two decades ago, or an absolute heresy because you've been taught that developing monoliths is a sin.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F01-end-to-end-type-safety.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F01-end-to-end-type-safety.webp" alt="01 - End to end type safety"></a></p>
<blockquote>
<p><em>Source: Sabin Adams - <a href="proxy.php?url=https://sabinadams.hashnode.dev/end-to-end-type-safety-what-why-and-how" rel="noopener noreferrer">End-To-End Type Safety</a></em></p>
</blockquote>
<p>As someone who has resisted TypeScript until now, this is terrifying to me. But I'm going to make an exception and embrace TypeScript for the first time in my life if this stack can actually provide a smooth and streamlined TypeScript experience.</p>
<p>But for those already in love with TypeScript and fullstack React frameworks, you are probably feeling a strange sense of deja-vu right now. This is an almost identical stack to Blitz.js and shares many of the same architectural principles. The notable difference is that CTA includes tRPC (which itself <a href="proxy.php?url=https://github.com/trpc/trpc/blob/7b492cfbf3cd8039587670cea9d6b7506a1a83f2/www/docs/further/further-reading.md#differences-to-blitzjs" rel="noopener noreferrer">has frequently been compared to Blitz.js</a>).</p>
<h3>
History of the t3 Stack and Create Nex App
</h3>
<p>The first iteration of the <code>init.tips</code> site suggested only one command was needed to initialize a mostly optimal boilerplate for the majority of web applications in 2021. This suggestion (in its infinite wisdom) was: <em>Create a Next.js app,</em> <strong><em>but with TypeScript</em></strong>.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F02-first-version-of-init-tips.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F02-first-version-of-init-tips.webp" alt="02 - Screenshot of the front page of the first version of init tips. The page is almost entirely blank with the exception of a short message saying, "></a></p>
<p>As people began to consider this advice, many developers inevitably asked:</p>
<blockquote>
<p><em>"Mmmm, but what about all that other stuff not included in this stack that I need to make an even borderline functional application?"</em></p>
</blockquote>
<p>This lead to <a href="proxy.php?url=https://init.tips/others" rel="noopener noreferrer">other recommendations</a> for add-ons to the stack. These add-ons targeted specific use cases such as:</p>
<ul>
<li>
<strong>Prisma</strong> for managing database migrations and SQL queries through an ORM</li>
<li>
<strong>Next-auth</strong> for client side authentication</li>
<li>
<strong>Tailwind</strong> for CSS and UI styling</li>
<li>
<strong>tRPC</strong> for end-to-end typesafe APIs</li>
</ul>
<p>If these were being frequently recommended, it stood to reason that it would make sense to create a new, more full featured command. This would generate not only a typed Next.js project, but one with an ORM, authentication, styling, and API protocol.</p>
<p>These would be automatically included while also giving you the ability to opt out if you still want the bare-bones version. I'm happy that this is taking off and that some consider it a novel idea.</p>
<p>I've spent the last two years relentlessly promoting frameworks assembling different versions of these kinds of stacks. RedwoodJS, Blitz.js, and Bison all have extremely similar but also slightly different stacks. To understand how these relate to each other, I would break it down like so:</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F03-framework-comparison-table.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F03-framework-comparison-table.webp" alt="03 - Framework comparison table between create-t3-app, RedwoodJS, Blitz.js, and Bison"></a></p>
<blockquote>
<p><em>This is not meant to be an exhaustive list and I've purposefully left off things like tests, mocks, Storybook, deployment, and other non-architectural pieces.</em></p>
</blockquote>
<p>As the project has evolved from <code>init.tips</code> to <code>create-t3-app</code>, it has taken on a life of its own. Theo has stated numerous times that he did not actually initiate the creation of <code>create-t3-app</code>, he simply talked about the idea numerous times in public.</p>
<p>In fact, he never would have had the time to build or manage such a project. On top of full time content creation, he's the <a href="proxy.php?url=https://t3.gg/blog/post/dream-job-revisited" rel="noopener noreferrer">CEO of a startup</a> building <a href="proxy.php?url=https://ping.gg/" rel="noopener noreferrer">ping.gg</a>, a collaborative streaming tool. His influence over the project primarily stemmed from his various public discussions of the stack.</p>
<p>These discussions inspired a group of people who were members of his recently formed <a href="proxy.php?url=https://discord.gg/tEAQjDseSX" rel="noopener noreferrer">Discord</a> server. This online space was created to bring together fans of his <a href="proxy.php?url=https://www.twitch.tv/Theo" rel="noopener noreferrer">Twitch</a> and <a href="proxy.php?url=https://www.youtube.com/c/theobrowne1017" rel="noopener noreferrer">YouTube</a> channels. A group independently began building out a full fledged project. This activity was centered around the work of Shoubhit Dash.</p>
<p>Known as <a href="proxy.php?url=https://www.nexxel.dev/" rel="noopener noreferrer">nexxel</a> or <a href="proxy.php?url=https://github.com/nexxeln" rel="noopener noreferrer">nexxeln</a> online, Shoubhit took the initiative to formalize the stack by developing an interactive CLI tool that would be able to scaffold out a project using arbitrary combinations of the various technologies used in the stack. nexxel, a 17 year old self-taught developer, is the true rosetta stone to this project.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1660363489289%2FdhnC5SGit.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1660363489289%2FdhnC5SGit.png" alt="04 - nexxel's first discord message about create-nex-app"></a></p>
<p>Nexxel was blogging about tRPC in May right before launching the framework. <a href="proxy.php?url=https://dev.to/nexxeln/build-end-to-end-typesafe-apis-with-trpc-3o3f">Build end to end typesafe APIs with tRPC</a> signaled the birth of the framework on May 21, 2022 along with an <a href="proxy.php?url=https://github.com/nexxeln/create-nex-app/commit/dac862ad595bf7e3c5e8a2bfbc6d29c8df2f92ed" rel="noopener noreferrer">initial commit</a> on May 20, 2022. Originally called <a href="proxy.php?url=https://github.com/nexxeln/create-t3-app/commit/7c82cb02f88cc9eadd691f864521b993b6c7b4b1" rel="noopener noreferrer">Create Nex App</a>, the <a href="proxy.php?url=https://github.com/nexxeln/create-t3-app/commit/a7a8f79083158c0a93ffb2ac3a5cbd51054a2944" rel="noopener noreferrer">README</a> described the project like so:</p>
<blockquote>
<p><em>Scaffold a starting project using the <a href="proxy.php?url=https://init.tips" rel="noopener noreferrer">t3 stack</a> using this interactive CLI.</em></p>
</blockquote>
<p>The early prototypes of the project included Next.js, Tailwind, and TypeScript along with tRPC. Throughout June, the project began attracting around a dozen contributors. Julius Marminge (<a href="proxy.php?url=https://github.com/juliusmarminge" rel="noopener noreferrer">juliusmarminge</a>) was one of the earliest contributors and remains active today.</p>
<p>Roughly a month later on June 26, 2022, nexxel published <a href="proxy.php?url=https://www.nexxel.dev/blog/ct3a" rel="noopener noreferrer">T3 stack and my most popular open source project ever</a>. This blog post was published after working with the other contributors to fully integrate Prisma and Next Auth, marking the completion of the stack's initial integration phase.</p>
<p>Throughout the month of June, the GitHub repo <a href="proxy.php?url=https://twitter.com/t3dotgg/status/1544845624113131520" rel="noopener noreferrer">gained nearly 2,000 GitHub stars</a>. Despite having only been created at <a href="proxy.php?url=https://github.com/t3-oss/create-t3-app/commit/7c82cb02f88cc9eadd691f864521b993b6c7b4b1" rel="noopener noreferrer">the end of May</a>, the project had reached nearly unprecedented levels of momentum. On July 17, 2022, nexxel <a href="proxy.php?url=https://github.com/nexxeln/nexxel.dev/commit/ad25e652dc86b217cb8dd7b2de09d55932484f4f" rel="noopener noreferrer">migrated his personal blog to create-t3-app</a> and by the middle of August the project had over 5,000 stars.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F05-create-t3-app-star-history-chart-august.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F05-create-t3-app-star-history-chart-august.webp" alt="05 - create-t3-app star history chart showing steep growth over the summer of 2022"></a></p>
<h2>
Create t3 App
</h2>
<p>To get started with <code>ct3a</code>, you can run any of the following three commands and answer the command prompt questions:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>npx create-t3-app@latest
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn create t3-app
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm dlx create-t3-app@latest
</code></pre>
</div>
<p>The following CLI options are currently available:</p>
<div class="table-wrapper-paragraph"><table>
<thead>
<tr>
<th>Option</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--noGit</code></td>
<td>Explicitly tell the CLI to not initialize a new git repo in the project</td>
</tr>
<tr>
<td>
<code>-y</code>, <code>--default</code>
</td>
<td>Bypass CLI and use all default options to bootstrap new t3-app</td>
</tr>
<tr>
<td><code>[dir]</code></td>
<td>Include a directory argument with a name for the project</td>
</tr>
<tr>
<td><code>--noInstall</code></td>
<td>Generate project without installing dependencies</td>
</tr>
</tbody>
</table></div>
<p>We'll give our project a name and select all available options with the exception of NextAuth.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm dlx create-t3-app@latest ajcwebdev-t3
</code></pre>
</div>
<p>I will select the following options:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>? Will you be using JavaScript or TypeScript? TypeScript
? Which packages would you like to enable? prisma, tailwind, trpc
? Initialize a new git repository? Yes
? Would you like us to run 'pnpm install'? Yes
</code></pre>
</div>
<p>Including the <code>-y</code> option will select the default configuration which bundles all four packages into the project. The interactive CLI prompt will also ask whether you want to use JavaScript or TypeScript. If you try to select JavaScript though, you will discover that the option is but a mere illusion. In fact, you must use TypeScript and also there is no God.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>Using: pnpm
✔ ajcwebdev-t3 scaffolded successfully!
✔ Successfully setup boilerplate for prisma
✔ Successfully setup boilerplate for tailwind
✔ Successfully setup boilerplate for trpc
✔ Successfully setup boilerplate for envVariables
✔ Successfully installed dependencies!
✔ Successfully initialized git
</code></pre>
</div>
<p>Enter your project directory and start the development server.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">cd </span>ajcwebdev-t3
pnpm dev
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:3000" rel="noopener noreferrer">localhost:3000</a> to see the generated project.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F06-create-t3-app-localhost.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F06-create-t3-app-localhost.webp" alt="06 - create-t3-app running on localhost"></a></p>
<h3>
Project Structure
</h3>
<p>If we ignore the configuration files in the root of our project then our folder and file structure includes the following:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>.
├── prisma
│ └── schema.prisma
├── public
│ └── favicon.ico
└── src
├── env
│ ├── client.mjs
│ ├── schema.mjs
│ └── server.mjs
├── pages
│ ├── _app.tsx
│ ├── api
│ │ ├── examples.ts
│ │ └── trpc
│ │ └── [trpc].ts
│ └── index.tsx
├── server
│ ├── db
│ │ └── client.ts
│ └── trpc
│ ├── context.ts
│ ├── router
│ │ ├── _app.ts
│ │ └── example.ts
│ └── trpc.ts
├── styles
│ └── globals.css
└── utils
└── trpc.ts
</code></pre>
</div>
<p>We'll break down each of these different directories and files as we progress through the tutorial. But as a quick overview, we'll be primarily working with:</p>
<ul>
<li>Pages and API Routes
<ul>
<li><code>src/pages/index.tsx</code></li>
<li><code>src/pages/api/examples.ts</code></li>
<li><code>src/pages/api/trpc/[trpc].ts</code></li>
</ul>
</li>
<li>Server and Database
<ul>
<li><code>src/server/db/client.ts</code></li>
<li><code>src/server/trpc/context.ts</code></li>
<li><code>src/server/trpc/router/_app.ts</code></li>
<li><code>src/server/trpc/router/example.ts</code></li>
<li><code>src/server/trpc/trpc.ts</code></li>
</ul>
</li>
<li>Styling
<ul>
<li><code>src/styles/globals.css</code></li>
</ul>
</li>
<li>Utilities
<ul>
<li><code>src/utils/trpc.ts</code></li>
</ul>
</li>
</ul>
<h3>
Tailwind Styles
</h3>
<p>Open <code>src/pages/index.tsx</code> and make some changes to customize the home page. Feel free to follow along or make your own alterations, there are many different ways this project could be organized. First, I will create a file called file called <code>home-styles.ts</code> to hold all styling that will be used on the home page of the website.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> src/styles/home-styles.ts
</code></pre>
</div>
<p>I will export a variable defining each Tailwind style that can be reused throughout the project.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/styles/home-styles.ts</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">appContainer</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">container mx-auto flex flex-col items-center justify-center min-h-screen p-4</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">text-5xl md:text-[5rem] leading-normal font-extrabold text-gray-700</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">purple</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">text-purple-300</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">body</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">text-2xl text-gray-700</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">grid</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">grid gap-3 pt-3 mt-3 text-center md:grid-cols-2 lg:w-2/3</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">queryResponse</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">pt-6 text-2xl text-blue-500 flex justify-center items-center w-full</span><span class="dl">"</span>
</code></pre>
</div>
<ul>
<li>
<code>appContainer</code> styles the <code>main</code> content</li>
<li>
<code>title</code> styles the page's <code>h1</code> header</li>
<li>
<code>purple</code> styles T3 with its signature purple color</li>
<li>
<code>body</code> styles the <code>p</code> tag introducing the list of technologies included in the stack</li>
<li>
<code>grid</code> styles the <code>div</code> wrapping the <code>TechnologyCard</code> components</li>
<li>
<code>queryResponse</code> styles the <code>div</code> wrapping the tRPC <code>hello</code> query</li>
</ul>
<p>Add these style variables to the <code>Home</code> component.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/index.tsx</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/trpc</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">appContainer</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">purple</span><span class="p">,</span> <span class="nx">body</span><span class="p">,</span> <span class="nx">grid</span><span class="p">,</span> <span class="nx">queryResponse</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/home-styles</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Home</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">hello</span> <span class="o">=</span> <span class="nx">trpc</span><span class="p">.</span><span class="nf">useQuery</span><span class="p">([</span>
<span class="dl">"</span><span class="s2">example.hello</span><span class="dl">"</span><span class="p">,</span>
<span class="p">{</span> <span class="na">text</span><span class="p">:</span> <span class="dl">"</span><span class="s2">from tRPC</span><span class="dl">"</span> <span class="p">}</span>
<span class="p">])</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>A First Look at create-t3-app<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"description"</span> <span class="na">content</span><span class="p">=</span><span class="s">"Example t3 project from A First Look at create-t3-app"</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="p">=</span><span class="s">"icon"</span> <span class="na">href</span><span class="p">=</span><span class="s">"/favicon.ico"</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">appContainer</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">title</span><span class="si">}</span><span class="p">></span>
Create <span class="p"><</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">purple</span><span class="si">}</span><span class="p">></span>T3<span class="p"></</span><span class="nt">span</span><span class="p">></span> App
<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">body</span><span class="si">}</span><span class="p">></span>This stack uses:<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">grid</span><span class="si">}</span><span class="p">></span>...<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">queryResponse</span><span class="si">}</span><span class="p">></span>...<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> src/styles/card-styles.ts
</code></pre>
</div>
<p>Add style variables to the <code>TechnologyCard</code> component:</p>
<ul>
<li>
<code>cardSection</code> styles the card's container on the <code>section</code> element</li>
<li>
<code>cardTitle</code> styles the technology's title on each card</li>
<li>
<code>cardDescription</code> styles the description of each technology</li>
<li>
<code>link</code> styles the link on each card
</li>
</ul>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/styles/card-styles.ts</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">cardSection</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">flex flex-col justify-center p-6 duration-500 border-2 border-gray-500 rounded shadow-xl motion-safe:hover:scale-105</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">cardTitle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">text-lg text-gray-700</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">cardDescription</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">text-sm text-gray-600</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">link</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">mt-3 text-sm underline text-violet-500 decoration-dotted underline-offset-2</span><span class="dl">"</span>
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/index.tsx</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/trpc</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">appContainer</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">purple</span><span class="p">,</span> <span class="nx">body</span><span class="p">,</span> <span class="nx">grid</span><span class="p">,</span> <span class="nx">queryResponse</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/home-styles</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">cardSection</span><span class="p">,</span> <span class="nx">cardTitle</span><span class="p">,</span> <span class="nx">cardDescription</span><span class="p">,</span> <span class="nx">link</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/card-styles</span><span class="dl">"</span>
<span class="kd">type</span> <span class="nx">TechnologyCardProps</span> <span class="o">=</span> <span class="p">{...}</span>
<span class="kd">const</span> <span class="nx">TechnologyCard</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">description</span><span class="p">,</span> <span class="nx">documentation</span> <span class="p">}:</span> <span class="nx">TechnologyCardProps</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nt">section</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">cardSection</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">cardTitle</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">name</span><span class="si">}</span>
<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">cardDescription</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">description</span><span class="si">}</span>
<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">a</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">link</span><span class="si">}</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="nx">documentation</span><span class="si">}</span> <span class="na">target</span><span class="p">=</span><span class="s">"_blank"</span> <span class="na">rel</span><span class="p">=</span><span class="s">"noreferrer"</span><span class="p">></span>
Documentation
<span class="p"></</span><span class="nt">a</span><span class="p">></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Now I will modify the four cards to include links to my blog and social media profiles. With that change in mind, I will use <code>url</code> instead of <code>documentation</code> for a more appropriate prop name. I will also change the links to include the entire card within the anchor tags so clicking anywhere on the card will open the hyperlink.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/index.tsx</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/trpc</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">appContainer</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">purple</span><span class="p">,</span> <span class="nx">body</span><span class="p">,</span> <span class="nx">grid</span><span class="p">,</span> <span class="nx">queryResponse</span><span class="p">,</span> <span class="nx">cardSection</span><span class="p">,</span> <span class="nx">cardTitle</span><span class="p">,</span> <span class="nx">cardDescription</span><span class="p">,</span> <span class="nx">link</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/home-styles</span><span class="dl">"</span>
<span class="kd">type</span> <span class="nx">TechnologyCardProps</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="kr">string</span>
<span class="na">url</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">TechnologyCard</span> <span class="o">=</span> <span class="p">({</span> <span class="nx">name</span><span class="p">,</span> <span class="nx">url</span> <span class="p">}:</span> <span class="nx">TechnologyCardProps</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="s2">`https://</span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2">`</span><span class="si">}</span> <span class="na">target</span><span class="p">=</span><span class="s">"_blank"</span> <span class="na">rel</span><span class="p">=</span><span class="s">"noreferrer"</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">cardSection</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">cardTitle</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">name</span><span class="si">}</span>
<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">link</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">url</span><span class="si">}</span>
<span class="p"></</span><span class="nt">span</span><span class="p">></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"></</span><span class="nt">a</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Home</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">hello</span> <span class="o">=</span> <span class="nx">trpc</span><span class="p">.</span><span class="nf">useQuery</span><span class="p">([</span>
<span class="dl">"</span><span class="s2">example.hello</span><span class="dl">"</span><span class="p">,</span>
<span class="p">{</span> <span class="na">text</span><span class="p">:</span> <span class="dl">"</span><span class="s2">from tRPC</span><span class="dl">"</span> <span class="p">}</span>
<span class="p">])</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>A First Look at create-t3-app<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"description"</span> <span class="na">content</span><span class="p">=</span><span class="s">"Example t3 project from A First Look at create-t3-app"</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="p">=</span><span class="s">"icon"</span> <span class="na">href</span><span class="p">=</span><span class="s">"/favicon.ico"</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">appContainer</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">title</span><span class="si">}</span><span class="p">></span>
Hello from <span class="p"><</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">purple</span><span class="si">}</span><span class="p">></span>ajc<span class="p"></</span><span class="nt">span</span><span class="p">></span>webdev
<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">grid</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nc">TechnologyCard</span> <span class="na">name</span><span class="p">=</span><span class="s">"Blog"</span> <span class="na">url</span><span class="p">=</span><span class="s">"ajcwebdev.com/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">TechnologyCard</span> <span class="na">name</span><span class="p">=</span><span class="s">"Twitter"</span> <span class="na">url</span><span class="p">=</span><span class="s">"twitter.com/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">TechnologyCard</span> <span class="na">name</span><span class="p">=</span><span class="s">"GitHub"</span> <span class="na">url</span><span class="p">=</span><span class="s">"github.com/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">TechnologyCard</span> <span class="na">name</span><span class="p">=</span><span class="s">"Polywork"</span> <span class="na">url</span><span class="p">=</span><span class="s">"poly.work/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">queryResponse</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span>
<span class="nx">hello</span><span class="p">.</span><span class="nx">data</span>
<span class="p">?</span> <span class="p"><</span><span class="nt">p</span><span class="p">></span><span class="si">{</span><span class="nx">hello</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">greeting</span><span class="si">}</span><span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p">:</span> <span class="p"><</span><span class="nt">p</span><span class="p">></span>Loading..<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="si">}</span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Return to <a href="proxy.php?url=http://localhost:3000" rel="noopener noreferrer">localhost:3000</a> to see the changes.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F07-home-page-with-ajcwebdev-info.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F07-home-page-with-ajcwebdev-info.webp" alt="07 - Homepage with ajcwebdev info"></a></p>
<p>Lastly, I will abstract out the <code>TechnologyCard</code> component into its own file and rename it to <code>Card</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src/components
<span class="nb">echo</span> <span class="o">></span> src/components/Card.tsx
</code></pre>
</div>
<p>Rename <code>TechnologyCardProps</code> to <code>CardProps</code> and create a <code>Card</code> component.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/components/Card.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">cardSection</span><span class="p">,</span> <span class="nx">cardTitle</span><span class="p">,</span> <span class="nx">link</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/home-styles</span><span class="dl">"</span>
<span class="kd">type</span> <span class="nx">CardProps</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="kr">string</span>
<span class="na">url</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Card</span><span class="p">({</span>
<span class="nx">name</span><span class="p">,</span> <span class="nx">url</span>
<span class="p">}:</span> <span class="nx">CardProps</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="s2">`https://</span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2">`</span><span class="si">}</span> <span class="na">target</span><span class="p">=</span><span class="s">"_blank"</span> <span class="na">rel</span><span class="p">=</span><span class="s">"noreferrer"</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">cardSection</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">cardTitle</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">name</span><span class="si">}</span>
<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">link</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">url</span><span class="si">}</span>
<span class="p"></</span><span class="nt">span</span><span class="p">></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"></</span><span class="nt">a</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Import <code>Card</code> into <code>src/pages/index.tsx</code> and remove <code>CardProps</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/index.tsx</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">Card</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../components/Card</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">appContainer</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">purple</span><span class="p">,</span> <span class="nx">grid</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/home-styles</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Home</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>A First Look at create-t3-app<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span>
<span class="na">name</span><span class="p">=</span><span class="s">"description"</span>
<span class="na">content</span><span class="p">=</span><span class="s">"Example t3 project from A First Look at create-t3-app"</span>
<span class="p">/></span>
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="p">=</span><span class="s">"icon"</span> <span class="na">href</span><span class="p">=</span><span class="s">"/favicon.ico"</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">appContainer</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">title</span><span class="si">}</span><span class="p">></span>
Hello from <span class="p"><</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">purple</span><span class="si">}</span><span class="p">></span>ajc<span class="p"></</span><span class="nt">span</span><span class="p">></span>webdev
<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">grid</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Blog"</span> <span class="na">url</span><span class="p">=</span><span class="s">"ajcwebdev.com/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Twitter"</span> <span class="na">url</span><span class="p">=</span><span class="s">"twitter.com/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"GitHub"</span> <span class="na">url</span><span class="p">=</span><span class="s">"github.com/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Polywork"</span> <span class="na">url</span><span class="p">=</span><span class="s">"poly.work/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<h2>
Provision PostgreSQL Database
</h2>
<p>Since this a fullstack framework, it already includes a tool called Prisma for working with our database. Our models will be defined in the <code>prisma/schema.prisma</code> file along with our specific database provider.</p>
<h3>
Add Posts Model to Prisma Schema
</h3>
<p>The initial generated project has the database <code>datasource</code> set to SQLite. Since we want to use a real database, open <code>schema.prisma</code> and update the <code>datasource</code> to the PostgreSQL provider.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
</code></pre>
</div>
<p>In addition to the current models in the schema, add a <code>Post</code> model with an <code>id</code>, <code>title</code>, <code>description</code>, <code>body</code>, and <code>createdAt</code> timestamp.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>// prisma/schema.prisma
model Post {
id String @id
title String
description String
body String
createdAt DateTime @default(now())
}
</code></pre>
</div>
<p>Also, uncomment all appearances of <code>@db.Text</code> on the <code>Account</code> model.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>// prisma/schema.prisma
model Account {
id String @id @default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? @db.Text
access_token String? @db.Text
expires_at Int?
token_type String?
scope String?
id_token String? @db.Text
session_state String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}
</code></pre>
</div>
<h3>
Initialize Railway Database and Run Migration
</h3>
<p>We'll use Railway to provision a PostgreSQL database. First, you need to <a href="proxy.php?url=http://railway.app/" rel="noopener noreferrer">create a Railway account</a> and install the <a href="proxy.php?url=https://docs.railway.app/cli/installation" rel="noopener noreferrer">Railway CLI</a>. If you are unable to login through the browser, run <code>railway login --browserless</code> instead.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>railway login
</code></pre>
</div>
<p>Run the following command, select "Empty Project," and give your project a name.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>railway init
</code></pre>
</div>
<p>To provision the database, add a plugin to your Railway project and select PostgreSQL.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>railway add
</code></pre>
</div>
<p>Set the <code>DATABASE_URL</code> environment variable for your database and create a <code>.env</code> file to hold it.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo </span><span class="nv">DATABASE_URL</span><span class="o">=</span><span class="sb">`</span>railway variables get DATABASE_URL<span class="sb">`</span> <span class="o">></span> .env
</code></pre>
</div>
<p>Run a migration with <code>prisma migrate dev</code> to generate the folders and files necessary to create a new migration. We'll name our migration <code>init</code> with the <code>--name</code> argument.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm prisma migrate dev <span class="nt">--name</span> init
</code></pre>
</div>
<p>After the migration is complete, generate the Prisma client with <code>prisma generate</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm prisma generate
</code></pre>
</div>
<h3>
Seed a Blog Post
</h3>
<p>Right now we'll avoid implementing an endpoint through our app with write, update, or delete functionality since we'll not be including authentication in this section. However, there are at least five different ways you can write data to your database. If you want to skip this section, the tl;dr is send raw SQL queries through the Railway Query dashboard.</p>
<p>The Railway dashboard provides three separate methods for accessing your database (not including using the connection string itself as an environment variable in the project as we'll do later):</p>
<ul>
<li>Execute raw SQL queries under the <strong>Query tab</strong>
</li>
<li>Connect to database with the <a href="proxy.php?url=https://www.postgresql.org/docs/current/app-psql.html" rel="noopener noreferrer"><code>psql</code></a> command under the <strong>Connect tab</strong>
</li>
<li>Enter data with Railway's UI under the <strong>Data tab</strong>
</li>
</ul>
<p>For Prisma, you can either:</p>
<ul>
<li>Login to the <a href="proxy.php?url=https://www.prisma.io/data-platform" rel="noopener noreferrer"><strong>Prisma Data Platform</strong></a> at <a href="proxy.php?url=https://cloud.prisma.io/" rel="noopener noreferrer">cloud.prisma.io</a>
</li>
<li>Run <a href="proxy.php?url=https://www.prisma.io/studio" rel="noopener noreferrer"><strong>Prisma Studio</strong></a> on localhost 5555 with <code>pnpm prisma studio</code>
</li>
</ul>
<blockquote>
<p><em>Note: I'm including Prisma Studio here for completion's sake, but I advise against using it. It's a seemingly sleek and polished product with a very strange input bug that causes it to throw out a value if you don't click away from the input before adding the record to the table. This means you can create a record and have a crucial field get totally wiped and replaced with a blank value.</em></p>
<p><em>Yes, this is should just be for a test database, and yes it's just dummy data. But still, to me, especially for a database tool, this seems fundamentally broken and I honestly can't recommend using this tool in good conscious. I first experienced this bug around the end of 2021 and you can view a recording of the bug in action on an <a href="proxy.php?url=https://www.youtube.com/watch?v=6W-Hkczv92U&t=3648s" rel="noopener noreferrer">episode of Teach Jenn Tech recorded in November 2022</a>.</em></p>
</blockquote>
<p>GUIs are more intuitive for developers without much SQL experience. But unfortunately, they can also be buggy or cumbersome. You especially don't want to be entering every row by hand when you need to input large amounts of data at once. SQL commands provide a more consistent and scalable technique for seeding a database or entering ongoing new data.</p>
<p>The first option on the list (execute raw SQL queries under the Query tab on Railway's dashboard) gives us the best of both worlds. It does not require entering data into any GUI but it also does not require installing a Postgres client like <code>psql</code> to your local machine and connecting to a database instance over the network. We could create a blog post with the following command:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight sql"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">"Post"</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">body</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span>
<span class="s1">'1'</span><span class="p">,</span>
<span class="s1">'A Blog Post Title'</span><span class="p">,</span>
<span class="s1">'This is the description of a blog post'</span><span class="p">,</span>
<span class="s1">'The body of the blog post is here. It is a very good blog post.'</span>
<span class="p">);</span>
</code></pre>
</div>
<p>This SQL command can be entered directly into the text area under the Query tab.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F08-create-post-with-raw-sql-in-query-tab-on-railway-dashboard.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F08-create-post-with-raw-sql-in-query-tab-on-railway-dashboard.webp" alt="08 - Create post with raw sql in query tab on railway dashboard"></a></p>
<p>Click "Run query" and then add two more blog posts:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight sql"><code><span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">"Post"</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">body</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span>
<span class="s1">'2'</span><span class="p">,</span>
<span class="s1">'Second Blog Post'</span><span class="p">,</span>
<span class="s1">'This is the description of ANOTHER blog post'</span><span class="p">,</span>
<span class="s1">'Even better than the last!'</span>
<span class="p">);</span>
<span class="k">INSERT</span> <span class="k">INTO</span> <span class="nv">"Post"</span> <span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">body</span><span class="p">)</span> <span class="k">VALUES</span> <span class="p">(</span>
<span class="s1">'3'</span><span class="p">,</span>
<span class="s1">'The Final Blog Post'</span><span class="p">,</span>
<span class="s1">'This is the description for my final blog post'</span><span class="p">,</span>
<span class="s1">'My blogging career is over. This is the end, thank you.'</span>
<span class="p">);</span>
</code></pre>
</div>
<h2>
Query Posts with tRPC
</h2>
<p>tRPC is a library that is designed for writing typesafe APIs. Instead of importing server code, the client only imports a single TypeScript type. tRPC transforms this type into a fully typesafe client that can be called from the frontend.</p>
<h3>
Create Post Router
</h3>
<p>Create a file to initialize a router instance called <code>postRouter</code>. This will query for all of our posts.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> src/server/router/post.ts
</code></pre>
</div>
<p>Add a query endpoint to the router with the <code>.query()</code> method. It with accept two arguments: <code>name</code> for the name of the endpoint and <code>params</code> for query parameters.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/server/router/post.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./context</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">postRouter</span> <span class="o">=</span> <span class="nf">createRouter</span><span class="p">()</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">all</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Add Prisma query</span>
<span class="p">},</span>
<span class="p">})</span>
</code></pre>
</div>
<p><code>params.resolve</code> implements the endpoint which will be a function with a single <code>req</code> argument that runs the Prisma Client <a href="proxy.php?url=https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#findmany" rel="noopener noreferrer"><code>findMany</code></a> query which returns a list of records, in this case <code>all</code> posts based on the <code>post</code> model.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/server/router/post.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">prisma</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../db/client</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./context</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">postRouter</span> <span class="o">=</span> <span class="nf">createRouter</span><span class="p">()</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">all</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">prisma</span><span class="p">.</span><span class="nx">post</span><span class="p">.</span><span class="nf">findMany</span><span class="p">()</span>
<span class="p">},</span>
<span class="p">})</span>
</code></pre>
</div>
<p><code>params.input</code> provides input validation and will be discussed in the Create Default Query Cell section.</p>
<h3>
Create App Router
</h3>
<p>In <code>src/server/router/index.ts</code>, there is a base <code>appRouter</code> for our server entry point. This can be gradually extended with more types and resolved into a single object.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/server/router/index.ts</span>
<span class="k">import</span> <span class="nx">superjson</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">superjson</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./context</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">exampleRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./example</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">protectedExampleRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./protected-example-router</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">appRouter</span> <span class="o">=</span> <span class="nf">createRouter</span><span class="p">()</span>
<span class="p">.</span><span class="nf">transformer</span><span class="p">(</span><span class="nx">superjson</span><span class="p">)</span>
<span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="dl">"</span><span class="s2">example.</span><span class="dl">"</span><span class="p">,</span> <span class="nx">exampleRouter</span><span class="p">)</span>
<span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="dl">"</span><span class="s2">question.</span><span class="dl">"</span><span class="p">,</span> <span class="nx">protectedExampleRouter</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">AppRouter</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">appRouter</span>
</code></pre>
</div>
<p>Import <code>postRouter</code> and use the <code>.merge()</code> method to combine the following three routes into a single <code>appRouter</code> instance:</p>
<ul>
<li><code>exampleRouter</code></li>
<li><code>postRouter</code></li>
<li>
<code>protectedExampleRouter</code>
</li>
</ul>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/server/router/index.ts</span>
<span class="k">import</span> <span class="nx">superjson</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">superjson</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./context</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">exampleRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./example</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">postRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./post</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">protectedExampleRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./protected-example-router</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">appRouter</span> <span class="o">=</span> <span class="nf">createRouter</span><span class="p">()</span>
<span class="p">.</span><span class="nf">transformer</span><span class="p">(</span><span class="nx">superjson</span><span class="p">)</span>
<span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="dl">"</span><span class="s2">example.</span><span class="dl">"</span><span class="p">,</span> <span class="nx">exampleRouter</span><span class="p">)</span>
<span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="dl">"</span><span class="s2">post.</span><span class="dl">"</span><span class="p">,</span> <span class="nx">postRouter</span><span class="p">)</span>
<span class="p">.</span><span class="nf">merge</span><span class="p">(</span><span class="dl">"</span><span class="s2">question.</span><span class="dl">"</span><span class="p">,</span> <span class="nx">protectedExampleRouter</span><span class="p">)</span>
<span class="k">export</span> <span class="kd">type</span> <span class="nx">AppRouter</span> <span class="o">=</span> <span class="k">typeof</span> <span class="nx">appRouter</span>
</code></pre>
</div>
<p>Queries related to blog posts will be prefixed with <code>post</code> (<code>post.all</code>, <code>post.byId</code>). The hello query example will be prefixed with <code>example</code> as seen earlier with <code>example.hello</code>.</p>
<h3>
Query Posts with useQuery
</h3>
<p>Open <code>src/pages/index.tsx</code> to query all posts and display them on the home page. Create a <code>Posts</code> component and initialize a variable called <code>postsQuery</code> above the return statement. Set the <code>postsQuery</code> variable to the output of <code>post.all</code> with the <code>useQuery()</code> hook.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/index.tsx</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/trpc</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">appContainer</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">purple</span><span class="p">,</span> <span class="nx">body</span><span class="p">,</span> <span class="nx">grid</span><span class="p">,</span> <span class="nx">queryResponse</span><span class="p">,</span> <span class="nx">cardSection</span><span class="p">,</span> <span class="nx">cardTitle</span><span class="p">,</span> <span class="nx">cardDescription</span><span class="p">,</span> <span class="nx">link</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/home-styles</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">Card</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../components/Card</span><span class="dl">"</span>
<span class="kd">const</span> <span class="nx">Posts</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">postsQuery</span> <span class="o">=</span> <span class="nx">trpc</span><span class="p">.</span><span class="nf">useQuery</span><span class="p">([</span>
<span class="dl">'</span><span class="s1">post.all</span><span class="dl">'</span>
<span class="p">])</span>
<span class="k">return </span><span class="p">(...)</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Home</span><span class="p">()</span> <span class="p">{...}</span>
</code></pre>
</div>
<p>As mentioned in the previous section, the <code>appRouter</code> object can be inferred on the client. Stringify the JSON output from <code>postsQuery.data</code> and display the data below the title of the page.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/index.tsx</span>
<span class="kd">const</span> <span class="nx">Posts</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">postsQuery</span> <span class="o">=</span> <span class="nx">trpc</span><span class="p">.</span><span class="nf">useQuery</span><span class="p">([</span>
<span class="dl">'</span><span class="s1">post.all</span><span class="dl">'</span>
<span class="p">])</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">postsQuery</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">queryResponse</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">data</span>
<span class="p">?</span> <span class="p"><</span><span class="nt">p</span><span class="p">></span><span class="si">{</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span><span class="si">}</span><span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p">:</span> <span class="p"><</span><span class="nt">p</span><span class="p">></span>Loading..<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="si">}</span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Return <code>Posts</code> in the <code>Home</code> component.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/index.tsx</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Home</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>A First Look at create-t3-app<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"description"</span> <span class="na">content</span><span class="p">=</span><span class="s">"Example t3 project from A First Look at create-t3-app"</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="p">=</span><span class="s">"icon"</span> <span class="na">href</span><span class="p">=</span><span class="s">"/favicon.ico"</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">appContainer</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">title</span><span class="si">}</span><span class="p">></span>
Hello from <span class="p"><</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">purple</span><span class="si">}</span><span class="p">></span>ajc<span class="p"></</span><span class="nt">span</span><span class="p">></span>webdev
<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">grid</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Blog"</span> <span class="na">url</span><span class="p">=</span><span class="s">"ajcwebdev.com/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Twitter"</span> <span class="na">url</span><span class="p">=</span><span class="s">"twitter.com/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"GitHub"</span> <span class="na">url</span><span class="p">=</span><span class="s">"github.com/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Polywork"</span> <span class="na">url</span><span class="p">=</span><span class="s">"poly.work/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nc">Posts</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F09-display-json-object-with-blog-posts-on-home-page.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F09-display-json-object-with-blog-posts-on-home-page.webp" alt="09 - Display json object with blog posts on the homepage"></a></p>
<p>We have some conditional logic to ensure that a loading message is displayed if the data has not yet returned from the server. But what if there are no blog posts in the database or the server returns an error? This is a case that would be perfectly suited for a Cell.</p>
<h2>
Add Cells for Declarative Data Fetching
</h2>
<p>One of my favorite patterns from Redwood that I have been hoping to see in other frameworks is the concept of a Cell. Cells provide a built-in convention for declarative data fetching that isn't exactly a state machine but shares common benefits and characteristics.</p>
<p>Unlike general purpose finite-state machines, Cells are specifically focused on common data fetching outcomes. They give developers the ability to avoid writing any conditional logic since a cell will manage what happens during the following four potential states of your data fetching:</p>
<ul>
<li>
<strong>Success</strong> - Display the response data</li>
<li>
<strong>Failure</strong> - Handle the error message and provide instructions to the user</li>
<li>
<strong>Empty</strong> - Show a message or graphic communicating an empty list</li>
<li>
<strong>Loading</strong> - Show a message or graphic communicating the data is still loading</li>
</ul>
<p>Thankfully, my hopes were fulfilled when lead tRPC maintainer, <a href="proxy.php?url=https://twitter.com/alexdotjs" rel="noopener noreferrer">Alex Johansson</a> <a href="proxy.php?url=https://github.com/trpc/trpc/pull/1734" rel="noopener noreferrer">opened a PR</a> with a tRPC Cell example that <a href="proxy.php?url=https://twitter.com/alexdotjs/status/1541645155647729664" rel="noopener noreferrer">he acknowledged was influenced by RedwoodJS</a>.</p>
<h3>
Create Default Query Cell
</h3>
<p><code>createQueryCell</code> is used to bootstrap <code>DefaultQueryCell</code> which can be used anywhere in your application.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> src/utils/DefaultQueryCell.tsx
</code></pre>
</div>
<p>Ideally this will one day be internal to either tRPC or <code>create-t3-app</code> and you'll be able to just write cells without thinking about it. But for now, we need to create this ourselves.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/utils/DefaultQueryCell.tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TRPCClientErrorLike</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@trpc/client</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">NextError</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/error</span><span class="dl">"</span>
<span class="k">import</span> <span class="kd">type</span> <span class="p">{</span> <span class="nx">AppRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../server/router/index</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span>
<span class="nx">QueryObserverIdleResult</span><span class="p">,</span>
<span class="nx">QueryObserverLoadingErrorResult</span><span class="p">,</span>
<span class="nx">QueryObserverLoadingResult</span><span class="p">,</span>
<span class="nx">QueryObserverRefetchErrorResult</span><span class="p">,</span>
<span class="nx">QueryObserverSuccessResult</span><span class="p">,</span>
<span class="nx">UseQueryResult</span><span class="p">,</span>
<span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">react-query</span><span class="dl">"</span>
<span class="kd">type</span> <span class="nx">JSXElementOrNull</span> <span class="o">=</span> <span class="nx">JSX</span><span class="p">.</span><span class="nx">Element</span> <span class="o">|</span> <span class="kc">null</span>
<span class="kd">type</span> <span class="nx">ErrorResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span> <span class="o">=</span>
<span class="o">|</span> <span class="nx">QueryObserverLoadingErrorResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span>
<span class="o">|</span> <span class="nx">QueryObserverRefetchErrorResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span>
<span class="kr">interface</span> <span class="nx">CreateQueryCellOptions</span><span class="o"><</span><span class="nx">TError</span><span class="o">></span> <span class="p">{</span>
<span class="na">error</span><span class="p">:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">ErrorResult</span><span class="o"><</span><span class="nx">unknown</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="na">loading</span><span class="p">:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">QueryObserverLoadingResult</span><span class="o"><</span><span class="nx">unknown</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="na">idle</span><span class="p">:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">QueryObserverIdleResult</span><span class="o"><</span><span class="nx">unknown</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="p">}</span>
<span class="kr">interface</span> <span class="nx">QueryCellOptions</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span> <span class="p">{</span>
<span class="na">query</span><span class="p">:</span> <span class="nx">UseQueryResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span>
<span class="nx">error</span><span class="p">?:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">ErrorResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="nx">loading</span><span class="p">?:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">QueryObserverLoadingResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="nx">idle</span><span class="p">?:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">QueryObserverIdleResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="p">}</span>
<span class="kr">interface</span> <span class="nx">QueryCellOptionsWithEmpty</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span>
<span class="kd">extends</span> <span class="nx">QueryCellOptions</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">QueryObserverSuccessResult</span><span class="o"><</span><span class="nb">NonNullable</span><span class="o"><</span><span class="nx">TData</span><span class="o">></span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="na">empty</span><span class="p">:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">QueryObserverSuccessResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="p">}</span>
<span class="kr">interface</span> <span class="nx">QueryCellOptionsNoEmpty</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span>
<span class="kd">extends</span> <span class="nx">QueryCellOptions</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span> <span class="p">{</span>
<span class="na">success</span><span class="p">:</span> <span class="p">(</span><span class="na">query</span><span class="p">:</span> <span class="nx">QueryObserverSuccessResult</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span> <span class="o">=></span> <span class="nx">JSXElementOrNull</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">createQueryCell</span><span class="o"><</span><span class="nx">TError</span><span class="o">></span><span class="p">(</span>
<span class="nx">queryCellOpts</span><span class="p">:</span> <span class="nx">CreateQueryCellOptions</span><span class="o"><</span><span class="nx">TError</span><span class="o">></span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
<span class="kd">function</span> <span class="nf">QueryCell</span><span class="o"><</span><span class="nx">TData</span><span class="o">></span><span class="p">(</span><span class="nx">opts</span><span class="p">:</span> <span class="nx">QueryCellOptionsWithEmpty</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">):</span> <span class="nx">JSXElementOrNull</span>
<span class="kd">function</span> <span class="nf">QueryCell</span><span class="o"><</span><span class="nx">TData</span><span class="o">></span><span class="p">(</span><span class="nx">opts</span><span class="p">:</span> <span class="nx">QueryCellOptionsNoEmpty</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">):</span> <span class="nx">JSXElementOrNull</span>
<span class="kd">function</span> <span class="nf">QueryCell</span><span class="o"><</span><span class="nx">TData</span><span class="o">></span><span class="p">(</span><span class="nx">opts</span><span class="p">:</span>
<span class="o">|</span> <span class="nx">QueryCellOptionsNoEmpty</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span>
<span class="o">|</span> <span class="nx">QueryCellOptionsWithEmpty</span><span class="o"><</span><span class="nx">TData</span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">,</span>
<span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">query</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">opts</span>
<span class="k">if </span><span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">success</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="dl">'</span><span class="s1">empty</span><span class="dl">'</span> <span class="k">in</span> <span class="nx">opts</span> <span class="o">&&</span>
<span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nx">data</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">||</span>
<span class="p">(</span><span class="nb">Array</span><span class="p">.</span><span class="nf">isArray</span><span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nx">data</span><span class="p">)</span> <span class="o">&&</span> <span class="nx">query</span><span class="p">.</span><span class="nx">data</span><span class="p">.</span><span class="nx">length</span> <span class="o">===</span> <span class="mi">0</span><span class="p">))</span>
<span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">opts</span><span class="p">.</span><span class="nf">empty</span><span class="p">(</span><span class="nx">query</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">opts</span><span class="p">.</span><span class="nf">success</span><span class="p">(</span><span class="nx">query</span> <span class="k">as</span> <span class="nx">QueryObserverSuccessResult</span><span class="o"><</span><span class="nb">NonNullable</span><span class="o"><</span><span class="nx">TData</span><span class="o">></span><span class="p">,</span> <span class="nx">TError</span><span class="o">></span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">opts</span><span class="p">.</span><span class="nx">error</span><span class="p">?.(</span><span class="nx">query</span><span class="p">)</span> <span class="o">??</span> <span class="nx">queryCellOpts</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">query</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">loading</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">opts</span><span class="p">.</span><span class="nx">loading</span><span class="p">?.(</span><span class="nx">query</span><span class="p">)</span> <span class="o">??</span> <span class="nx">queryCellOpts</span><span class="p">.</span><span class="nf">loading</span><span class="p">(</span><span class="nx">query</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">query</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">idle</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">opts</span><span class="p">.</span><span class="nx">idle</span><span class="p">?.(</span><span class="nx">query</span><span class="p">)</span> <span class="o">??</span> <span class="nx">queryCellOpts</span><span class="p">.</span><span class="nf">idle</span><span class="p">(</span><span class="nx">query</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">null</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">QueryCell</span>
<span class="p">}</span>
<span class="kd">type</span> <span class="nx">TError</span> <span class="o">=</span> <span class="nx">TRPCClientErrorLike</span><span class="o"><</span><span class="nx">AppRouter</span><span class="o">></span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">DefaultQueryCell</span> <span class="o">=</span> <span class="nx">createQueryCell</span><span class="o"><</span><span class="nx">TError</span><span class="o">></span><span class="p">({</span>
<span class="na">error</span><span class="p">:</span> <span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><</span><span class="nc">NextError</span>
<span class="na">title</span><span class="p">=</span><span class="si">{</span><span class="nx">result</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="si">}</span>
<span class="na">statusCode</span><span class="p">=</span><span class="si">{</span><span class="nx">result</span><span class="p">.</span><span class="nx">error</span><span class="p">.</span><span class="nx">data</span><span class="p">?.</span><span class="nx">httpStatus</span> <span class="o">??</span> <span class="mi">500</span><span class="si">}</span>
<span class="p">/></span>
<span class="p">),</span>
<span class="na">idle</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p"><</span><span class="nt">div</span><span class="p">></span>Loading...<span class="p"></</span><span class="nt">div</span><span class="p">>,</span>
<span class="na">loading</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="p"><</span><span class="nt">div</span><span class="p">></span>Loading...<span class="p"></</span><span class="nt">div</span><span class="p">>,</span>
<span class="p">})</span>
</code></pre>
</div>
<p>We want to be able to query an individual blog post based on its <code>id</code>. Create a <code>post</code> page with a <a href="proxy.php?url=https://nextjs.org/docs/routing/dynamic-routes" rel="noopener noreferrer">dynamic route</a> based on the <code>id</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src/pages/post
<span class="nb">echo</span> <span class="o">></span> src/pages/post/<span class="se">\[</span><span class="nb">id</span><span class="se">\]</span>.tsx
</code></pre>
</div>
<p>Since we'll be sending data to the database, we need to validate the <code>input</code>. <a href="proxy.php?url=https://github.com/colinhacks/zod" rel="noopener noreferrer"><code>zod</code></a> is a TypeScript schema validator with static type inference. We'll also import <code>TRPCError</code> for error handling.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/server/router/post.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">prisma</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../db/client</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TRPCError</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@trpc/server</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">z</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">zod</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./context</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">postRouter</span> <span class="o">=</span> <span class="nf">createRouter</span><span class="p">()</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">all</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">prisma</span><span class="p">.</span><span class="nx">post</span><span class="p">.</span><span class="nf">findMany</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre>
</div>
<p>Add <code>byId</code> query to Post router in <code>src/server/router/post.ts</code> and destructure the <code>id</code> from the <code>input</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/server/router/post.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">prisma</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../db/client</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TRPCError</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@trpc/server</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">z</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">zod</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./context</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">postRouter</span> <span class="o">=</span> <span class="nf">createRouter</span><span class="p">()</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">all</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">prisma</span><span class="p">.</span><span class="nx">post</span><span class="p">.</span><span class="nf">findMany</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">byId</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">input</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">object</span><span class="p">({</span> <span class="na">id</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">string</span><span class="p">()</span> <span class="p">}),</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">({</span> <span class="nx">input</span> <span class="p">})</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">input</span>
<span class="p">},</span>
<span class="p">})</span>
</code></pre>
</div>
<p><code>findUnique</code> query lets you retrieve a single database record based on the <code>id</code> provided by passing it to Prisma's <code>where</code> option.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/server/router/post.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">prisma</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../db/client</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TRPCError</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@trpc/server</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">z</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">zod</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./context</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">postRouter</span> <span class="o">=</span> <span class="nf">createRouter</span><span class="p">()</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">all</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">prisma</span><span class="p">.</span><span class="nx">post</span><span class="p">.</span><span class="nf">findMany</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">byId</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">input</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">object</span><span class="p">({</span> <span class="na">id</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">string</span><span class="p">()</span> <span class="p">}),</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">({</span> <span class="nx">input</span> <span class="p">})</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">input</span>
<span class="kd">const</span> <span class="nx">post</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">prisma</span><span class="p">.</span><span class="nx">post</span><span class="p">.</span><span class="nf">findUnique</span><span class="p">({</span>
<span class="na">where</span><span class="p">:</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span>
<span class="p">})</span>
<span class="p">},</span>
<span class="p">})</span>
</code></pre>
</div>
<p>Last but not least, throw an error with <code>TRPCError</code> if a post is not returned.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// src/server/router/post.ts</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">prisma</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../db/client</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">TRPCError</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@trpc/server</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">z</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">zod</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">createRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./context</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">postRouter</span> <span class="o">=</span> <span class="nf">createRouter</span><span class="p">()</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">all</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">prisma</span><span class="p">.</span><span class="nx">post</span><span class="p">.</span><span class="nf">findMany</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">.</span><span class="nf">query</span><span class="p">(</span><span class="dl">'</span><span class="s1">byId</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">input</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">object</span><span class="p">({</span> <span class="na">id</span><span class="p">:</span> <span class="nx">z</span><span class="p">.</span><span class="nf">string</span><span class="p">()</span> <span class="p">}),</span>
<span class="k">async</span> <span class="nf">resolve</span><span class="p">({</span> <span class="nx">input</span> <span class="p">})</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">input</span>
<span class="kd">const</span> <span class="nx">post</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">prisma</span><span class="p">.</span><span class="nx">post</span><span class="p">.</span><span class="nf">findUnique</span><span class="p">({</span>
<span class="na">where</span><span class="p">:</span> <span class="p">{</span> <span class="nx">id</span> <span class="p">}</span>
<span class="p">})</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">post</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nc">TRPCError</span><span class="p">({</span>
<span class="na">code</span><span class="p">:</span> <span class="dl">'</span><span class="s1">NOT_FOUND</span><span class="dl">'</span><span class="p">,</span>
<span class="na">message</span><span class="p">:</span> <span class="s2">`No post with id '</span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2">'`</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">post</span>
<span class="p">}</span>
<span class="p">})</span>
</code></pre>
</div>
<h3>
Create Post Page
</h3>
<p>Import <code>DefaultQueryCell</code> in <code>src/pages/post/[id].tsx</code> and create a component called <code>PostPage</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/post/[id].tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/router</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DefaultQueryCell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../utils/DefaultQueryCell</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../utils/trpc</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">PostPage</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(...)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Return <code>DefaultQueryCell</code> and pass <code>postQuery</code> to <code>query</code> and <code>data</code> to <code>success</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/post/[id].tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/router</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DefaultQueryCell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../utils/DefaultQueryCell</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../utils/trpc</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">PostPage</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nf">useRouter</span><span class="p">().</span><span class="nx">query</span><span class="p">.</span><span class="nx">id</span> <span class="k">as</span> <span class="kr">string</span>
<span class="kd">const</span> <span class="nx">postQuery</span> <span class="o">=</span> <span class="nx">trpc</span><span class="p">.</span><span class="nf">useQuery</span><span class="p">([</span>
<span class="dl">'</span><span class="s1">post.byId</span><span class="dl">'</span><span class="p">,</span>
<span class="p">{</span> <span class="nx">id</span> <span class="p">}</span>
<span class="p">])</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nc">DefaultQueryCell</span>
<span class="na">query</span><span class="p">=</span><span class="si">{</span><span class="nx">postQuery</span><span class="si">}</span>
<span class="na">success</span><span class="p">=</span><span class="si">{</span><span class="p">({</span> <span class="nx">data</span> <span class="p">})</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span><span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"description"</span> <span class="na">content</span><span class="p">=</span><span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">description</span><span class="si">}</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span><span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span><span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">body</span><span class="si">}</span><span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">em</span><span class="p">></span>Created <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">createdAt</span><span class="p">.</span><span class="nf">toLocaleDateString</span><span class="p">()</span><span class="si">}</span><span class="p"></</span><span class="nt">em</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span><span class="si">}</span>
<span class="p">/></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Lastly, add <code>blogContainer</code>, <code>blogTitle</code>, and <code>blogBody</code> for styling the posts.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/post/[id].tsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useRouter</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/router</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DefaultQueryCell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../utils/DefaultQueryCell</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../utils/trpc</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">blogContainer</span><span class="p">,</span> <span class="nx">blogTitle</span><span class="p">,</span> <span class="nx">blogBody</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../../styles/blog-styles</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">PostPage</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">id</span> <span class="o">=</span> <span class="nf">useRouter</span><span class="p">().</span><span class="nx">query</span><span class="p">.</span><span class="nx">id</span> <span class="k">as</span> <span class="kr">string</span>
<span class="kd">const</span> <span class="nx">postQuery</span> <span class="o">=</span> <span class="nx">trpc</span><span class="p">.</span><span class="nf">useQuery</span><span class="p">([</span>
<span class="dl">'</span><span class="s1">post.byId</span><span class="dl">'</span><span class="p">,</span>
<span class="p">{</span> <span class="nx">id</span> <span class="p">}</span>
<span class="p">])</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nc">DefaultQueryCell</span>
<span class="na">query</span><span class="p">=</span><span class="si">{</span><span class="nx">postQuery</span><span class="si">}</span>
<span class="na">success</span><span class="p">=</span><span class="si">{</span><span class="p">({</span> <span class="nx">data</span> <span class="p">})</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span><span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span><span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"description"</span> <span class="na">content</span><span class="p">=</span><span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">description</span><span class="si">}</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">blogContainer</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">blogTitle</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">title</span><span class="si">}</span>
<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">blogBody</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">body</span><span class="si">}</span>
<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"><</span><span class="nt">em</span><span class="p">></span>Created <span class="si">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">createdAt</span><span class="p">.</span><span class="nf">toLocaleDateString</span><span class="p">()</span><span class="si">}</span><span class="p"></</span><span class="nt">em</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span><span class="si">}</span>
<span class="p">/></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:3000/post/1" rel="noopener noreferrer">localhost:3000/post/1</a> to see your first blog post.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F10-first-blog-post-page.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F10-first-blog-post-page.webp" alt="10 - First blog post page"></a></p>
<h3>
Create Posts Cell
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> src/components/PostsCell.tsx
<span class="nb">echo</span> <span class="o">></span> src/styles/blog-styles.ts
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/styles/blog-styles.ts</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">blogContainer</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">container mx-auto min-h-screen p-4</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">blogTitle</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">text-5xl leading-normal font-extrabold text-gray-700</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">blogBody</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">mb-2 text-lg text-gray-700</span><span class="dl">"</span>
<span class="k">export</span> <span class="kd">const</span> <span class="nx">blogHeader</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">text-5xl leading-normal font-extrabold text-gray-700</span><span class="dl">"</span>
</code></pre>
</div>
<p>Create a <code>PostsCell</code> function and import the following above it:</p>
<ul>
<li>
<code>Link</code> for linking to each blog post's page</li>
<li>
<code>blogHeader</code> and <code>link</code> for styling the list output from the Cell</li>
<li>
<code>DefaultQueryCell</code> for creating the cell</li>
<li>
<code>trpc</code> for executing the query
</li>
</ul>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/components/PostsCell.tsx</span>
<span class="k">import</span> <span class="nx">Link</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/link</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">blogHeader</span><span class="p">,</span> <span class="nx">link</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/blog-styles</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DefaultQueryCell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/DefaultQueryCell</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/trpc</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">PostsCell</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(...)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Create a type called <code>BlogPostProps</code> with an <code>id</code> and <code>title</code> of type <code>string</code>. Delete the <code>Posts</code> component in <code>src/pages/index.tsx</code> and move the <code>useQuery</code> hook into the <code>PostsCell</code> component.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/components/PostsCell.tsx</span>
<span class="k">import</span> <span class="nx">Link</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/link</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">blogHeader</span><span class="p">,</span> <span class="nx">link</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/blog-styles</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DefaultQueryCell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/DefaultQueryCell</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/trpc</span><span class="dl">"</span>
<span class="kd">type</span> <span class="nx">BlogPostProps</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">id</span><span class="p">:</span> <span class="kr">string</span>
<span class="na">title</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">PostsCell</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">postsQuery</span> <span class="o">=</span> <span class="nx">trpc</span><span class="p">.</span><span class="nf">useQuery</span><span class="p">([</span>
<span class="dl">'</span><span class="s1">post.all</span><span class="dl">'</span>
<span class="p">])</span>
<span class="k">return </span><span class="p">(...)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Return <code>DefaultQueryCell</code> with the <code>query</code> set to <code>postsQuery</code>. <code>success</code> will map over the <code>data</code> object and display a link for each blog post.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/components/PostsCell.tsx</span>
<span class="k">import</span> <span class="nx">Link</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/link</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">blogHeader</span><span class="p">,</span> <span class="nx">link</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/blog-styles</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">DefaultQueryCell</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/DefaultQueryCell</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">trpc</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../utils/trpc</span><span class="dl">"</span>
<span class="kd">type</span> <span class="nx">BlogPostProps</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">id</span><span class="p">:</span> <span class="kr">string</span>
<span class="na">title</span><span class="p">:</span> <span class="kr">string</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">PostsCell</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">postsQuery</span> <span class="o">=</span> <span class="nx">trpc</span><span class="p">.</span><span class="nf">useQuery</span><span class="p">([</span>
<span class="dl">'</span><span class="s1">post.all</span><span class="dl">'</span>
<span class="p">])</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nt">h2</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">blogHeader</span><span class="si">}</span><span class="p">></span>Posts<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="si">{</span><span class="nx">postsQuery</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">loading</span><span class="dl">'</span><span class="si">}</span>
<span class="p"><</span><span class="nc">DefaultQueryCell</span>
<span class="na">query</span><span class="p">=</span><span class="si">{</span><span class="nx">postsQuery</span><span class="si">}</span>
<span class="na">success</span><span class="p">=</span><span class="si">{</span><span class="p">({</span> <span class="nx">data</span> <span class="p">}:</span> <span class="kr">any</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span>
<span class="nx">data</span><span class="p">.</span><span class="nf">map</span><span class="p">(({</span><span class="nx">id</span><span class="p">,</span> <span class="nx">title</span><span class="p">}:</span> <span class="nx">BlogPostProps</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><</span><span class="nc">Link</span> <span class="na">key</span><span class="p">=</span><span class="si">{</span><span class="nx">id</span><span class="si">}</span> <span class="na">href</span><span class="p">=</span><span class="si">{</span><span class="s2">`/post/</span><span class="p">${</span><span class="nx">id</span><span class="p">}</span><span class="s2">`</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">link</span><span class="si">}</span><span class="p">></span>
<span class="si">{</span><span class="nx">title</span><span class="si">}</span>
<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"></</span><span class="nc">Link</span><span class="p">></span>
<span class="p">))</span>
<span class="p">)</span><span class="si">}</span>
<span class="na">empty</span><span class="p">=</span><span class="si">{</span><span class="p">()</span> <span class="o">=></span> <span class="p"><</span><span class="nt">p</span><span class="p">></span>WE NEED POSTS!!!<span class="p"></</span><span class="nt">p</span><span class="p">></span><span class="si">}</span>
<span class="p">/></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Import <code>PostsCell</code> in <code>src/pages/index.tsx</code> and return the component in the <code>Home</code> function.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight tsx"><code><span class="c1">// src/pages/index.tsx</span>
<span class="k">import</span> <span class="nx">Head</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">next/head</span><span class="dl">"</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">appContainer</span><span class="p">,</span> <span class="nx">title</span><span class="p">,</span> <span class="nx">purple</span><span class="p">,</span> <span class="nx">grid</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../styles/home-styles</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">Card</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../components/Card</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">PostsCell</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">../components/PostsCell</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">Home</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span>A First Look at create-t3-app<span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"description"</span> <span class="na">content</span><span class="p">=</span><span class="s">"Example t3 project from A First Look at create-t3-app"</span> <span class="p">/></span>
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="p">=</span><span class="s">"icon"</span> <span class="na">href</span><span class="p">=</span><span class="s">"/favicon.ico"</span> <span class="p">/></span>
<span class="p"></</span><span class="nc">Head</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">appContainer</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">title</span><span class="si">}</span><span class="p">></span>
Hello from <span class="p"><</span><span class="nt">span</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">purple</span><span class="si">}</span><span class="p">></span>ajc<span class="p"></</span><span class="nt">span</span><span class="p">></span>webdev
<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">className</span><span class="p">=</span><span class="si">{</span><span class="nx">grid</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Blog"</span> <span class="na">url</span><span class="p">=</span><span class="s">"ajcwebdev.com/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Twitter"</span> <span class="na">url</span><span class="p">=</span><span class="s">"twitter.com/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"GitHub"</span> <span class="na">url</span><span class="p">=</span><span class="s">"github.com/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"><</span><span class="nc">Card</span> <span class="na">name</span><span class="p">=</span><span class="s">"Polywork"</span> <span class="na">url</span><span class="p">=</span><span class="s">"poly.work/ajcwebdev/"</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nc">PostsCell</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F11-home-page-with-blog-post-titles.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F11-home-page-with-blog-post-titles.webp" alt="11 - Homepage with blog post titles"></a></p>
<h2>
Deployment
</h2>
<p>Commit your current changes and create a new repository on GitHub with the <a href="proxy.php?url=https://cli.github.com/" rel="noopener noreferrer">GitHub CLI</a>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"ct3a"</span>
gh repo create ajcwebdev-t3 <span class="nt">--public</span> <span class="nt">--push</span> <span class="se">\</span>
<span class="nt">--source</span><span class="o">=</span><span class="nb">.</span> <span class="se">\</span>
<span class="nt">--description</span><span class="o">=</span><span class="s2">"An example T3 application with Next.js, Prisma, tRPC, and Tailwind deployed on Vercel and Fly."</span> <span class="se">\</span>
<span class="nt">--remote</span><span class="o">=</span>upstream
</code></pre>
</div>
<h3>
Deploy to Vercel
</h3>
<p>Install the <code>vercel</code> CLI on your machine or add it to your project with <code>pnpm</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm add <span class="nt">-D</span> vercel
</code></pre>
</div>
<p>Use the following command to pass your database environment variable and deploy to Vercel. Use <code>--confirm</code> to give the default answer for each question.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm vercel <span class="nt">--env</span> <span class="nv">DATABASE_URL</span><span class="o">=</span>YOUR_URL_HERE
</code></pre>
</div>
<blockquote>
<p><em>After the first deployment this command will deploy to a preview branch. You will need to include <code>--prod</code> to push changes directly to the live site for future deployments.</em></p>
</blockquote>
<p>Open <code>ajcwebdev-t3.vercel.app</code> to see your blog.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F12-home-page-deployed-on-vercel.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F12-home-page-deployed-on-vercel.webp" alt="12 - Homepage deployed on vercel"></a></p>
<p>API endpoints are exposed on <code>api/trpc/</code>, so <a href="proxy.php?url=https://ajcwebdev-t3.vercel.app/api/trpc/post.all" rel="noopener noreferrer">ajcwebdev-t3.vercel.app/api/trpc/post.all</a> will display all blog posts.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F13-all-posts-trpc-endpoint-on-vercel-api-route.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F13-all-posts-trpc-endpoint-on-vercel-api-route.webp" alt="13 - All posts trpc endpoint on vercel api route"></a></p>
<p>Or you can hit the endpoint with curl:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="s2">"https://ajcwebdev-t3.vercel.app/api/trpc/post.all"</span> | npx json
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="kc">null</span><span class="p">,</span><span class="w">
</span><span class="nl">"result"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"data"</span><span class="p">,</span><span class="w">
</span><span class="nl">"data"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"json"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1"</span><span class="p">,</span><span class="w">
</span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"A Blog Post Title"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"This is the description of a blog post"</span><span class="p">,</span><span class="w">
</span><span class="nl">"body"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The body of the blog post is here. It is a very good blog post."</span><span class="p">,</span><span class="w">
</span><span class="nl">"createdAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2022-08-13T08:30:59.344Z"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2"</span><span class="p">,</span><span class="w">
</span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Second Blog Post"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"This is the description of ANOTHER blog post"</span><span class="p">,</span><span class="w">
</span><span class="nl">"body"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Even better than the last!"</span><span class="p">,</span><span class="w">
</span><span class="nl">"createdAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2022-08-13T08:36:59.790Z"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"id"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3"</span><span class="p">,</span><span class="w">
</span><span class="nl">"title"</span><span class="p">:</span><span class="w"> </span><span class="s2">"The Final Blog Post"</span><span class="p">,</span><span class="w">
</span><span class="nl">"description"</span><span class="p">:</span><span class="w"> </span><span class="s2">"This is the description for my final blog post"</span><span class="p">,</span><span class="w">
</span><span class="nl">"body"</span><span class="p">:</span><span class="w"> </span><span class="s2">"My blogging career is over. This is the end, thank you."</span><span class="p">,</span><span class="w">
</span><span class="nl">"createdAt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2022-08-13T08:40:32.133Z"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">],</span><span class="w">
</span><span class="nl">"meta"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"values"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="err">...</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p>For single blog posts try any of the following:</p>
<ul>
<li><a href="proxy.php?url=https://ajcwebdev-t3.vercel.app/api/trpc/post.byId?batch=1&input=%7B%220%22%3A%7B%22json%22%3A%7B%22id%22%3A%221%22%7D%7D%7D" rel="noopener noreferrer"><code>%7B%220%22%3A%7B%22json%22%3A%7B%22id%22%3A%221%22%7D%7D%7D</code></a></li>
<li><a href="proxy.php?url=https://ajcwebdev-t3.vercel.app/api/trpc/post.byId?batch=1&input=%7B%220%22%3A%7B%22json%22%3A%7B%22id%22%3A%222%22%7D%7D%7D" rel="noopener noreferrer"><code>%7B%220%22%3A%7B%22json%22%3A%7B%22id%22%3A%222%22%7D%7D%7D</code></a></li>
<li><a href="proxy.php?url=https://ajcwebdev-t3.vercel.app/api/trpc/post.byId?batch=1&input=%7B%220%22%3A%7B%22json%22%3A%7B%22id%22%3A%223%22%7D%7D%7D" rel="noopener noreferrer"><code>%7B%220%22%3A%7B%22json%22%3A%7B%22id%22%3A%223%22%7D%7D%7D</code></a></li>
</ul>
<p>And copy them to the end of:</p>
<blockquote>
<p><code>https://ajcwebdev-t3.vercel.app/api/trpc/post.byId?batch=1&input=</code></p>
</blockquote>
<p>Check PageSpeed Insights for Desktop <a href="proxy.php?url=https://pagespeed.web.dev/report?url=https%3A%2F%2Fajcwebdev-t3.vercel.app%2F&form_factor=desktop" rel="noopener noreferrer">here</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F14-pagespeed-insight-desktop.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F14-pagespeed-insight-desktop.webp" alt="14 - Pagespeed insight desktop"></a></p>
<p>If what I know about these metrics is correct, then I believe 100 is considered a preferable score when compared to other scores which are not 100.</p>
<p>Check PageSpeed Insights for Mobile <a href="proxy.php?url=https://pagespeed.web.dev/report?url=https%3A%2F%2Fajcwebdev-t3.vercel.app%2F&form_factor=mobile" rel="noopener noreferrer">here</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F15-pagespeed-insight-mobile.webp" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fajcwebdev.com%2Fimages%2F2022%2F08%2F13%2Fcreate-t3-app%2F15-pagespeed-insight-mobile.webp" alt="15 - Pagespeed insight mobile"></a></p>
<p>100 again! Equally as preferable!!</p>
<h3>
Deploy to Fly
</h3>
<p>Since <code>create-t3-app</code> is mostly Next.js and Prisma at the end of the day, it can be deployed very easily on platforms like Vercel. But, in return for that ease of use, you will be taking a performance hit whenever your database is queried.</p>
<p>When Prisma is running in a Lambda function it has a noticeable cold start. Future guides in the ct3a documentation will demonstrate how to use platforms like Fly, Railway, and Render to deploy your project to a long running server. Install the <a href="proxy.php?url=https://fly.io/docs/hands-on/install-flyctl/" rel="noopener noreferrer"><code>flyctl</code></a> CLI and run the following command to initialize your project.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>fly launch <span class="nt">--remote-only</span> <span class="se">\</span>
<span class="nt">--name</span> ajcwebdev-t3 <span class="se">\</span>
<span class="nt">--region</span> ord <span class="se">\</span>
<span class="nt">--env</span> <span class="nv">DATABASE_URL</span><span class="o">=</span>YOUR_URL_HERE
</code></pre>
</div>
<p><code>flyctl platform regions</code> to see available regions.</p>
<h2>
Resources, Articles, and Videos
</h2>
<ul>
<li>
<a href="proxy.php?url=https://create.t3.gg/" rel="noopener noreferrer">create.t3.gg</a>
<ul>
<li><a href="proxy.php?url=https://github.com/t3-oss/create-t3-app/" rel="noopener noreferrer">Repo</a></li>
<li><a href="proxy.php?url=https://create-t3-app-docs.vercel.app/en/introduction" rel="noopener noreferrer">Documentation</a></li>
</ul>
</li>
<li>
<a href="proxy.php?url=https://init.tips/" rel="noopener noreferrer">init.tips</a>
<ul>
<li><a href="proxy.php?url=https://github.com/t3-oss/init.tips/" rel="noopener noreferrer">Repo</a></li>
<li><a href="proxy.php?url=https://init.tips/others" rel="noopener noreferrer">Documentation</a></li>
</ul>
</li>
</ul>
<div class="table-wrapper-paragraph"><table>
<thead>
<tr>
<th>Date</th>
<th>Title</th>
</tr>
</thead>
<tbody>
<tr>
<td>2022-08-10</td>
<td><a href="proxy.php?url=https://dev.to/nexxeln/build-a-full-stack-app-with-create-t3-app-5e1e">Build a full stack app with create-t3-app</a></td>
</tr>
<tr>
<td>2022-07-10</td>
<td><a href="proxy.php?url=https://github.com/t3-oss/create-t3-app/issues/166" rel="noopener noreferrer">ct3a End to End Tutorial Proposal</a></td>
</tr>
<tr>
<td>2022-06-26</td>
<td><a href="proxy.php?url=https://www.nexxel.dev/blog/ct3a" rel="noopener noreferrer">T3 stack and my most popular open source project ever</a></td>
</tr>
<tr>
<td>2022-05-21</td>
<td><a href="proxy.php?url=https://dev.to/nexxeln/build-end-to-end-typesafe-apis-with-trpc-3o3f">Build end to end typesafe APIs with tRPC</a></td>
</tr>
</tbody>
</table></div>
<div class="table-wrapper-paragraph"><table>
<thead>
<tr>
<th>Date</th>
<th>Title</th>
</tr>
</thead>
<tbody>
<tr>
<td>2022-07-17</td>
<td><a href="proxy.php?url=https://www.youtube.com/watch?v=dXRRY37MPuk" rel="noopener noreferrer">Build a Live Chat Application with the T3 Stack</a></td>
</tr>
<tr>
<td>2022-07-12</td>
<td><a href="proxy.php?url=https://www.youtube.com/watch?v=H-FXwnEjSsI" rel="noopener noreferrer">The T3 Stack - How We Built It</a></td>
</tr>
<tr>
<td>2022-07-10</td>
<td><a href="proxy.php?url=https://www.youtube.com/watch?v=VJH8dsPtbeU" rel="noopener noreferrer">An Overview of the create T3 App</a></td>
</tr>
<tr>
<td>2022-07-03</td>
<td><a href="proxy.php?url=https://www.youtube.com/watch?v=PbjHxIuHduU" rel="noopener noreferrer">The BEST Stack For Your Next Project</a></td>
</tr>
<tr>
<td>2022-06-28</td>
<td><a href="proxy.php?url=https://www.youtube.com/watch?v=syEWlxVFUrY" rel="noopener noreferrer">Build a Blog With the T3 Stack</a></td>
</tr>
</tbody>
</table></div>
nextjstrpctailwindcsstypescriptA First Look at Ethers and HardhatajcwebdevFri, 04 Mar 2022 06:01:05 +0000
https://dev.to/ajcwebdev/a-first-look-at-ethers-and-hardhat-5848
https://dev.to/ajcwebdev/a-first-look-at-ethers-and-hardhat-5848<h2>
Outline
</h2>
<ul>
<li>Introduction</li>
<li>
Create Project
<ul>
<li>Install Dependencies</li>
<li>Initialize Hardhat Environment</li>
<li>Sample Deployment Script</li>
<li>Hardhat Configuration</li>
<li>Greeter Solidity Contract</li>
</ul>
</li>
<li>
Deploy to Testnet
<ul>
<li>Compile Application Binary Interface</li>
<li>Start Test Node</li>
<li>Run Deployment Script</li>
</ul>
</li>
<li>
Connect to MetaMask
<ul>
<li>Import Account into MetaMask</li>
</ul>
</li>
<li>
Create React App
<ul>
<li>Start Development Server</li>
<li>Connect React App to MetaMask</li>
</ul>
</li>
<li>
Deploy to Ropsten Testnet
<ul>
<li>Connect MetaMask to Ropsten</li>
<li>Create an Alchemy Account</li>
<li>Create an Alchemy Application</li>
<li>Get Alchemy Key</li>
<li>Add Alchemy Endpoint to Hardhat Configuration</li>
<li>Deploy Contract to Ropsten</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong><em>All of this project's code can be found in the <a href="proxy.php?url=https://github.com/ajcwebdev/a-first-look/tree/main/web3/ethers/">First Look monorepo</a> on my GitHub.</em></strong></p>
</blockquote>
<h2>
Introduction
</h2>
<p><a href="proxy.php?url=https://ethers.org/">ethers.js</a> is a library that aims to be a complete and compact tool for interacting with the Ethereum Blockchain and its ecosystem. This example uses <a href="proxy.php?url=https://hardhat.org/">Hardhat</a>, an Ethereum development environment, to compile a smart contract and deploy it to <a href="proxy.php?url=https://ropsten.etherscan.io/">Ropsten</a>, an Ethereum test network that allows for blockchain development testing.</p>
<p>The contract is written in <a href="proxy.php?url=https://docs.soliditylang.org/">Solidity</a>, an object-oriented, high-level language for implementing smart contracts. After deploying the contract, we will generate a boilerplate React application with <a href="proxy.php?url=https://vitejs.dev/">Vite</a> and connect the application to our smart contract running on Ropsten. <a href="proxy.php?url=https://www.alchemy.com/">Alchemy</a> provides a <a href="proxy.php?url=https://www.infoq.com/articles/blockchain-as-a-service-get-block/">managed node</a> that enables connecting to various tools in the blockchain ecosystem.</p>
<h2>
Create Project
</h2>
<p>This article is heavily based on Nader Dabit's definitive article, <a href="proxy.php?url=https://dev.to/dabit3/the-complete-guide-to-full-stack-ethereum-development-3j13">The Complete Guide to Full Stack Ethereum Development</a>. I've made a few alternations including using Vite instead of Create React App and the inclusion of <code>dotenv</code> for environment variables but all credit due to Sensei Dabit.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm create vite ajcwebdev-ethers <span class="nt">--template</span> react
<span class="nb">cd </span>ajcwebdev-ethers
</code></pre>
</div>
<p>Remove <code>"type": "module"</code> from <code>package.json</code>.</p>
<h3>
Install Dependencies
</h3>
<p>In addition to Ethers and Hardhat we will also install <a href="proxy.php?url=https://ethereum-waffle.readthedocs.io/">Waffle</a> and <a href="proxy.php?url=https://www.chaijs.com/">Chai</a> for testing our contracts.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm add dotenv ethers hardhat chai ethereum-waffle <span class="se">\</span>
@nomiclabs/hardhat-ethers @nomiclabs/hardhat-waffle
</code></pre>
</div>
<p>Create a <code>.env</code> file to store environment variables later in the tutorial.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">touch</span> .env
</code></pre>
</div>
<p>The file will include the following three variables:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>ALCHEMY_URL=
ALCHEMY_KEY=
VITE_GREETER_ADDRESS=
</code></pre>
</div>
<h3>
Initialize Hardhat Environment
</h3>
<p>Developing smart contracts requires the ability to deploy your contracts, run tests, and debug Solidity code. We will also need a way to compile Solidity code into code that can be run in a client-side application. Hardhat compiles your contracts and runs them on a development network. This lets you develop without having to deal with live environments.</p>
<p>We'll create the following files/directories:</p>
<ul>
<li>
<code>hardhat.config.js</code> - Entire Hardhat setup including config, plugins, and custom tasks.</li>
<li>
<code>scripts</code> - Contains a script named <code>deploy.js</code> that deploys your smart contract when executed</li>
<li>
<code>contracts</code> - Contains a file called <code>Greeter.sol</code> with an example Solidity smart contract
</li>
</ul>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>scripts contracts
<span class="nb">echo</span> <span class="o">></span> scripts/deploy.js
<span class="nb">echo</span> <span class="o">></span> contracts/Greeter.sol
<span class="nb">echo</span> <span class="o">></span> hardhat.config.js
</code></pre>
</div>
<h3>
Sample Deployment Script
</h3>
<p>In <code>deploy.js</code> we use <code>getContractFactory</code> to create a <code>ContractFactory</code>, an abstraction used to deploy new smart contracts. <code>Greeter</code> is a factory for instances of our greeting contract. Calling <code>deploy()</code> on a <code>ContractFactory</code> will start the deployment, and return a <code>Promise</code> that resolves to a <code>Contract</code>. This is the object that has a method for each of your smart contract functions. Modify the text to send your own message.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// scripts/deploy.js</span>
<span class="kd">const</span> <span class="nx">hre</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">hardhat</span><span class="dl">"</span><span class="p">)</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">Greeter</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">hre</span><span class="p">.</span><span class="nx">ethers</span><span class="p">.</span><span class="nf">getContractFactory</span><span class="p">(</span><span class="dl">"</span><span class="s2">Greeter</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">greeter</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Greeter</span><span class="p">.</span><span class="nf">deploy</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello from ajcwebdev!</span><span class="dl">"</span><span class="p">)</span>
<span class="k">await</span> <span class="nx">greeter</span><span class="p">.</span><span class="nf">deployed</span><span class="p">()</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Greeter deployed to:</span><span class="dl">"</span><span class="p">,</span> <span class="nx">greeter</span><span class="p">.</span><span class="nx">address</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">main</span><span class="p">()</span>
<span class="p">.</span><span class="nf">then</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">process</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span>
<span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span>
<span class="nx">process</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="p">})</span>
</code></pre>
</div>
<p>I'll also add a few more console logs to the <code>main</code> function to log additional pieces of information including the <code>signer</code>, <code>hash</code>, and <code>blockHash</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// scripts/deploy.js</span>
<span class="kd">const</span> <span class="nx">hre</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">hardhat</span><span class="dl">"</span><span class="p">)</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">Greeter</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">hre</span><span class="p">.</span><span class="nx">ethers</span><span class="p">.</span><span class="nf">getContractFactory</span><span class="p">(</span><span class="dl">"</span><span class="s2">Greeter</span><span class="dl">"</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">greeter</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">Greeter</span><span class="p">.</span><span class="nf">deploy</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello from ajcwebdev!</span><span class="dl">"</span><span class="p">)</span>
<span class="k">await</span> <span class="nx">greeter</span><span class="p">.</span><span class="nf">deployed</span><span class="p">()</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Greeter deployed to </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">greeter</span><span class="p">.</span><span class="nx">address</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> address</span><span class="dl">"</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Greeter deployed by </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">greeter</span><span class="p">.</span><span class="nx">signer</span><span class="p">)</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"> signer</span><span class="dl">"</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Deploy transaction hash: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">greeter</span><span class="p">.</span><span class="nx">deployTransaction</span><span class="p">.</span><span class="nx">hash</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Deploy transaction block hash: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">greeter</span><span class="p">.</span><span class="nx">deployTransaction</span><span class="p">.</span><span class="nx">blockHash</span><span class="p">)</span>
<span class="p">}</span>
<span class="nf">main</span><span class="p">()</span>
<span class="p">.</span><span class="nf">then</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">process</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">0</span><span class="p">))</span>
<span class="p">.</span><span class="k">catch</span><span class="p">((</span><span class="nx">error</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span>
<span class="nx">process</span><span class="p">.</span><span class="nf">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="p">})</span>
</code></pre>
</div>
<h3>
Hardhat Configuration
</h3>
<p>When Hardhat is run, it searches for the closest <code>hardhat.config.js</code> file starting from the current working directory. This file normally lives in the root of your project. The entirety of your Hardhat setup, including your config, plugins and custom tasks, is contained in this file.</p>
<p>We need to make a couple changes to our <code>hardhat.config.js</code> file. Update the chain ID to <a href="proxy.php?url=https://hardhat.org/metamask-issue.html">1337</a> and change the location for the <a href="proxy.php?url=https://hardhat.org/guides/compile-contracts.html#artifacts">artifacts</a> of the compiled contracts to the <code>src</code> directory of the React app.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// hardhat.config.js</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">@nomiclabs/hardhat-waffle</span><span class="dl">"</span><span class="p">)</span>
<span class="nf">task</span><span class="p">(</span><span class="dl">"</span><span class="s2">accounts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Prints the list of accounts</span><span class="dl">"</span><span class="p">,</span> <span class="k">async </span><span class="p">(</span><span class="nx">taskArgs</span><span class="p">,</span> <span class="nx">hre</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">accounts</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">hre</span><span class="p">.</span><span class="nx">ethers</span><span class="p">.</span><span class="nf">getSigners</span><span class="p">()</span>
<span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">account</span> <span class="k">of</span> <span class="nx">accounts</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">account</span><span class="p">.</span><span class="nx">address</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="cm">/**
* @type import('hardhat/config').HardhatUserConfig
*/</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">solidity</span><span class="p">:</span> <span class="dl">"</span><span class="s2">0.8.7</span><span class="dl">"</span><span class="p">,</span>
<span class="na">paths</span><span class="p">:</span> <span class="p">{</span>
<span class="na">artifacts</span><span class="p">:</span> <span class="dl">'</span><span class="s1">./src/artifacts</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="na">networks</span><span class="p">:</span> <span class="p">{</span>
<span class="na">hardhat</span><span class="p">:</span> <span class="p">{</span>
<span class="na">chainId</span><span class="p">:</span> <span class="mi">1337</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<h3>
Greeter Solidity Contract
</h3>
<p>Solidity files start with a pragma that is used by the Solidity compiler to validate its version. <code>contract</code> specifies the main building block for the smart contract, which is named <code>Greeter</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// contracts/Greeter.sol</span>
<span class="c1">//SPDX-License-Identifier: MIT</span>
<span class="nx">pragma</span> <span class="nx">solidity</span> <span class="o">^</span><span class="mf">0.8</span><span class="p">.</span><span class="mi">7</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">hardhat/console.sol</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">contract</span> <span class="nx">Greeter</span> <span class="p">{</span> <span class="p">}</span>
</code></pre>
</div>
<p>The <code>constructor</code> is executed only once when the contract is created. The <code>Greeter</code> constructor will set a <code>greeting</code> variable and expose a function (<code>greet</code>) that can be called to return the <code>greeting</code>. It also exposes a function called <code>setGreeting</code> that allows a user to update the greeting.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// contracts/Greeter.sol</span>
<span class="c1">//SPDX-License-Identifier: MIT</span>
<span class="nx">pragma</span> <span class="nx">solidity</span> <span class="o">^</span><span class="mf">0.8</span><span class="p">.</span><span class="mi">7</span><span class="p">;</span>
<span class="k">import</span> <span class="dl">"</span><span class="s2">hardhat/console.sol</span><span class="dl">"</span><span class="p">;</span>
<span class="nx">contract</span> <span class="nx">Greeter</span> <span class="p">{</span>
<span class="nx">string</span> <span class="kr">private</span> <span class="nx">greeting</span><span class="p">;</span>
<span class="nf">constructor</span><span class="p">(</span><span class="nx">string</span> <span class="nx">memory</span> <span class="nx">_greeting</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Deploying a Greeter with greeting: </span><span class="dl">"</span><span class="p">,</span> <span class="nx">_greeting</span><span class="p">);</span>
<span class="nx">greeting</span> <span class="o">=</span> <span class="nx">_greeting</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">greet</span><span class="p">()</span> <span class="kr">public</span> <span class="nx">view</span> <span class="nf">returns </span><span class="p">(</span><span class="nx">string</span> <span class="nx">memory</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">greeting</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">setGreeting</span><span class="p">(</span><span class="nx">string</span> <span class="nx">memory</span> <span class="nx">_greeting</span><span class="p">)</span> <span class="kr">public</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Changing greeting from '%s' to '%s'</span><span class="dl">"</span><span class="p">,</span> <span class="nx">greeting</span><span class="p">,</span> <span class="nx">_greeting</span><span class="p">);</span>
<span class="nx">greeting</span> <span class="o">=</span> <span class="nx">_greeting</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<p>These methods will be available for a user to interact with when deployed to the Ethereum blockchain. These methods represent the two ways of interacting with a smart contract:</p>
<ul>
<li>
<code>greet</code> reads from the blockchain and does not require money for <a href="proxy.php?url=https://www.investopedia.com/terms/g/gas-ethereum.asp">gas</a>.</li>
<li>
<code>setGreeting</code> writes to the blockchain and requires payment for the transaction if you want to deploy to <a href="proxy.php?url=https://ethereum.org/en/glossary/#mainnet">mainnet</a> and not a <a href="proxy.php?url=https://ethereum.org/en/glossary/#testnet">testnet</a>.</li>
</ul>
<h2>
Deploy to Testnet
</h2>
<p>To deploy to the local network, you first need to compile your contract and then start a local test node. Our React app will interact with the smart contract using a combination of the Ethers library, the contract address, and the ABI that will be created from the contract by <code>hardhat</code>.</p>
<h3>
Compile Application Binary Interface
</h3>
<p>ABI stands for application binary interface. It is the interface between your client-side application and the Ethereum blockchain where the smart contract you are going to be interacting with is deployed.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm hardhat compile
</code></pre>
</div>
<p>ABIs are typically compiled from Solidity smart contracts by a development framework like Hardhat. You can also often find the ABIs for a smart contract on <a href="proxy.php?url=https://etherscan.io/">Etherscan</a>.</p>
<h3>
Start Test Node
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm hardhat node
</code></pre>
</div>
<p>This will return a list of addresses and private keys.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>Started HTTP and WebSocket JSON-RPC server at http://127.0.0.1:8545/
Accounts
========
Account #0: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 (10000 ETH)
Private Key: 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
Account #1: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8 (10000 ETH)
Private Key: 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
...
</code></pre>
</div>
<p>We will need to pick an account and corresponding private key. I will use Account #11:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>Account #11: 0x71be63f3384f5fb98995898a86b02fb2426c5788 (10000 ETH)
Private Key: 0x701b615bbdfb9de65240bc28bd21bbc0d996645a3dd57e7b12bc2bdf6f192c82
</code></pre>
</div>
<p>Leave this terminal running and open another for the next command.</p>
<h3>
Run Deployment Script
</h3>
<p>Run the deploy script and give a flag to the CLI to let it know that we would like to deploy to our local network:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm hardhat run scripts/deploy.js <span class="se">\</span>
<span class="nt">--network</span> localhost
</code></pre>
</div>
<p>This will result in the following output in your terminal:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>Greeter deployed to 0x5FbDB2315678afecb367f032d93F642f64180aa3 address
Greeter deployed by "<SignerWithAddress 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266>" signer
Deploy transaction hash: 0x74b10cb0666183a9f2d5d20dff6cbf12f2d026d39ce7c48db0f9bb02f52f2ee1
Deploy transaction block hash: 0xfb0865cdff882c11ee07521ed2732b4c961c915d88d407953336484e6ffd2395
</code></pre>
</div>
<p>The address returned on the first line (<code>0x5FbDB2315678afecb367f032d93F642f64180aa3</code>) is what we will use in our client application to talk to the smart contract. Save this address somewhere that you can access later as we will need to use it when connecting to the contract from the client application.</p>
<h2>
Connect to MetaMask
</h2>
<p>To send transactions to the smart contract, we will need to connect our MetaMask wallet using one of the accounts created when we ran <code>pnpm hardhat node</code>. In the terminal running our local node, you will see the following output verifying that the <code>Greeter</code> contract was successfully deployed.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>web3_clientVersion
eth_chainId
eth_accounts
eth_blockNumber
eth_chainId (2)
eth_estimateGas
eth_getBlockByNumber
eth_feeHistory
eth_sendTransaction
Contract deployment: Greeter
Contract address: 0x5fbdb2315678afecb367f032d93f642f64180aa3
Transaction: 0x74b10cb0666183a9f2d5d20dff6cbf12f2d026d39ce7c48db0f9bb02f52f2ee1
From: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
Value: 0 ETH
Gas used: 505488 of 505488
Block #1: 0xfb0865cdff882c11ee07521ed2732b4c961c915d88d407953336484e6ffd2395
console.log:
Deploying a Greeter with greeting: Hello from ajcwebdev!
eth_chainId
eth_getTransactionByHash
eth_chainId
eth_getTransactionReceipt
</code></pre>
</div>
<p>In the list of contracts that the CLI logs out, you should see both an Account number as well as a Private Key for the account you originally chose at the beginning of the tutorial. We can import this account into MetaMask in order to start using some of its fake Eth. To do so, first open MetaMask and check the available networks.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsuehp9d7vb1mfylez2q.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsuehp9d7vb1mfylez2q.png" alt="01 - check metamask networks" width="800" height="769"></a></p>
<p>If you only see the Ethereum Mainnet, go to Settings > Advanced and make sure "Show test networks" is set to ON.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bxwvd2v3uz093u2hoe3.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8bxwvd2v3uz093u2hoe3.png" alt="02 - set metamask to localhost 8545" width="800" height="778"></a></p>
<p>Update the network to Localhost 8545.</p>
<h3>
Import Account into MetaMask
</h3>
<p>Once you are connected to Localhost 8545, click the icon at the top right to see your accounts. Select "Import Account."</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcskqw7cw2h5g7532zc92.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcskqw7cw2h5g7532zc92.png" alt="03 - import account into metamask" width="800" height="578"></a></p>
<p>Go back to the list of accounts printed by <code>pnpm hardhat node</code> and copy the account's private keys. I'll be using Account #11.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxzhie84g1jqarghftuv.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsxzhie84g1jqarghftuv.png" alt="04 - paste private key to import account" width="800" height="584"></a></p>
<p>After importing the account you will see ether in the account.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvx212ng4nyagzbt7rge.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbvx212ng4nyagzbt7rge.png" alt="05 - ether account balance in metamask" width="800" height="769"></a></p>
<p>If you are using MetaMask for other purposes, it will be important to distinguish this account from an account that is actually holding funds. Go to "Account details."</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbowt63izdzaa5anjaccu.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbowt63izdzaa5anjaccu.png" alt="06 - get account details in metamask" width="800" height="364"></a></p>
<p>Click the pencil icon next to the account name to give it a more descriptive name.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hst60nomgcxutmt5c0y.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5hst60nomgcxutmt5c0y.png" alt="07 - rename account to hardhat account 11" width="800" height="591"></a></p>
<h2>
Create React App
</h2>
<p>Set <code>greeterAddress</code> to the contract address logged out to the CLI when it was deployed. I have created an environment variable named <code>VITE_GREETER_ADDRESS</code> in a <code>.env</code> file. Any environment variables that need to be exposed on the client must be prefixed with <code>VITE_</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/App.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ethers</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ethers</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">Greeter</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./artifacts/contracts/Greeter.sol/Greeter.json</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">greeterAddress</span> <span class="o">=</span> <span class="k">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">VITE_GREETER_ADDRESS</span>
<span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span> <span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">App</span>
</code></pre>
</div>
<p>We'll import the <code>useState</code> hook and set the state to <code>greeting</code> with <code>setGreetingValue</code>. <code>fetchGreeting</code> will call the smart contract and read the current greeting value.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/App.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ethers</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ethers</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">Greeter</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./artifacts/contracts/Greeter.sol/Greeter.json</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">greeterAddress</span> <span class="o">=</span> <span class="k">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">VITE_GREETER_ADDRESS</span>
<span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">greeting</span><span class="p">,</span> <span class="nx">setGreetingValue</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">()</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">requestAccount</span><span class="p">()</span> <span class="p">{</span>
<span class="k">await</span> <span class="nb">window</span><span class="p">.</span><span class="nx">ethereum</span><span class="p">.</span><span class="nf">request</span><span class="p">({</span> <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">eth_requestAccounts</span><span class="dl">'</span> <span class="p">})</span>
<span class="p">}</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">fetchGreeting</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nb">window</span><span class="p">.</span><span class="nx">ethereum</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">undefined</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ethers</span><span class="p">.</span><span class="nx">providers</span><span class="p">.</span><span class="nc">Web3Provider</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">ethereum</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">contract</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ethers</span><span class="p">.</span><span class="nc">Contract</span><span class="p">(</span><span class="nx">greeterAddress</span><span class="p">,</span> <span class="nx">Greeter</span><span class="p">.</span><span class="nx">abi</span><span class="p">,</span> <span class="nx">provider</span><span class="p">)</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">contract</span><span class="p">.</span><span class="nf">greet</span><span class="p">()</span>
<span class="nf">setGreetingValue</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Greeting: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contract Address: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">contract</span><span class="p">.</span><span class="nx">address</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contract Network: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">contract</span><span class="p">.</span><span class="nx">provider</span><span class="p">.</span><span class="nx">_network</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error: </span><span class="dl">"</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Ethers.js, Hardhat, Solidity<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>and React, Alchemy, and MetaMask<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span>Greeting<span class="p"></</span><span class="nt">h3</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">fetchGreeting</span><span class="si">}</span><span class="p">></span>
Fetch Greeting
<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span><span class="si">{</span><span class="nx">greeting</span><span class="si">}</span><span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">App</span>
</code></pre>
</div>
<p>I'll include Water.css in <code>index.html</code> for some nice looking CSS defaults.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!-- index.html --></span>
<span class="cp"><!DOCTYPE html></span>
<span class="nt"><html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">></span>
<span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span> <span class="nt">/></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"icon"</span> <span class="na">type=</span><span class="s">"image/svg+xml"</span> <span class="na">href=</span><span class="s">"/vite.svg"</span> <span class="nt">/></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span> <span class="nt">/></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"</span><span class="nt">></span>
<span class="nt"><title></span>A First look at Ethers and Hardhat<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"root"</span><span class="nt">></div></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">src=</span><span class="s">"/src/main.jsx"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span>
</code></pre>
</div>
<h3>
Start Development Server
</h3>
<p>Start the development server with <code>pnpm dev</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm dev
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:3000">localhost:3000</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkoxpx53ht60b3o7ki34.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzkoxpx53ht60b3o7ki34.png" alt="08 - react app on localhost 3000 with fetch greeting button" width="800" height="286"></a></p>
<p>Click the "Fetch Greeting" button to display the greeting from the deployed contract.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a26a1vq1urbtvaewprp.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5a26a1vq1urbtvaewprp.png" alt="09 - greeting displayed after clicking fetch greeting button" width="800" height="263"></a></p>
<p><code>setGreeting</code> calls the smart contract and sends an update.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/App.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ethers</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ethers</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">Greeter</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./artifacts/contracts/Greeter.sol/Greeter.json</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">greeterAddress</span> <span class="o">=</span> <span class="k">import</span><span class="p">.</span><span class="nx">meta</span><span class="p">.</span><span class="nx">env</span><span class="p">.</span><span class="nx">VITE_GREETER_ADDRESS</span>
<span class="kd">function</span> <span class="nf">App</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">greeting</span><span class="p">,</span> <span class="nx">setGreetingValue</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">()</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">requestAccount</span><span class="p">()</span> <span class="p">{</span>
<span class="k">await</span> <span class="nb">window</span><span class="p">.</span><span class="nx">ethereum</span><span class="p">.</span><span class="nf">request</span><span class="p">({</span> <span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">eth_requestAccounts</span><span class="dl">'</span> <span class="p">})</span>
<span class="p">}</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">fetchGreeting</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nb">window</span><span class="p">.</span><span class="nx">ethereum</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">undefined</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ethers</span><span class="p">.</span><span class="nx">providers</span><span class="p">.</span><span class="nc">Web3Provider</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">ethereum</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">contract</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ethers</span><span class="p">.</span><span class="nc">Contract</span><span class="p">(</span><span class="nx">greeterAddress</span><span class="p">,</span> <span class="nx">Greeter</span><span class="p">.</span><span class="nx">abi</span><span class="p">,</span> <span class="nx">provider</span><span class="p">)</span>
<span class="k">try</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">data</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">contract</span><span class="p">.</span><span class="nf">greet</span><span class="p">()</span>
<span class="nf">setGreetingValue</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Greeting: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">data</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contract Address: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">contract</span><span class="p">.</span><span class="nx">address</span><span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Contract Network: </span><span class="dl">'</span><span class="p">,</span> <span class="nx">contract</span><span class="p">.</span><span class="nx">provider</span><span class="p">.</span><span class="nx">_network</span><span class="p">.</span><span class="nx">name</span><span class="p">)</span>
<span class="p">}</span> <span class="k">catch </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Error: </span><span class="dl">"</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">setGreeting</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">greeting</span><span class="p">)</span> <span class="k">return</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nb">window</span><span class="p">.</span><span class="nx">ethereum</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">undefined</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">await</span> <span class="nf">requestAccount</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">provider</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ethers</span><span class="p">.</span><span class="nx">providers</span><span class="p">.</span><span class="nc">Web3Provider</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">ethereum</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">signer</span> <span class="o">=</span> <span class="nx">provider</span><span class="p">.</span><span class="nf">getSigner</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">contract</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">ethers</span><span class="p">.</span><span class="nc">Contract</span><span class="p">(</span><span class="nx">greeterAddress</span><span class="p">,</span> <span class="nx">Greeter</span><span class="p">.</span><span class="nx">abi</span><span class="p">,</span> <span class="nx">signer</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">transaction</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">contract</span><span class="p">.</span><span class="nf">setGreeting</span><span class="p">(</span><span class="nx">greeting</span><span class="p">)</span>
<span class="k">await</span> <span class="nx">transaction</span><span class="p">.</span><span class="nf">wait</span><span class="p">()</span>
<span class="nf">fetchGreeting</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>Ethers.js, Hardhat, Solidity<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>and React, Alchemy, and MetaMask<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span>Greeting<span class="p"></</span><span class="nt">h3</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">fetchGreeting</span><span class="si">}</span><span class="p">></span>
Fetch Greeting
<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span><span class="si">{</span><span class="nx">greeting</span><span class="si">}</span><span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">input</span>
<span class="na">onChange</span><span class="p">=</span><span class="si">{</span><span class="nx">e</span> <span class="o">=></span> <span class="nf">setGreetingValue</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span><span class="si">}</span>
<span class="na">placeholder</span><span class="p">=</span><span class="s">"Set greeting"</span>
<span class="p">/></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">setGreeting</span><span class="si">}</span><span class="p">></span>
Set Greeting
<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p">)</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">App</span>
</code></pre>
</div>
<h3>
Connect React App to MetaMask
</h3>
<p>Select account to connect with MetaMask.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frytdc1cj071kk4xcm27v.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frytdc1cj071kk4xcm27v.png" alt="10 - select account to connect with metamask" width="800" height="553"></a></p>
<p>Connect to the account.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqft6ta0lj5xhk4i0knu8.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqft6ta0lj5xhk4i0knu8.png" alt="11 - connect to account with metamask" width="800" height="554"></a></p>
<p>Enter input and click Set Greeting. You will be asked to pay a gas fee.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxppbyh6zwtqgacjojjax.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxppbyh6zwtqgacjojjax.png" alt="12 - pay gas fee" width="800" height="625"></a></p>
<p>The greeting value will be set to the new inputted greeting.</p>
<h2>
Deploy to Ropsten Testnet
</h2>
<p>Ethereum provides test networks like Ropsten, Rinkeby, or Kovan that we can use to deploy a publicly accessible version of our contract without having to deploy it to the mainnet.</p>
<h3>
Connect MetaMask to Ropsten
</h3>
<p>Update your MetaMask wallet to connect to the <a href="proxy.php?url=https://ropsten.etherscan.io/">Ropsten</a> network.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeefv78bpy2vj1i1xdi8.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxeefv78bpy2vj1i1xdi8.png" alt="13 - change to ropsten testnet" width="710" height="1194"></a></p>
<p>We can get access to Ropsten and other test networks by using a service like <a href="proxy.php?url=https://infura.io/">Infura</a>, <a href="proxy.php?url=https://www.alchemy.com/">Alchemy</a>, or <a href="proxy.php?url=https://www.quicknode.com/">QuickNode</a>.</p>
<h3>
Create an Alchemy Account
</h3>
<p>Alchemy Supernode is a blockchain API that can connect to various tools in the blockchain ecosystem such as Ethereum, Polygon, Arbitrum, Optimism, and Flow. It provides common node functionality including JSON-RPC support with built in reliability, data correctness and scalability. After creating an account, you will see your dashboard.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1w8axkijbifzcfa9lqu7.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1w8axkijbifzcfa9lqu7.png" alt="14 - alchemy dashboard" width="800" height="321"></a></p>
<h3>
Create an Alchemy Application
</h3>
<p>Click <strong>Create App</strong> and give your app a name and description. Select development for the environment, Ethereum for the chain, and Ropsten for the network.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrxvimw53jqlu4nrmcii.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdrxvimw53jqlu4nrmcii.png" alt="15 - create app on alchemy" width="800" height="405"></a></p>
<p>After creating the app you will see it appear in your dashboard.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fpw8akulm90jgp6vup0.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0fpw8akulm90jgp6vup0.png" alt="16 - alchemy dashboard with ajcwebdev-ethers app" width="800" height="166"></a></p>
<h3>
Get Alchemy Key
</h3>
<p>Click the name of the app to see more details.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjzpqe9zgmo3nzcstkmy.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjzpqe9zgmo3nzcstkmy.png" alt="17 - alchemy app view details" width="800" height="311"></a></p>
<p>Click <strong>View Key</strong> to see your endpoints.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvxdpozaswc9u4fy4ccc.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnvxdpozaswc9u4fy4ccc.png" alt="18 - alchemy endpoints" width="800" height="284"></a></p>
<p>The https endpoint will look something like this:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>https://eth-ropsten.alchemyapi.io/v2/PROJECT_ID
</code></pre>
</div>
<p>Click <strong>Edit App</strong> and add the account address from <code>App.jsx</code> to the allowed accounts.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ved13q0s5d1lrgnmofu.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ved13q0s5d1lrgnmofu.png" alt="19 - allowed addresses" width="800" height="568"></a></p>
<h3>
Add Alchemy Endpoint to Hardhat Configuration
</h3>
<p>Open <code>hardhat.config.js</code> and add <code>networks</code> property.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// hardhat.config.js</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">@nomiclabs/hardhat-waffle</span><span class="dl">"</span><span class="p">)</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">dotenv</span><span class="dl">'</span><span class="p">).</span><span class="nf">config</span><span class="p">()</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">ALCHEMY_URL</span><span class="p">,</span> <span class="nx">ALCHEMY_KEY</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">process</span><span class="p">.</span><span class="nx">env</span>
<span class="nf">task</span><span class="p">(</span><span class="dl">"</span><span class="s2">accounts</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">Prints the list of accounts</span><span class="dl">"</span><span class="p">,</span> <span class="k">async </span><span class="p">(</span><span class="nx">taskArgs</span><span class="p">,</span> <span class="nx">hre</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">accounts</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">hre</span><span class="p">.</span><span class="nx">ethers</span><span class="p">.</span><span class="nf">getSigners</span><span class="p">()</span>
<span class="k">for </span><span class="p">(</span><span class="kd">const</span> <span class="nx">account</span> <span class="k">of</span> <span class="nx">accounts</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">account</span><span class="p">.</span><span class="nx">address</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="cm">/**
* @type import('hardhat/config').HardhatUserConfig
*/</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">solidity</span><span class="p">:</span> <span class="dl">"</span><span class="s2">0.8.7</span><span class="dl">"</span><span class="p">,</span>
<span class="na">defaultNetwork</span><span class="p">:</span> <span class="dl">"</span><span class="s2">hardhat</span><span class="dl">"</span><span class="p">,</span>
<span class="na">paths</span><span class="p">:</span> <span class="p">{</span>
<span class="na">artifacts</span><span class="p">:</span> <span class="dl">'</span><span class="s1">./src/artifacts</span><span class="dl">'</span><span class="p">,</span>
<span class="p">},</span>
<span class="na">networks</span><span class="p">:</span> <span class="p">{</span>
<span class="na">hardhat</span><span class="p">:</span> <span class="p">{},</span>
<span class="na">ropsten</span><span class="p">:</span> <span class="p">{</span>
<span class="na">url</span><span class="p">:</span> <span class="s2">`</span><span class="p">${</span><span class="nx">ALCHEMY_URL</span><span class="p">}</span><span class="s2">`</span><span class="p">,</span>
<span class="na">accounts</span><span class="p">:</span> <span class="p">[</span><span class="s2">`0x`</span> <span class="o">+</span> <span class="s2">`</span><span class="p">${</span><span class="nx">ALCHEMY_KEY</span><span class="p">}</span><span class="s2">`</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
</div>
<h3>
Deploy Contract to Ropsten
</h3>
<p>Send yourself test Ether by visiting a test faucet like <a href="proxy.php?url=https://faucet.ropsten.be/">faucet.ropsten.be</a> or <a href="proxy.php?url=https://faucet.dimensions.network/">faucet.dimensions.network</a>. Run the following script to deploy.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>pnpm hardhat run scripts/deploy.js <span class="se">\</span>
<span class="nt">--network</span> ropsten
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>Greeter deployed to 0x42839A6cD421B0C29AcD3FB900feAa6a76f34684 address
Greeter deployed by "<SignerWithAddress 0x71bE63f3384f5fb98995898A86B02Fb2426c5788>" signer
Deploy transaction hash: 0x9f5875c939d804501767690d843aefb05d91f4b1f5d34d477dbe6d7327c83ca4
Deploy transaction block hash: null
</code></pre>
</div>
<p>Once your contract is deployed you can view the live contract on <a href="proxy.php?url=https://ropsten.etherscan.io/">Etherscan Ropsten Testnet Explorer</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcbigmb5felstbcdeiio.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcbigmb5felstbcdeiio.png" alt="20 - contract on etherscan" width="800" height="372"></a></p>
ethereumsolidityhardhatethersQuerying MongoDB with Prisma and RailwayajcwebdevTue, 01 Feb 2022 07:44:36 +0000
https://dev.to/ajcwebdev/query-a-mongodb-database-with-prisma-and-railway-ig8
https://dev.to/ajcwebdev/query-a-mongodb-database-with-prisma-and-railway-ig8<h2>
Outline
</h2>
<ul>
<li>Introduction</li>
<li>
Create Prisma Project
<ul>
<li>Initialize Prisma Schema</li>
<li>Prisma Schema</li>
</ul>
</li>
<li>
Provision a MongoDB Database with Railway
<ul>
<li>Railway Dashboard</li>
<li>Railway CLI</li>
</ul>
</li>
<li>
Connect Railway Database to Prisma Project
<ul>
<li>Set Environment Variable</li>
<li>Seed Database</li>
<li>Generate Prisma Client</li>
</ul>
</li>
<li>
Create a Script to Query the Database
<ul>
<li>Run the Script</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong><em>All of this project's code can be found in the <a href="proxy.php?url=https://github.com/ajcwebdev/a-first-look/tree/main/backend/prisma-mongo-railway/" rel="noopener noreferrer">First Look monorepo</a> on my GitHub.</em></strong></p>
</blockquote>
<h2>
Introduction
</h2>
<p>In this guide we will deploy a MongoDB database with Railway, add seed data to the database, connect to the database through a connection string, and create a Node script to query that seed data with Prisma Client.</p>
<h2>
Create Prisma Project
</h2>
<p>Create a blank new project and initialize a <code>package.json</code>.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
<span class="nb">mkdir </span>railway-prisma-mongodb
<span class="nb">cd </span>railway-prisma-mongodb
yarn init <span class="nt">-y</span>
</code></pre>
</div>
<p>Install the <code>prisma</code> dependencies.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
yarn add @prisma/client
yarn add <span class="nt">-D</span> prisma
</code></pre>
</div>
<p>Set <code>type</code> to <code>module</code> in <code>package.json</code>.</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"railway-prisma-mongodb"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MIT"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"@prisma/client"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^3.10.0"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"prisma"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^3.10.0"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"type"</span><span class="p">:</span><span class="w"> </span><span class="s2">"module"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<h3>
Initialize Prisma Schema
</h3>
<p><code>prisma init</code> scaffolds a basic Prisma project.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
yarn prisma init
</code></pre>
</div>
<p>The only other necessary file is <code>index.js</code>. This is used for running test commands against our database with the Prisma Client.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
<span class="nb">touch </span>index.js
</code></pre>
</div>
<p>Our project now has the following structure:</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>
/
├── prisma
│ └── schema.prisma
├── .env
├── .gitignore
├── index.js
└── package.json
</code></pre>
</div>
<h3>
Prisma Schema
</h3>
<p>Define a <code>Post</code> model by adding the following to the <code>schema.prisma</code> file.</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["mongoDb"]
}
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
slug String @unique
title String
body String
}
</code></pre>
</div>
<p><code>previewFeatures</code> must be set to <code>mongoDb</code> in the <code>client</code> generator to enable MongoDB support.</p>
<h2>
Provision a MongoDB Database with Railway
</h2>
<p>There are two ways to setup a MongoDB database with Railway, through the dashboard or through the CLI.</p>
<h3>
Railway Dashboard
</h3>
<p>To use the dashboard, click <a href="proxy.php?url=https://dev.new" rel="noopener noreferrer">dev.new</a> and choose "Provision MongoDB."</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qkn18aj97rj0zj1xcp6.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qkn18aj97rj0zj1xcp6.png" alt="01-railway-project-dashboard"></a></p>
<p>After the database is setup click "MongoDB" on the left to see the default test database that is autogenerated.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjalfi6g2nxutb6qqy83.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgjalfi6g2nxutb6qqy83.png" alt="02-mongodb-data-dashboard"></a></p>
<p>Choose "Connect" to find your connection string.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6l6p48xu2it30oqu1gtd.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6l6p48xu2it30oqu1gtd.png" alt="03-mongodb-connection-string"></a></p>
<h3>
Railway CLI
</h3>
<p>If you want to use the Railway CLI instead of the dashboard, first you need to <a href="proxy.php?url=https://docs.railway.app/cli/installation" rel="noopener noreferrer">install it</a>. To verify that you successfully installed the CLI, run the following command to check the CLI version.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
railway version
</code></pre>
</div>
<p>Run <code>railway login</code> to authenticate your Railway account. If you do not have a Railway account you will be prompted to create one.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
railway login
</code></pre>
</div>
<p><code>railway init</code> initializes a project and asks if you want to start with an empty project or a starter template. Select “Empty Project” and give your project a name.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
railway init
</code></pre>
</div>
<p>Run <code>railway add</code> and select MongoDB to add the MongoDB plugin to your Railway project.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
railway add
</code></pre>
</div>
<h2>
Connect Railway Database to Prisma Project
</h2>
<p>Return to the Railway dashboard to find your connection string.</p>
<h3>
Set Environment Variable
</h3>
<p>Inside your <code>.env</code> file include <code>DATABASE_URL</code> and set the variable to the connection string provided by Railway. Specify the database name and authentication source at the end of the connection string by adding <code>/test?authSource=admin</code> after the port number.</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>
DATABASE_URL="mongodb://mongo:<PASSWORD>@containers-us-west-1.railway.app:6852/test?authSource=admin"
</code></pre>
</div>
<p>Sync the schema with the database by using <code>prisma db push</code>.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
yarn prisma db push
</code></pre>
</div>
<h3>
Seed Database
</h3>
<p>You can also connect to the database directly with the <code>mongosh</code> command.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
mongosh <span class="s2">"mongodb://mongo:<PASSWORD>@containers-us-west-1.railway.app:6852"</span>
</code></pre>
</div>
<p>The database can be seeded from the Railway dashboard or through <code>mongosh</code> with the following seed command.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
db.Post.insertOne<span class="o">(</span>
<span class="o">{</span>
slug: <span class="s2">"first-post-slug"</span>,
title: <span class="s2">"First Post Title"</span>,
body: <span class="s2">"First post body."</span>
<span class="o">}</span>
<span class="o">)</span>
</code></pre>
</div>
<p>Check the data tab in the Railway dashboard to see the data.</p>
<h3>
Generate Prisma Client
</h3>
<p>Generate the Prisma Client with the <code>prisma generate</code> command.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
yarn prisma generate
</code></pre>
</div>
<h2>
Create a Script to Query the Database
</h2>
<p>Add the following script to <code>index.js</code> to test that we can read data from our database. This function runs the <code>findMany</code> query on our <code>post</code> collection and returns all posts in the collection.</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code>
<span class="c1">// index.js</span>
<span class="k">import</span> <span class="nx">pkg</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@prisma/client</span><span class="dl">'</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">PrismaClient</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">pkg</span>
<span class="kd">const</span> <span class="nx">prisma</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">PrismaClient</span><span class="p">()</span>
<span class="k">async</span> <span class="kd">function</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">await</span> <span class="nx">prisma</span><span class="p">.</span><span class="nf">$connect</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">posts</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">prisma</span><span class="p">.</span><span class="nx">post</span><span class="p">.</span><span class="nf">findMany</span><span class="p">()</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">dir</span><span class="p">(</span><span class="nx">posts</span><span class="p">,</span> <span class="p">{</span> <span class="na">depth</span><span class="p">:</span> <span class="kc">Infinity</span> <span class="p">})</span>
<span class="p">}</span>
<span class="nf">main</span><span class="p">()</span>
<span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="nx">console</span><span class="p">.</span><span class="nx">error</span><span class="p">)</span>
<span class="p">.</span><span class="k">finally</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">prisma</span><span class="p">.</span><span class="nf">$disconnect</span><span class="p">())</span>
</code></pre>
</div>
<h3>
Run the Script
</h3>
<p>Run <code>node index.js</code> to execute the <code>main</code> function.</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>
node index.js
</code></pre>
</div>
<p>If you followed along correctly you should get the following output:</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code>
<span class="p">[</span>
<span class="p">{</span>
<span class="na">id</span><span class="p">:</span> <span class="dl">'</span><span class="s1">61f369df79160504b0ee41d9</span><span class="dl">'</span><span class="p">,</span>
<span class="na">slug</span><span class="p">:</span> <span class="dl">'</span><span class="s1">first-post-slug</span><span class="dl">'</span><span class="p">,</span>
<span class="na">title</span><span class="p">:</span> <span class="dl">'</span><span class="s1">First Post Title</span><span class="dl">'</span><span class="p">,</span>
<span class="na">body</span><span class="p">:</span> <span class="dl">'</span><span class="s1">First post body.</span><span class="dl">'</span>
<span class="p">}</span>
<span class="p">]</span>
</code></pre>
</div>
mongodbprismarailwaydatabaseA First Look at AstroajcwebdevSun, 28 Nov 2021 01:14:57 +0000
https://dev.to/ajcwebdev/a-first-look-at-astro-2937
https://dev.to/ajcwebdev/a-first-look-at-astro-2937<h2>
Outline
</h2>
<ul>
<li>
Introduction
<ul>
<li>Partial Hydration</li>
<li>Client Directives</li>
</ul>
</li>
<li>
Create Project
<ul>
<li>Install Astro Dependency</li>
<li>Add CLI Commands</li>
<li>Create a Page</li>
<li>Start Development Server</li>
<li>Add Styling</li>
</ul>
</li>
<li>
Add Components
<ul>
<li>Create a Markdown Component</li>
<li>Create a React Component</li>
<li>Create a Svelte Component</li>
<li>Create a Vue Component</li>
<li>Create a GraphQL Data Fetching Component</li>
</ul>
</li>
<li>
Deploy to Netlify
<ul>
<li>Create a GitHub Repository</li>
<li>Connect GitHub Repository to Netlify</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong><em>All of this project's code can be found in the <a href="proxy.php?url=https://github.com/ajcwebdev/a-first-look/tree/main/frontend/astro/">First Look monorepo</a> on my GitHub.</em></strong></p>
</blockquote>
<h2>
Introduction
</h2>
<p><a href="proxy.php?url=https://astro.build/">Astro</a> is an open source web framework influenced by the <a href="proxy.php?url=https://jasonformat.com/islands-architecture/">Islands Architecture</a>. It was created by Fred K. Schott, Matthew Phillips, Nate Moore, and Drew Powers as an outgrowth of the work being done simultaneously on Snowpack and Skypack.</p>
<p>It supports a variety of UI integrations including React, Svelte, Vue, Solid, and many more. The framework deserves a fair amount of credit for bringing partial hydration and the concept of "Islands of Interactivity" to the mainstream web development conversation.</p>
<h3>
Partial Hydration
</h3>
<p>I have an <a href="proxy.php?url=https://ajcwebdev.com/2021/11/22/what-is-partial-hydration-and-why-is-everyone-talking-about-it/">entirely separate, lengthy article</a> about this, but here's the summary. The conversation had been present but on the fringes for well over a decade. The first framework that fully supported these techniques, Marko, was created in 2014 but remained the odd duck out until around 2019.</p>
<p>However, in the last 2 years there has been an influx of frameworks drawing on similar motivations and prior art including Slinkity, Elder.js, îles, and Qwik. Fred K. Schott describes the architecture and goals of Astro in <a href="proxy.php?url=https://astro.build/blog/introducing-astro/">Introducing Astro: Ship Less JavaScript (June 8, 2021)</a>:</p>
<blockquote>
<p><em>Astro works a lot like a static site generator. If you have ever used Eleventy, Hugo, or Jekyll (or even a server-side web framework like Rails, Laravel, or Django) then you should feel right at home with Astro.</em></p>
<p><em>In Astro, you compose your website using UI components from your favorite JavaScript web framework (React, Svelte, Vue, etc). Astro renders your entire site to static HTML during the build. The result is a fully static website with all JavaScript removed from the final page.</em></p>
</blockquote>
<p>While there are plenty of frameworks based on <a href="proxy.php?url=https://nextjs.org/docs/advanced-features/static-html-export">React</a>, <a href="proxy.php?url=https://nuxtjs.org/announcements/going-full-static/">Vue</a>, and <a href="proxy.php?url=https://sapper.svelte.dev/docs#sapper_export">Svelte</a> that let you render components to static HTML during build time, if you want to hydrate these projects on the client then you have to ship an entire bundle of dependencies along with the static HTML. Astro, on the other hand, includes the ability to load just a single component and its dependencies where that component is needed.</p>
<h3>
Client Directives
</h3>
<p>Astro includes five <code>client:*</code> directives to hydrate components on the client at runtime. A directive is a component attribute that tells Astro how your component should be rendered.</p>
<div class="table-wrapper-paragraph"><table>
<thead>
<tr>
<th>Directive</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code><Component client:load /></code></td>
<td>Hydrates the component on page load.</td>
</tr>
<tr>
<td><code><Component client:idle /></code></td>
<td>Hydrates the component as soon as main thread is free.</td>
</tr>
<tr>
<td><code><Component client:visible /></code></td>
<td>Hydrates the component as soon as the element enters the viewport.</td>
</tr>
<tr>
<td><code><Component client:media={QUERY} /></code></td>
<td>Hydrates the component as soon as the browser matches the given media query.</td>
</tr>
<tr>
<td><code><Component client:only /></code></td>
<td>Hydrates the component at page load, similar to <code>client:load</code>. The component will be skipped at build time.</td>
</tr>
</tbody>
</table></div>
<h2>
Create Project
</h2>
<p>This tutorial will build up an Astro project from scratch instead of using any of the starter templates because I believe that is a better way to learn how a framework works, but the <a href="proxy.php?url=https://github.com/withastro/astro/tree/main/examples">templates</a> are really fantastic.</p>
<p>Start by creating a new directory for your project and initializing a <code>package.json</code> file.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>ajcwebdev-astro
<span class="nb">cd </span>ajcwebdev-astro
yarn init <span class="nt">-y</span>
</code></pre>
</div>
<h3>
Install Astro Dependency
</h3>
<p>Install the <code>astro</code> dependency and create <code>.gitignore</code> file.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn add <span class="nt">-D</span> astro
<span class="nb">echo</span> <span class="s1">'node_modules\ndist\n.DS_Store'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<h3>
Add CLI Commands
</h3>
<p>Add the following scripts to <code>package.json</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ajcwebdev-astro"</span><span class="p">,</span><span class="w">
</span><span class="nl">"version"</span><span class="p">:</span><span class="w"> </span><span class="s2">"1.0.0"</span><span class="p">,</span><span class="w">
</span><span class="nl">"main"</span><span class="p">:</span><span class="w"> </span><span class="s2">"index.js"</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MIT"</span><span class="p">,</span><span class="w">
</span><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"astro"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^1.0.0-beta.28"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"astro dev"</span><span class="p">,</span><span class="w">
</span><span class="nl">"start"</span><span class="p">:</span><span class="w"> </span><span class="s2">"astro dev"</span><span class="p">,</span><span class="w">
</span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"astro build"</span><span class="p">,</span><span class="w">
</span><span class="nl">"preview"</span><span class="p">:</span><span class="w"> </span><span class="s2">"astro preview"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p>All commands are run from the root of the project.</p>
<ul>
<li>
<code>yarn dev</code> and <code>yarn start</code> both start a local development server on <code>localhost:3000</code>.</li>
<li>
<code>yarn build</code> builds a production site to <code>./dist</code>.</li>
<li>
<code>yarn preview</code> previews the build locally before deploying.</li>
</ul>
<h3>
Create a Page
</h3>
<p>Astro looks for <code>.astro</code> or <code>.md</code> files in the <code>src/pages</code> directory. Each page is exposed as a route based on its file name. Static assets such as images can be placed in the <code>public</code> directory.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir</span> <span class="nt">-p</span> src/pages public
<span class="nb">touch </span>src/pages/index.astro
</code></pre>
</div>
<p>Inside the <code>src/pages</code> directory we created an <code>index.astro</code> file.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="c1">// src/pages/index.astro</span>
<span class="kd">let</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ajcwebdev-astro</span><span class="dl">'</span>
<span class="o">---</span>
<span class="p"><</span><span class="nt">html</span> <span class="na">lang</span><span class="p">=</span><span class="s">"en"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">charset</span><span class="p">=</span><span class="s">"UTF-8"</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"viewport"</span> <span class="na">content</span><span class="p">=</span><span class="s">"width=device-width"</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span><span class="si">{</span><span class="nx">title</span><span class="si">}</span><span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">h1</span><span class="p">></span>ajcwebdev-astro<span class="p"></</span><span class="nt">h1</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"><</span><span class="nt">p</span><span class="p">></span>Hello! This is an example Astro project by Anthony Campolo (ajcwebdev).<span class="p"></</span><span class="nt">p</span><span class="p">></span>
<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">footer</span><span class="p">></span>
<span class="p"><</span><span class="nt">h3</span><span class="p">></span>Find me on the internet:<span class="p"></</span><span class="nt">h3</span><span class="p">></span>
<span class="p"><</span><span class="nt">ul</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://ajcwebdev.com"</span><span class="p">></span>Blog<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://github.com/ajcwebdev"</span><span class="p">></span>GitHub<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
<span class="p"><</span><span class="nt">li</span><span class="p">><</span><span class="nt">a</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://twitter.com/ajcwebdev"</span><span class="p">></span>Twitter<span class="p"></</span><span class="nt">a</span><span class="p">></</span><span class="nt">li</span><span class="p">></span>
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
<span class="p"></</span><span class="nt">footer</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre>
</div>
<h3>
Start Development Server
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn dev
</code></pre>
</div>
<p>Open <a href="proxy.php?url=https://localhost:3000">localhost:3000</a> to see the home page.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79gx6ieqcw5cbu02dd2h.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F79gx6ieqcw5cbu02dd2h.png" alt="01 - ajcwebdev-astro-home-page" width="800" height="400"></a></p>
<h3>
Add Styling
</h3>
<p>I'll include <a href="proxy.php?url=https://watercss.kognise.dev/">Water.css</a> in the <code>head</code> of <code>index.html</code> for some nice looking CSS defaults.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><head></span>
<span class="nt"><meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">></span>
<span class="nt"><meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width"</span><span class="nt">></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"stylesheet"</span> <span class="na">href=</span><span class="s">"https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"</span><span class="nt">></span>
<span class="nt"><title></span>{title}<span class="nt"></title></span>
<span class="nt"></head></span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwtz1bqa5imqgued7o8c.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkwtz1bqa5imqgued7o8c.png" alt="02 - ajcwebdev-astro-home-page-with-styling" width="800" height="512"></a></p>
<h2>
Add Components
</h2>
<p>We'll create a directory called <code>components</code> inside <code>src</code> to hold any Astro/React/Vue/Svelte/Preact components. Then we will create three extra directories that will hold <code>.astro</code>, <code>.jsx</code>, and <code>.svelte</code> files for Markdown, React, and Svelte components respectively.</p>
<h3>
Create a Markdown Component
</h3>
<p>The first example will use Markdown and render completely statically. Astro includes a built in <a href="proxy.php?url=https://docs.astro.build/guides/markdown-content/#astros-markdown-component"><code>Markdown</code> component</a> that can be imported into any <code>.md</code> file.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir</span> <span class="nt">-p</span> src/components/markdown
<span class="nb">touch </span>src/components/markdown/HelloMarkdown.astro
</code></pre>
</div>
<p>Import the <code>Markdown</code> component from <code>astro/components</code> and write some Markdown.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="c1">// src/components/markdown/HelloMarkdown.astro</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Markdown</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">astro/components</span><span class="dl">'</span>
<span class="o">---</span>
<span class="p"><</span><span class="nt">article</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>Markdown<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nc">Markdown</span><span class="p">></span>
### This is an h3 with Markdown
*Pretty* **cool**, ***right***?
<span class="p"></</span><span class="nc">Markdown</span><span class="p">></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"></</span><span class="nt">article</span><span class="p">></span>
</code></pre>
</div>
<p>Return to <code>index.astro</code> and import <code>HelloMarkdown</code> from <code>'../components/markdown/HelloMarkdown.astro'</code>. Place <code><HelloMarkdown /></code> inside the <code>main</code> tags.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="c1">// src/pages/index.astro</span>
<span class="k">import</span> <span class="nx">HelloMarkdown</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/markdown/HelloMarkdown.astro</span><span class="dl">'</span>
<span class="kd">let</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ajcwebdev-astro</span><span class="dl">'</span>
<span class="o">---</span>
<span class="p"><</span><span class="nt">html</span> <span class="na">lang</span><span class="p">=</span><span class="s">"en"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>...<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>...<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloMarkdown</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">footer</span><span class="p">></span>...<span class="p"></</span><span class="nt">footer</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2h0ds2hvcev0wg7529u.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2h0ds2hvcev0wg7529u.png" alt="03 - home-page-with-markdown-component" width="800" height="433"></a></p>
<h3>
Create a React Component
</h3>
<p>To configure Astro, add an <code>astro.config.mjs</code> file and install the necessary React dependencies by running the following command:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn astro add react
</code></pre>
</div>
<p>This installs <code>@astrojs/react</code>, <code>react-dom@^18.0.0</code>, and <code>react@^18.0.0</code>. It also adds the following code to <code>astro.config.mjs</code> to enable the React renderer and provide support for React JSX components.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// astro.config.mjs</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">astro/config</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">react</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@astrojs/react</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
<span class="na">integrations</span><span class="p">:</span> <span class="p">[</span><span class="nf">react</span><span class="p">()]</span>
<span class="p">})</span>
</code></pre>
</div>
<p>We'll create a <code>react</code> directory with a <code>HelloReact.jsx</code> component inside.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src/components/react
<span class="nb">touch </span>src/components/react/HelloReact.jsx
</code></pre>
</div>
<p>It's a React component so you're contractually obligated to make it a counter with <code>useState</code> that is triggered with an <code>onClick</code> event handler on a <code><button></code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="c1">// src/components/react/HelloReact.jsx</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">useState</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">react</span><span class="dl">'</span>
<span class="k">export</span> <span class="k">default</span> <span class="kd">function</span> <span class="nf">HelloReact</span><span class="p">({</span> <span class="nx">children</span><span class="p">,</span> <span class="na">count</span><span class="p">:</span> <span class="nx">initialCount</span> <span class="p">})</span> <span class="p">{</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">count</span><span class="p">,</span> <span class="nx">setCount</span><span class="p">]</span> <span class="o">=</span> <span class="nf">useState</span><span class="p">(</span><span class="nx">initialCount</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">add</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="nf">setCount</span><span class="p">((</span><span class="nx">i</span><span class="p">)</span> <span class="o">=></span> <span class="nx">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">subtract</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="nf">setCount</span><span class="p">((</span><span class="nx">i</span><span class="p">)</span> <span class="o">=></span> <span class="nx">i</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">return </span><span class="p">(</span>
<span class="p"><></span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span><span class="si">{</span><span class="nx">children</span><span class="si">}</span><span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">div</span> <span class="na">style</span><span class="p">=</span><span class="si">{</span><span class="p">{</span><span class="na">display</span><span class="p">:</span><span class="dl">'</span><span class="s1">flex</span><span class="dl">'</span><span class="p">}</span><span class="si">}</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">subtract</span><span class="si">}</span><span class="p">></span>-<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"><</span><span class="nt">pre</span><span class="p">></span><span class="si">{</span><span class="nx">count</span><span class="si">}</span><span class="p"></</span><span class="nt">pre</span><span class="p">></span>
<span class="p"><</span><span class="nt">button</span> <span class="na">onClick</span><span class="p">=</span><span class="si">{</span><span class="nx">add</span><span class="si">}</span><span class="p">></span>+<span class="p"></</span><span class="nt">button</span><span class="p">></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
<span class="p"></></span>
<span class="p">)</span>
<span class="p">}</span>
</code></pre>
</div>
<p>Importing the <code>HelloReact</code> component is much like the <code>HelloMarkdown</code> component. However, this time we're including <code>someProps</code> in the front matter to set the initial <code>count</code> to <code>0</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="c1">// src/pages/index.astro</span>
<span class="k">import</span> <span class="nx">HelloMarkdown</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/markdown/HelloMarkdown.astro</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">HelloReact</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/react/HelloReact.jsx</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">someProps</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">count</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ajcwebdev-astro</span><span class="dl">'</span>
<span class="o">---</span>
<span class="p"><</span><span class="nt">html</span> <span class="na">lang</span><span class="p">=</span><span class="s">"en"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>...<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>...<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloReact</span> <span class="si">{</span><span class="p">...</span><span class="nx">someProps</span><span class="si">}</span> <span class="na">client</span><span class="err">:</span><span class="na">visible</span><span class="p">></span>
React
<span class="p"></</span><span class="nc">HelloReact</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloMarkdown</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">footer</span><span class="p">></span>...<span class="p"></</span><span class="nt">footer</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre>
</div>
<p>We also include <code>client:visible</code> to hydrate the component as soon as the element enters the viewport.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnk6cjimj0gmhdqu3n5q.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftnk6cjimj0gmhdqu3n5q.png" alt="04 - home-page-with-react-component" width="800" height="325"></a></p>
<h3>
Create a Svelte Component
</h3>
<p>Add <code>svelte()</code> to <code>integrations</code> in <code>astro.config.mjs</code> to enable the Svelte renderer and provide support for Svelte components.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn astro add svelte
</code></pre>
</div>
<p>This also installs <code>@astrojs/svelte</code> and <code>svelte@^3.46.4</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// astro.config.mjs</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">astro/config</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">react</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@astrojs/react</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">svelte</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@astrojs/svelte</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
<span class="na">integrations</span><span class="p">:</span> <span class="p">[</span><span class="nf">react</span><span class="p">(),</span> <span class="nf">svelte</span><span class="p">()]</span>
<span class="p">})</span>
</code></pre>
</div>
<p>As with React, create a <code>svelte</code> directory and <code>HelloSvelte.svelte</code> file.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src/components/svelte
<span class="nb">touch </span>src/components/svelte/HelloSvelte.svelte
</code></pre>
</div>
<p>Our Svelte component will contain the same functionality as our React component.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!-- src/components/svelte/HelloSvelte.svelte --></span>
<span class="nt"><script></span>
<span class="kd">let</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</span>
<span class="kd">function</span> <span class="nf">add</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">count</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">subtract</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">count</span> <span class="o">-=</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="nt"><h2><slot</span> <span class="nt">/></h2></span>
<span class="nt"><div></span>
<span class="nt"><button</span> <span class="na">on:click=</span><span class="s">{subtract}</span><span class="nt">></span>-<span class="nt"></button></span>
<span class="nt"><pre></span>{ count }<span class="nt"></pre></span>
<span class="nt"><button</span> <span class="na">on:click=</span><span class="s">{add}</span><span class="nt">></span>+<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"><style></span>
<span class="nt">div</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt"></style></span>
</code></pre>
</div>
<p>Import <code>HelloSvelte</code> and set it to <code>client:visible</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="c1">// src/pages/index.astro</span>
<span class="k">import</span> <span class="nx">HelloMarkdown</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/markdown/HelloMarkdown.astro</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">HelloReact</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/react/HelloReact.jsx</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">HelloSvelte</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/svelte/HelloSvelte.svelte</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">someProps</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">count</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ajcwebdev-astro</span><span class="dl">'</span>
<span class="o">---</span>
<span class="p"><</span><span class="nt">html</span> <span class="na">lang</span><span class="p">=</span><span class="s">"en"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>...<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>...<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloReact</span> <span class="si">{</span><span class="p">...</span><span class="nx">someProps</span><span class="si">}</span> <span class="na">client</span><span class="err">:</span><span class="na">visible</span><span class="p">></span>
React
<span class="p"></</span><span class="nc">HelloReact</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloSvelte</span> <span class="na">client</span><span class="err">:</span><span class="na">visible</span><span class="p">></span>
Svelte
<span class="p"></</span><span class="nc">HelloSvelte</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloMarkdown</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">footer</span><span class="p">></span>...<span class="p"></</span><span class="nt">footer</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9tojm1qbuso56z1inds.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb9tojm1qbuso56z1inds.png" alt="05 - home-page-with-svelte-component" width="800" height="485"></a></p>
<h3>
Create a Vue Component
</h3>
<p>Add <code>vue()</code> to <code>integrations</code> in <code>astro.config.mjs</code> to enable the Vue renderer.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn astro add vue
</code></pre>
</div>
<p>This also install <code>@astrojs/vue</code> and <code>vue@^3.2.30</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// astro.config.mjs</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">astro/config</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">react</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@astrojs/react</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">svelte</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@astrojs/svelte</span><span class="dl">"</span>
<span class="k">import</span> <span class="nx">vue</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@astrojs/vue</span><span class="dl">"</span>
<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
<span class="na">integrations</span><span class="p">:</span> <span class="p">[</span><span class="nf">react</span><span class="p">(),</span> <span class="nf">svelte</span><span class="p">(),</span> <span class="nf">vue</span><span class="p">()]</span>
<span class="p">})</span>
</code></pre>
</div>
<p>You know the drill.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src/components/vue
<span class="nb">touch </span>src/components/vue/HelloVue.vue
</code></pre>
</div>
<p>Our Vue component will contain the same functionality as our React and Svelte components.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!-- src/components/vue/HelloVue.vue --></span>
<span class="nt"><template></span>
<span class="nt"><h2><slot</span> <span class="nt">/></h2></span>
<span class="nt"><div></span>
<span class="nt"><button</span> <span class="err">@</span><span class="na">click=</span><span class="s">"subtract()"</span><span class="nt">></span>-<span class="nt"></button></span>
<span class="nt"><pre></span>{{ count }}<span class="nt"></pre></span>
<span class="nt"><button</span> <span class="err">@</span><span class="na">click=</span><span class="s">"add()"</span><span class="nt">></span>+<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"></template></span>
<span class="nt"><script></span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">ref</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vue</span><span class="dl">'</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="nf">setup</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">count</span> <span class="o">=</span> <span class="nf">ref</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">add</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">(</span><span class="nx">count</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">count</span><span class="p">.</span><span class="nx">value</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">subtract</span> <span class="o">=</span> <span class="p">()</span> <span class="o">=></span> <span class="p">(</span><span class="nx">count</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nx">count</span><span class="p">.</span><span class="nx">value</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">return</span> <span class="p">{</span>
<span class="nx">count</span><span class="p">,</span> <span class="nx">add</span><span class="p">,</span> <span class="nx">subtract</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="p">}</span>
<span class="nt"></script></span>
<span class="nt"><style </span><span class="na">scoped</span><span class="nt">></span>
<span class="nt">div</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt"></style></span>
</code></pre>
</div>
<p>Import <code>HelloVue</code> and set it to <code>client:visible</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="c1">// src/pages/index.astro</span>
<span class="k">import</span> <span class="nx">HelloMarkdown</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/markdown/HelloMarkdown.astro</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">HelloReact</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/react/HelloReact.jsx</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">HelloSvelte</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/svelte/HelloSvelte.svelte</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">HelloVue</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/vue/HelloVue.vue</span><span class="dl">'</span>
<span class="kd">const</span> <span class="nx">someProps</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">count</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="p">}</span>
<span class="kd">let</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ajcwebdev-astro</span><span class="dl">'</span>
<span class="o">---</span>
<span class="p"><</span><span class="nt">html</span> <span class="na">lang</span><span class="p">=</span><span class="s">"en"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>...<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">header</span><span class="p">></span>...<span class="p"></</span><span class="nt">header</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloReact</span> <span class="si">{</span><span class="p">...</span><span class="nx">someProps</span><span class="si">}</span> <span class="na">client</span><span class="err">:</span><span class="na">visible</span><span class="p">></span>
React
<span class="p"></</span><span class="nc">HelloReact</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloSvelte</span> <span class="na">client</span><span class="err">:</span><span class="na">visible</span><span class="p">></span>
Svelte
<span class="p"></</span><span class="nc">HelloSvelte</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloVue</span> <span class="na">client</span><span class="err">:</span><span class="na">visible</span><span class="p">></span>
Vue
<span class="p"></</span><span class="nc">HelloVue</span><span class="p">></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloMarkdown</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">section</span><span class="p">></span>
<span class="p"><</span><span class="nt">footer</span><span class="p">></span>...<span class="p"></</span><span class="nt">footer</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7af6v0ry3f41i8e83fzl.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7af6v0ry3f41i8e83fzl.png" alt="06 - home-page-with-vue-component" width="800" height="632"></a></p>
<h3>
Create a GraphQL Data Fetching Component
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>src/components/graphql
<span class="nb">touch </span>src/components/graphql/HelloGraphQL.astro src/pages/graphql.astro
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="c1">// src/components/graphql/HelloGraphQL.astro</span>
<span class="kd">const</span> <span class="nx">res</span> <span class="o">=</span> <span class="k">await</span> <span class="nf">fetch</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://rickandmortyapi.com/graphql</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">method</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
<span class="na">headers</span><span class="p">:</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">Content-Type</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span>
<span class="p">},</span>
<span class="na">body</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span>
<span class="na">query</span><span class="p">:</span> <span class="s2">`{
characters {
results {
name
}
}
}`</span>
<span class="p">})</span>
<span class="p">})</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">res</span><span class="p">.</span><span class="nf">json</span><span class="p">()</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">characters</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">data</span>
<span class="o">---</span>
<span class="p"><</span><span class="nt">h2</span><span class="p">></span>Rick and Morty GraphQL API<span class="p"></</span><span class="nt">h2</span><span class="p">></span>
<span class="p"><</span><span class="nt">ul</span><span class="p">></span>
<span class="si">{</span><span class="nx">characters</span><span class="p">.</span><span class="nx">results</span><span class="p">.</span><span class="nf">map</span><span class="p">(({</span> <span class="nx">name</span> <span class="p">})</span> <span class="o">=></span> <span class="p">(</span>
<span class="p"><</span><span class="nt">li</span><span class="p">></span><span class="si">{</span><span class="nx">name</span><span class="si">}</span><span class="p"></</span><span class="nt">li</span><span class="p">></span>
<span class="p">))</span><span class="si">}</span>
<span class="p"></</span><span class="nt">ul</span><span class="p">></span>
</code></pre>
</div>
<p>Import <code>HelloGraphQL</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="c1">// src/pages/graphql.astro</span>
<span class="k">import</span> <span class="nx">HelloGraphQL</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/graphql/HelloGraphQL.astro</span><span class="dl">'</span>
<span class="kd">let</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">GraphQL</span><span class="dl">'</span>
<span class="o">---</span>
<span class="p"><</span><span class="nt">html</span> <span class="na">lang</span><span class="p">=</span><span class="s">"en"</span><span class="p">></span>
<span class="p"><</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">charset</span><span class="p">=</span><span class="s">"UTF-8"</span><span class="p">></span>
<span class="p"><</span><span class="nt">meta</span> <span class="na">name</span><span class="p">=</span><span class="s">"viewport"</span> <span class="na">content</span><span class="p">=</span><span class="s">"width=device-width"</span><span class="p">></span>
<span class="p"><</span><span class="nt">link</span> <span class="na">rel</span><span class="p">=</span><span class="s">"stylesheet"</span> <span class="na">href</span><span class="p">=</span><span class="s">"https://cdn.jsdelivr.net/npm/water.css@2/out/water.css"</span><span class="p">></span>
<span class="p"><</span><span class="nt">title</span><span class="p">></span><span class="si">{</span><span class="nx">title</span><span class="si">}</span><span class="p"></</span><span class="nt">title</span><span class="p">></span>
<span class="p"></</span><span class="nt">head</span><span class="p">></span>
<span class="p"><</span><span class="nt">body</span><span class="p">></span>
<span class="p"><</span><span class="nt">main</span><span class="p">></span>
<span class="p"><</span><span class="nc">HelloGraphQL</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">main</span><span class="p">></span>
<span class="p"></</span><span class="nt">body</span><span class="p">></span>
<span class="p"></</span><span class="nt">html</span><span class="p">></span>
</code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13zxil1nxfslupdt9onu.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F13zxil1nxfslupdt9onu.png" alt="07 - graphql-page-with-graphql-data-fetching-component" width="800" height="824"></a></p>
<h2>
Deploy to Netlify
</h2>
<p>The Astro docs contain over <a href="proxy.php?url=https://docs.astro.build/guides/deploy/">a dozen different deployment options</a>. We will deploy our project to Netlify. Create a <code>netlify.toml</code> file for our build configuration.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">touch </span>netlify.toml
</code></pre>
</div>
<p>Add <code>yarn build</code> for the build command and <code>dist</code> for the publish directory.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight toml"><code><span class="nn">[build]</span>
<span class="py">command</span> <span class="p">=</span> <span class="s">"yarn build"</span>
<span class="py">publish</span> <span class="p">=</span> <span class="s">"dist"</span>
</code></pre>
</div>
<p>Here is our final project structure.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>/
├── public
├── src
│ ├── components
│ │ ├── graphql
│ │ │ └── HelloGraphQL.astro
│ │ ├── markdown
│ │ │ └── HelloMarkdown.astro
│ │ ├── react
│ │ │ └── HelloReact.jsx
│ │ ├── svelte
│ │ │ └── HelloSvelte.svelte
│ │ └── vue
│ │ └── HelloVue.vue
│ └── pages
│ ├── graphql.astro
│ └── index.astro
├── .gitignore
├── astro.config.mjs
├── netlify.toml
└── package.json
</code></pre>
</div>
<h3>
Create a GitHub Repository
</h3>
<p>Initialize a git repo and push to a GitHub repository.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"svuereactroQL"</span>
gh repo create ajcwebdev-astro <span class="nt">--public</span> <span class="nt">--push</span> <span class="se">\</span>
<span class="nt">--source</span><span class="o">=</span><span class="nb">.</span> <span class="se">\</span>
<span class="nt">--description</span><span class="o">=</span><span class="s2">"Example Astro project with Markdown, React, Svelte, and Vue"</span> <span class="se">\</span>
<span class="nt">--remote</span><span class="o">=</span>upstream
</code></pre>
</div>
<h3>
Connect GitHub Repository to Netlify
</h3>
<p>Connect your GitHub repository to Netlify.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswov4429p9qim3zqk76x.jpeg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fswov4429p9qim3zqk76x.jpeg" alt="08 - connect-github-repository-to-netlify" width="800" height="614"></a></p>
<p>Go to "Site settings," to give yourself a custom domain name.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2byjbeb82upj9wntcemf.jpeg" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2byjbeb82upj9wntcemf.jpeg" alt="09 - add-a-custom-domain" width="800" height="478"></a></p>
<p>Open <a href="proxy.php?url=https://ajcwebdev-astro.netlify.app/">ajcwebdev-astro.netlify.app</a> to see the deployed site.</p>
astrovitepartialhydrationWhat's Partial Hydration and Why's Everyone Talking About It?ajcwebdevMon, 22 Nov 2021 18:12:13 +0000
https://dev.to/ajcwebdev/what-is-partial-hydration-and-why-is-everyone-talking-about-it-3k56
https://dev.to/ajcwebdev/what-is-partial-hydration-and-why-is-everyone-talking-about-it-3k56<p>In <a href="proxy.php?url=https://addyosmani.com/blog/rehydration/" rel="noopener noreferrer">The Cost Of Client-side Rehydration (February 8, 2019)</a>, Addy Osmani argued that:</p>
<blockquote>
<p><em>Server rendering a page and then rehydrating DOM client-side through a serialized version of a UI's dependencies (rehydration) can come with a real cost. It can heavily delay Time To Interactive by making UI look ready before client-side processing has completed.</em></p>
</blockquote>
<p>He wondered if the benefits of rehydration were outweighed by the Uncanny Valley that is created for users by painting pixels early. To illustrate this point, he created the following graphic:</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Fhydrate%402x.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Faddyosmani.com%2Fassets%2Fimages%2Fhydrate%402x.png" alt="01-rehydration-uncanny-valley"></a></p>
<h2>
Outline
</h2>
<ul>
<li>
To Hydrate or Not to Hydrate
<ul>
<li>Partial Hydration and Progressive Rehydration</li>
<li>Islands of Interactivity</li>
</ul>
</li>
<li>
Adding Partial Hydration to Existing Frameworks
<ul>
<li>React</li>
<li>Preact</li>
<li>Vue</li>
<li>Solid</li>
<li>Svelte</li>
</ul>
</li>
<li>
Frameworks Built for Partial Hydration
<ul>
<li>Marko</li>
<li>Elder.js</li>
<li>Astro</li>
<li>Slinkity</li>
<li>îles</li>
<li>Qwik</li>
</ul>
</li>
<li>Conclusion</li>
</ul>
<h2>
To Hydrate or Not to Hydrate
</h2>
<p>Hydration (or rehydration) is a technique that uses client-side JavaScript to convert static HTML pages into dynamic web pages by attaching event handlers to the HTML elements. The JavaScript can be delivered either through static hosting or server-side rendering.</p>
<h3>
Partial Hydration and Progressive Rehydration
</h3>
<p>Partial rehydration is an extension of the idea of progressive rehydration where individual pieces of a server-rendered application are “booted up” over time. This contrasts with the approach of initializing the entire application at once. In an attempt to systematically define and compare the different commonly used rendering and hydration techniques, Addy Osmani and Jason Miller published <a href="proxy.php?url=https://developers.google.com/web/updates/2019/02/rendering-on-the-web" rel="noopener noreferrer">Rendering on the Web (February 6, 2019)</a>:</p>
<blockquote>
<p><em>Partial rehydration has proven difficult to implement. This approach is an extension of the idea of progressive rehydration, where the individual pieces (components / views / trees) to be progressively rehydrated are analyzed and those with little interactivity or no reactivity are identified.</em></p>
<p><em>For each of these mostly-static parts, the corresponding JavaScript code is then transformed into inert references and decorative functionality, reducing their client-side footprint to near-zero.</em></p>
</blockquote>
<p>Sounds great! But like all things in tech, nothing is a silver bullet (especially since silver bullets only work on <a href="proxy.php?url=https://twitter.com/threepointone/status/1459574071465762820" rel="noopener noreferrer">werewolf shaped problems</a> anyway).</p>
<blockquote>
<p><em>The partial hydration approach comes with its own issues and compromises. It poses some interesting challenges for caching, and client-side navigation means we can't assume server-rendered HTML for inert parts of the application will be available without a full page load.</em></p>
</blockquote>
<p>To summarize the points of the article, the authors included this behemoth of a comparison table:</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevelopers.google.com%2Fweb%2Fupdates%2Fimages%2F2019%2F02%2Frendering-on-the-web%2Finfographic.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdevelopers.google.com%2Fweb%2Fupdates%2Fimages%2F2019%2F02%2Frendering-on-the-web%2Finfographic.png" alt="02-comparison-chart-of-different-rendering-techniques"></a></p>
<p>But this table isn't the whole story, as Jason Miller noted on <a href="proxy.php?url=https://twitter.com/_developit/status/1093256348488396800" rel="noopener noreferrer">Twitter</a>:</p>
<blockquote>
<p><em>One thing I want to clarify: the diagram in this tweet doesn't tell the full story, and doesn't show SSR+Hydration with possible optimizations like component caching applied.</em></p>
</blockquote>
<p>To expand on this view point, <a href="proxy.php?url=https://twitter.com/mikesherov/status/1093842567173718016" rel="noopener noreferrer">Mike Sherov</a> argued:</p>
<blockquote>
<p><em>The chart talks about Time to First Byte and Time to Interactive relative to First Contentful Paint, but not First Contentful Paint relative to Time to First Byte... so it misses out on that positive fact that SSR solutions will render faster. The chart assumes that “SSR with (re)hydration” doesn’t employ a “JavaScript as progressive enhancement”... that is, it assumes the page is only functional after all JavaScript is delivered</em></p>
<p><em>Next.js and others encourage JavaScript as progressive enhancement so that Time to Interactive equals First Contentful Paint still. As mentioned in the disclaimer tweet, this ignores CDN caching of SSR HTML, but ignoring it significantly changes the value prop of “SSR with JavaScript as progressive enhancement”. CDN caching of HTML is flexible AND has high Time to First Byte.</em></p>
</blockquote>
<h3>
Islands of Interactivity
</h3>
<p>While continuing to explore this problem space, Jason Miller coined the term <a href="proxy.php?url=https://jasonformat.com/islands-architecture/" rel="noopener noreferrer">Islands Architecture (August 11, 2020)</a> in reference to the "Component Islands" pattern advocated by Katie Sylor-Miller. He believed web developers should aim to create "islands of interactivity" with JavaScript carefully selected for inclusion only when necessary.</p>
<blockquote>
<p><em>In an "islands" model, server rendering is not a bolt-on optimization aimed at improving SEO or UX. Instead, it is a fundamental part of how pages are delivered to the browser. The HTML returned in response to navigation contains a meaningful and immediately renderable representation of the content the user requested.</em></p>
</blockquote>
<p>Does this mean that partial hydration and the Island's Architecture are interchangeable, or that partial hydration is an implementation of the Island's Architecture? Not exactly. According to Ryan Carniato in <a href="proxy.php?url=https://dev.to/this-is-learning/is-0kb-of-javascript-in-your-future-48og">Is 0kb of JavaScript in your Future? (May 3, 2021)</a>, partial hydration is a lot like the Island's architecture because the end result is Islands of interactivity but the difference is the authoring experience.</p>
<blockquote>
<p><em>Instead of authoring a static layer and an Island's layer, you write your code like a single app more like a typical frontend framework. Partial hydration automatically can create the islands for you to ship the minimal code to the browser. Marko is a JavaScript library that uses its compiler to automate this partial hydration process to remove Server only rendered components from the browser bundle.</em></p>
</blockquote>
<h2>
Adding Partial Hydration to Existing Frameworks
</h2>
<p>Despite the growing awareness throughout 2019 and 2020 of the difficulties of hydration and the need for some form of partial hydration, we can find discussions of the topic much earlier. Paul Lewis described three different levels of hydration (which he called "booting models") in his blog post, <a href="proxy.php?url=https://aerotwist.com/blog/when-everything-is-important-nothing-is/" rel="noopener noreferrer">When everything's important, nothing is! (December 10, 2016)</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F2000%2F0%2AKhPIdw8fgMb8uHRb" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F2000%2F0%2AKhPIdw8fgMb8uHRb" alt="03-three-modes-of-partial-hydration-javascript-based-server-render-plus-hydrate-and-progressive-render-plus-bootstrap"></a></p>
<p>Early attempts at this were made by <a href="proxy.php?url=https://github.com/angular/angular/issues/13446" rel="noopener noreferrer">Angular</a> and <a href="proxy.php?url=https://github.com/ember-fastboot/ember-cli-fastboot#ember-fastboot" rel="noopener noreferrer">Ember</a>. These attempts appear to have struggled to gain traction. The relevant Angular issue is currently still open 5 years later and Brian Cardarella argued in <a href="proxy.php?url=https://dockyard.com/blog/2017/08/01/should-you-use-ember-fastboot-or-not-part-1" rel="noopener noreferrer">Should you use Ember FastBoot or not? (August 1, 2017)</a> that the costs were too high for DockYard to implement FastBoot.</p>
<h3>
React
</h3>
<p>The release of <a href="proxy.php?url=https://reactjs.org/blog/2017/09/26/react-v16.0.html" rel="noopener noreferrer">React v16.0</a> in September 2017 introduced the <a href="proxy.php?url=https://reactjs.org/docs/react-dom.html#hydrate" rel="noopener noreferrer"><code>hydrate()</code></a> method as an alternative to the <code>render()</code> method. According to Andrew Clark:</p>
<blockquote>
<p><em>React 16 is better at hydrating server-rendered HTML once it reaches the client. It no longer requires the initial render to exactly match the result from the server. Instead, it will attempt to reuse as much of the existing DOM as possible. No more checksums!</em></p>
</blockquote>
<p><code>hydrate()</code> behaves similarly to the <code>render()</code> method. However, instead of rendering a React element into the DOM in the supplied container and returning a reference to the component, <code>hydrate()</code> can hydrate a container whose HTML contents are rendered by <code>ReactDOMServer</code> and attempt to attach event listeners to the existing markup.</p>
<p>To balance this trade off, developers were advised to use <code>render()</code> for rendering the content solely on the client side and <code>hydrate()</code> for rendering on top of server-side rendered markup. Much like the Google team, the React team urged caution when deciding whether to use hydration:</p>
<blockquote>
<p><em>In general, we don’t recommend that you render different content on the client versus the server, but it can be useful in some cases (e.g. timestamps). However, it’s dangerous to have missing nodes on the server render as this might cause sibling nodes to be created with incorrect attributes.</em></p>
</blockquote>
<p>Two years later, Sebastian Markbåge opened a PR to implement <a href="proxy.php?url=https://github.com/facebook/react/pull/14717" rel="noopener noreferrer">Partial Hydration (January 28, 2019)</a> as a native feature in React:</p>
<blockquote>
<p><em>Partial hydration adds a mechanism for partially hydrating a server rendered result while other parts of the page are still loading the code or data. This means that you can start interacting with parts of the screen while others are still hydrating. In this model you always have to hydrate the root content first because it is what provides props to the children, which can be of arbitrary complexity.</em></p>
<p><em>The model assumes that the root of the app is designed to be relatively shallow and then each abstraction gets progressively more complex the deeper it gets. To become interactive faster, components in the tree can themselves use progressive enhancement to add more complexity after initial hydration.</em></p>
</blockquote>
<h3>
Preact
</h3>
<p>Markus Oberlehner foreshadows Slinkity (discussed more in a later section) by explaining how to combine the static site generator Eleventy with Preact in <a href="proxy.php?url=https://markus.oberlehner.net/blog/building-partially-hydrated-progressively-enhanced-static-websites-with-isomorphic-preact-and-eleventy/" rel="noopener noreferrer">Building Partially Hydrated, Progressively Enhanced Static Websites with Isomorphic Preact and Eleventy (March 22, 2020)</a>:</p>
<blockquote>
<p><em>What if I tell you that we can have it all? We can use a modern JavaScript framework, at least as powerful as React, combine it with an exceptional static site generator, and build our websites in a way that they offer real progressive enhancement and a minimal JavaScript bundle size. Combining Eleventy with Preact makes this possible.</em></p>
</blockquote>
<h3>
Vue
</h3>
<p>Based on his work porting <a href="proxy.php?url=https://github.com/maoberlehner/vue-lazy-hydration" rel="noopener noreferrer">vue-lazy-hydration</a> to Vue 3, Markus Oberlehner compares different forms of partial hydration in <a href="proxy.php?url=https://markus.oberlehner.net/blog/partial-hydration-concepts-lazy-and-active/" rel="noopener noreferrer">Partial Hydration Concepts: Lazy and Active (November 8, 2020)</a>:</p>
<blockquote>
<p><em>Lazy Hydration is a form of Partial Hydration where you can trigger hydration at a later point and not immediately after loading the site. A good example is components outside of the viewport. You don’t need to hydrate them instantly, but you can delay hydration until the component is visible.</em><br>
</p>
</blockquote>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><template></span>
<span class="nt"><TheNavigation/></span>
<span class="nt"><LazyHydrate</span> <span class="na">skip</span><span class="nt">></span>
<span class="nt"><TheBlogArticle/></span>
<span class="nt"></LazyHydrate></span>
<span class="nt"><LazyHydrate</span> <span class="na">when-visible</span><span class="nt">></span>
<span class="nt"><TheFooter/></span>
<span class="nt"></LazyHydrate></span>
<span class="nt"></template></span>
</code></pre>
</div>
<blockquote>
<p><em>The Lazy Hydration concept you can see above works best when we have a mostly interactive application, but we want to exclude some parts from the hydration. Let’s imagine the other way around: we have a huge application with deeply nested components, it is a static website, but there is this one deeply nested component, which must be interactive.</em><br>
</p>
</blockquote>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><template></span>
<span class="nt"><LazyHydrate</span> <span class="na">skip</span><span class="nt">></span>
<span class="nt"><App/></span>
<span class="nt"></LazyHydrate></span>
<span class="nt"></template></span>
</code></pre>
</div>
<h3>
Solid
</h3>
<p>Ryan Carniato, taking influence from Marko (discussed more in a later section), proposes using sub-component (or component-level) hydration in <a href="proxy.php?url=https://github.com/solidjs/solid/issues/264" rel="noopener noreferrer">Partial Hydration (November 15, 2020)</a>.</p>
<blockquote>
<p><em>This is the last core feature missing in our SSR story. Truth be told outside of Marko most libraries aren't doing amazing here. We can too consider a more manual approach here at first. I think the key innovation would be to follow Marko's footsteps and recognize there in fact 3 partial hydration modes rather than 2 other libraries are aware of. There is a middle mode that make us considerably more efficient at this. This is uniquely possible given the granular non-component tied approach used here.</em></p>
</blockquote>
<h3>
Svelte
</h3>
<p>According to Rich Harris in March 2021, partial hydration is <a href="proxy.php?url=https://news.ycombinator.com/item?id=26558886" rel="noopener noreferrer">on Svelte's radar but not yet on its roadmap</a>. This makes sense since Svelte is already compiling an optimized build of vanilla JavaScript that does not require a runtime. However, Kevin Åberg Kultalahti proposed <a href="proxy.php?url=https://github.com/sveltejs/kit/issues/1390" rel="noopener noreferrer">Partial Hydration in Svelte (May 9, 2021)</a> via the <code>use:action</code> directive. We will also see in the next section that a Svelte metaframework, Elder.js, has struck out on its own to implement partial hydration.</p>
<h2>
Frameworks Built for Partial Hydration
</h2>
<p>As we can see, essentially every major frontend JavaScript framework has attempted to add some form of partial hydration with varying degrees of success. But there is another category entirely of frontend frameworks that consider partial hydration a key feature to be included from their inception.</p>
<h3>
Marko
</h3>
<p>If there is one framework that can be given credit for first introducing partial hydration as a primary feature (even before the term was invented), my money would be on Marko. Patrick Steele-Idem discussed Marko's internals at length in <a href="proxy.php?url=https://tech.ebayinc.com/engineering/async-fragments-rediscovering-progressive-html-rendering-with-marko/" rel="noopener noreferrer">Async Fragments: Rediscovering Progressive HTML Rendering with Marko (December 8, 2014)</a>. He also includes a wealth of links to prior art such as:</p>
<ul>
<li>
<a href="proxy.php?url=https://blog.codinghorror.com/the-lost-art-of-progressive-html-rendering/" rel="noopener noreferrer">The Lost Art of Progressive HTML Rendering (November 14, 2005)</a> by Jeff Atwood.</li>
<li>
<a href="proxy.php?url=https://developer.yahoo.com/performance/rules.html" rel="noopener noreferrer">Best Practices for Speeding Up Your Web Site (December 12, 2006)</a> by Yahoo's Exceptional Performance Team (epic team name). </li>
<li>
<a href="proxy.php?url=https://www.phpied.com/progressive-rendering-via-multiple-flushes/" rel="noopener noreferrer">Progressive rendering via multiple flushes (December 21, 2009)</a> by Stoyan Stefanov.</li>
<li>
<a href="proxy.php?url=https://www.facebook.com/notes/10158791368532200/" rel="noopener noreferrer">BigPipe: Pipelining web pages for high performance (June 4, 2010)</a> by Changhao Jiang.</li>
</ul>
<p>Michael Rawlings takes a look at Marko's innovations from the perspective of the last few years in <a href="proxy.php?url=https://medium.com/@mlrawlings/maybe-you-dont-need-that-spa-f2c659bc7fec" rel="noopener noreferrer">Maybe you don’t need that SPA (May 12, 2020)</a>:</p>
<blockquote>
<p><em>Marko allows you to build pages by composing components and some of these components can be stateful. Only those components that have state, or other logic targeted at the browser are actually sent to the browser and Marko automatically handles serializing any data from the server needed by these components and mounting them in the browser.</em></p>
<p><em>This means for most apps, you end up sending down much less code than you would for an equivalent SPA — even with code-splitting. And if no components need to be hydrated? Nothing is hydrated.</em></p>
</blockquote>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F1400%2F1%2A5UGQKpjUAJAJMUtOgbJc5A.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmiro.medium.com%2Fmax%2F1400%2F1%2A5UGQKpjUAJAJMUtOgbJc5A.png" alt="04-full-page-hydration-versus-component-level-hydration-in-marko"></a></p>
<h3>
Elder.js
</h3>
<p>Despite Svelte core choosing to hold off on focusing on partial hydration throughout 2020-2021, one of the early entrants into the race for partial hydration leadership was Elder.js by Nick Reese, a static site generator built with Svelte. Focused primarily on SEO, Elder.js lets you hydrate just the parts of the client that need to be interactive.</p>
<p>This lets you reduce your payloads while still having control over component lazy-loading, preloading, and eager-loading. While lesser known than Astro, Elder.js included partial hydration as early as <a href="proxy.php?url=https://github.com/Elderjs/elderjs/commit/19ecd9429284e5358858bbb5fb92e17c41dea33d" rel="noopener noreferrer">August</a> <a href="proxy.php?url=https://github.com/Elderjs/elderjs/commit/b1bffae863228b8721ec78ed3f44822af24bc859" rel="noopener noreferrer">2020</a>, roughly six months before Astro's <a href="proxy.php?url=https://github.com/snowpackjs/astro/commit/af6b029e95e9c98e6fb9c642915d461b8d7f448e" rel="noopener noreferrer">initial commit</a>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="p"><</span><span class="nt">div</span> <span class="na">class</span><span class="p">=</span><span class="s">"right"</span><span class="p">></span>
<span class="p"><</span><span class="nc">Clock</span> <span class="na">hydrate-client</span><span class="p">=</span><span class="si">{</span><span class="p">{}</span><span class="si">}</span> <span class="p">/></span>
<span class="p"></</span><span class="nt">div</span><span class="p">></span>
</code></pre>
</div>
<h3>
Astro
</h3>
<p>Despite the early innovations of Marko and Elder, the framework that deserves the most credit for bringing partial hydration to the mainstream is Astro. Fred K. Schott describes the architecture and goals of Astro in <a href="proxy.php?url=https://astro.build/blog/introducing-astro/" rel="noopener noreferrer">Introducing Astro: Ship Less JavaScript (June 8, 2021)</a>.</p>
<blockquote>
<p><em>Astro works a lot like a static site generator. If you have ever used Eleventy, Hugo, or Jekyll (or even a server-side web framework like Rails, Laravel, or Django) then you should feel right at home with Astro.</em></p>
<p><em>In Astro, you compose your website using UI components from your favorite JavaScript web framework (React, Svelte, Vue, etc). Astro renders your entire site to static HTML during the build. The result is a fully static website with all JavaScript removed from the final page.</em></p>
</blockquote>
<p>There are already plenty of frameworks based on <a href="proxy.php?url=https://nextjs.org/docs/advanced-features/static-html-export" rel="noopener noreferrer">React</a>, <a href="proxy.php?url=https://nuxtjs.org/announcements/going-full-static/" rel="noopener noreferrer">Vue</a>, and <a href="proxy.php?url=https://sapper.svelte.dev/docs#sapper_export" rel="noopener noreferrer">Svelte</a> that include the ability to render your components to static HTML during build time. However, if you want to hydrate these projects on the client then you have to ship an entire bundle of dependencies along with the static HTML. Unlike these previous frameworks, Astro includes the ability to load just a single component and its dependencies where that component is needed.</p>
<blockquote>
<p><em>Of course, sometimes client-side JavaScript is inevitable for things like image carousels, shopping carts, and auto-complete search bars. This is where Astro really shines: When a component needs some JavaScript, Astro only loads that one component (and any dependencies). The rest of your site continues to exist as static, lightweight HTML.</em></p>
<p><em>In Astro, this kind of partial hydration is built into the tool itself. You can even automatically defer components to only load once they become visible on the page with the :visible modifier. This new approach to web architecture is called islands architecture.</em></p>
</blockquote>
<p>Astro includes five <code>client:*</code> directives to hydrate components on the client at runtime. A directive is a component attribute that tells Astro how your component should be rendered.</p>
<ul>
<li>
<code><MyComponent client:load /></code> - Hydrate the component on page load.</li>
<li>
<code><MyComponent client:idle /></code> - Hydrate the component as soon as main thread is free.</li>
<li>
<code><MyComponent client:visible /></code> - Hydrate the component as soon as the element enters the viewport. Useful for content lower down on the page.</li>
<li>
<code><MyComponent client:media={QUERY} /></code> - Hydrate the component as soon as the browser matches the given media query. Useful for sidebar toggles, or other elements that should only display on mobile or desktop devices.</li>
<li>
<code><MyComponent client:only /></code> - Hydrates the component at page load, similar to <code>client:load</code>. The component will be skipped at build time, useful for components that are entirely dependent on client-side APIs. Best avoided unless absolutely needed.</li>
</ul>
<p>The following example is hydrating a React component (<code>MyReactComponent</code>) in the browser with <code>client:visible</code>. <code>client:visible</code> means the component won't load any client-side JavaScript until it becomes visible in the user's browser.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="o">---</span>
<span class="k">import</span> <span class="nx">MyReactComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/MyReactComponent.jsx</span><span class="dl">'</span><span class="p">;</span>
<span class="o">---</span>
<span class="p"><</span><span class="nc">MyReactComponent</span> <span class="na">client</span><span class="err">:</span><span class="na">visible</span> <span class="p">/></span>
</code></pre>
</div>
<h3>
Slinkity
</h3>
<p><a href="proxy.php?url=https://slinkity.dev/" rel="noopener noreferrer">Slinkity</a> is a framework that uses Vite to bring dynamic, client side interactions to your static 11ty sites with <a href="proxy.php?url=https://slinkity.dev/docs/partial-hydration/" rel="noopener noreferrer">partial hydration</a>. In <a href="proxy.php?url=https://www.netlify.com/blog/2021/11/12/ship-javascript-where-it-counts-with-vite-partial-hydration/" rel="noopener noreferrer">Ship JavaScript where it counts with Vite + Partial Hydration (November 12, 2021)</a>, Ben Holmes makes the case for turning off partial hydration by default so the developer has to explicitly opt-in:</p>
<blockquote>
<p><em>Right now, the Jamstack landscape definitely relies on an opt-out mindset. Too much JavaScript on initial page load? Opt-out with code splitting and lazy ESM loading. Need less JavaScript on your company’s splash page? Opt-out with server-rendered components. The world of partial hydration introduced by Astro, Slinkity + 11ty, or Îles flips that opt-out to an opt-in.</em></p>
<p><em>Too much JavaScript on initial page load? Well, you’ll need to opt-in to JavaScript hydration for your UI components to create that problem! These frameworks default to no JavaScript shipped for your React, Vue, Svelte, etc, with hydration “modes” to decide how and when those resources should be loaded (if at all).</em></p>
</blockquote>
<p>To choose how a given component is rendered, you'll need to pass a <code>render</code> prop, as in a prop literally named <code>render</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight jsx"><code><span class="k">export</span> <span class="kd">const</span> <span class="nx">frontMatter</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">render</span><span class="p">:</span> <span class="dl">'</span><span class="s1">eager</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">Page</span><span class="p">()</span> <span class="p">{...}</span>
</code></pre>
</div>
<p>Alternatively, you can use a shortcode that includes <code>render</code> and the hydration option you want to select. The default for all components is <code>eager</code> to mirror how previous component-based frameworks operate.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!-- page-with-shortcode.html --></span>
<span class="nt"><body></span>
{% react 'components/Example' 'render' 'eager' %}
<span class="nt"></body></span>
</code></pre>
</div>
<ul>
<li>A component loaded with <code>eager</code> will be rendered to static HTML, and shipped to the client as a JavaScript bundle. In the previous example, visiting <code>page-with-shortcode.html</code> imports React and the <code>components/Example.jsx</code> JavaScript bundle as soon as the page is done parsing, ensuring our component is interactive as soon as possible.</li>
<li>
<code>lazy</code> is similar to <code>eager</code> except it only loads your component's JavaScript when your component is scrolled into view by using the <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API" rel="noopener noreferrer">Intersection Observer API</a>.</li>
<li>
<code>static</code> components are rendered to HTML at build time and don't ship any JavaScript to the client, meaning no interactivity or state management. This is useful if you want to use component languages like React as just a templating language.</li>
</ul>
<h3>
îles
</h3>
<p><a href="proxy.php?url=https://iles-docs.netlify.app/" rel="noopener noreferrer">îles</a> is a static-site generator that provides automatic <a href="proxy.php?url=https://iles-docs.netlify.app/guide/hydration" rel="noopener noreferrer">partial hydration</a>. The name of the project is French for "islands," as a clever homage to both the islands architecture and it's build tool Vite. Inspired by Astro, you are able to define which components should remain interactive in the production build with <code>client:</code> directives in components. Here is an example with MDX:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight markdown"><code><span class="nn">---</span>
<span class="na">audio</span><span class="pi">:</span> <span class="s">/song-for-you.mp3</span>
<span class="nn">---</span>
<span class="gu">## Play a song</span>
<span class="nt"><AudioPlayer</span> <span class="err">{...</span><span class="na">frontmatter</span><span class="err">}</span> <span class="na">client:visible</span><span class="nt">/></span>
</code></pre>
</div>
<p>Components in the <code>src/components</code> directory are auto-imported on demand, which is why the example above does not need to import the <code>AudioPlayer</code> component. You can also use directives inside Vue components as seen in the following example:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><Audio</span> <span class="na">client:visible</span> <span class="na">:src=</span><span class="s">"audio"</span> <span class="na">:initialDuration=</span><span class="s">"initialDuration"</span><span class="nt">/></span>
</code></pre>
</div>
<h3>
Qwik
</h3>
<p><a href="proxy.php?url=https://github.com/BuilderIO/qwik" rel="noopener noreferrer">Qwik</a> focuses on resumability of server-side-rendering of HTML and fine-grained lazy-loading of code. It is designed for the best possible time to interactive. According to Miško Hevery in <a href="proxy.php?url=https://dev.to/mhevery/a-first-look-at-qwik-the-html-first-framework-af">A First Look at Qwik - The HTML First Framework (June 23, 2021)</a>:</p>
<blockquote>
<p><em>The basic goal of Qwik is to focus on the time-to-interactive metric by delaying JavaScript as much as possible to take advantage of the browser’s lazy loading capabilities. This is in stark contrast to existing frameworks that treat server-side-rendering and time-to-interactive more as an afterthought rather than a primary goal, which drives all other design decisions.</em></p>
</blockquote>
<p>With Qwik we now have a framework explicitly aiming to prioritize Time to Interactive which it does through resumability. But what does it mean to be resumable?</p>
<blockquote>
<p><em>The basic idea behind Qwik is that it is resumable. It can continue where the server left off. There is but the tiniest amount of code to execute on the client. The <code>qwikloader</code>, which takes the static HTML generated from server-side-rendering and resumes it, is less than 1kb and will execute in under 1ms... All the other interactivity of your website is downloaded lazily as you interact with the site in the smallest possible chunks.</em></p>
</blockquote>
<p>Qwik also <a href="proxy.php?url=https://dev.to/mhevery/qwik-the-answer-to-optimal-fine-grained-lazy-loading-2hdp">rehydrates components asynchronously</a> and out of order to ensure that the first interaction does not cause a full application download and bootstrap.</p>
<blockquote>
<p><em>Here asynchronously means that the rendering system can pause rendering to asynchronously download a template for a component, and then continue the rendering process. This is in stark contrast to all of the existing frameworks, which have fully synchronous rendering pipelines. And because the rendering is synchronous, there is no place to insert asynchronous lazy-loading. The consequence is that all of the templates need to be present ahead of call to render.</em></p>
</blockquote>
<p>Ryan Carniato had <a href="proxy.php?url=https://twitter.com/RyanCarniato/status/1458872931962933259" rel="noopener noreferrer">this</a> to say about Qwik:</p>
<blockquote>
<p><em>It's the only framework that goes from shipping basically only the JavaScript you need on a page (starting maybe even from 0kb), to being able to go full SPA. It can actually with out of order lazy hydration bring in client routing after the fact.</em></p>
<p><em>It is the only framework that I know of today that basically operates as Islands but can morph into a single application as needed. This is coming to Marko and probably others but we shouldn't get ahead of ourselves. And it's a longer road for traditional SPAs.</em></p>
</blockquote>
<h2>
Conclusion
</h2>
<p>Frameworks like Qwik bring us full circle from Addy Osmani's warning to developers that slow Time to Interactive scores can cause an uncanny valley effect. Websites ship skeleton HTML that looks like it should be interactive but momentarily is not because the user has to wait for the client to hydrate.</p>
<p><a href="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdy3wa5ewpz9o9xoa87ys.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdy3wa5ewpz9o9xoa87ys.png" alt="05-qwik-time-to-interactive-graphic"></a></p>
<p>But as this article demonstrates, as soon as developers are given the ability to configure these parameters they find themselves in a complex decision matrix of trade offs. The next evolution will involve taking these performance advantages and designing conventions to simplify these techniques and create happy paths in the frameworks that developers are already comfortable using.</p>
<p><em>Special thanks to Ryan Carniato for early feedback on the post and Ben Holmes for chatting with me for literally months about this stuff.</em></p>
webdevperformancehydrationastroHow to Display a Custom Daily GreetingajcwebdevThu, 11 Nov 2021 01:46:36 +0000
https://dev.to/ajcwebdev/how-to-display-a-custom-greeting-based-on-the-day-of-the-week-1n9p
https://dev.to/ajcwebdev/how-to-display-a-custom-greeting-based-on-the-day-of-the-week-1n9p<h2>
Introduction
</h2>
<p>I discovered a cool little trick while source diving through <a href="proxy.php?url=https://scottmathson.com/">Scott Mathson</a>'s web site. With just a couple lines of JavaScript you can create a message that displays a different greeting depending on the day of the week.</p>
<h3>
Create a Script with a Weekday Array
</h3>
<p>Create a <code><script></code> tag with <code>type</code> of <code>text/javascript</code>. Define a variable called <code>weekday</code> with a different greeting set to each index.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="kd">var</span> <span class="nx">weekday</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Array</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">spectacular Sunday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">marvelous Monday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">terrific Tuesday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">wonderful Wednesday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">totally cool Thursday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">fantastic Friday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">sweet Saturday</span><span class="dl">"</span>
<span class="nt"></script></span>
</code></pre>
</div>
<h3>
Set Weekday Value to the Current Date
</h3>
<p>Also inside the script tag, create a variable called <code>currentDate</code> set with the <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date"><code>Date()</code></a> object and then set the current day to <code>weekdayValue</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="kd">var</span> <span class="nx">currentDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">()</span>
<span class="nx">weekdayValue</span> <span class="o">=</span> <span class="nx">currentDate</span><span class="p">.</span><span class="nf">getDay</span><span class="p">()</span>
</code></pre>
</div>
<h3>
Write to the Document
</h3>
<p>Use the <a href="proxy.php?url=https://developer.mozilla.org/en-US/docs/Web/API/Document/write">Document.write()</a> method to write a string of text to the document with paragraph tags containing the weekday value..<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="nb">document</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span>
<span class="dl">'</span><span class="s1"><p>Have a </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">weekday</span><span class="p">[</span><span class="nx">weekdayValue</span><span class="p">]</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">!</p></span><span class="dl">'</span>
<span class="p">)</span>
</code></pre>
</div>
<h3>
Noscript Fallback
</h3>
<p>Lastly, you'll want to include a <code><noscript></code> tag in case the user has JavaScript turned off in their browser.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><noscript></span>
<span class="nt"><p></span>Have a great day!<span class="nt"></p></span>
<span class="nt"></noscript></span>
</code></pre>
</div>
<h2>
Full Script
</h2>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="nt"><script </span><span class="na">type=</span><span class="s">"text/javascript"</span><span class="nt">></span>
<span class="kd">var</span> <span class="nx">weekday</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Array</span><span class="p">(</span><span class="mi">7</span><span class="p">)</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">spectacular Sunday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">marvelous Monday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">terrific Tuesday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">wonderful Wednesday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">totally cool Thursday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">fantastic Friday</span><span class="dl">"</span>
<span class="nx">weekday</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">sweet Saturday</span><span class="dl">"</span>
<span class="kd">var</span> <span class="nx">currentDate</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">()</span>
<span class="nx">weekdayValue</span> <span class="o">=</span> <span class="nx">currentDate</span><span class="p">.</span><span class="nf">getDay</span><span class="p">()</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span>
<span class="dl">'</span><span class="s1"><p>Have a </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">weekday</span><span class="p">[</span><span class="nx">weekdayValue</span><span class="p">]</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">!</p></span><span class="dl">'</span>
<span class="p">)</span>
<span class="nt"></script></span>
<span class="nt"><noscript></span>
<span class="nt"><p></span>Have a great day!<span class="nt"></p></span>
<span class="nt"></noscript></span>
</code></pre>
</div>
htmljavascriptdatedomA First Look at OakajcwebdevTue, 02 Nov 2021 14:47:43 +0000
https://dev.to/ajcwebdev/a-first-look-at-oak-2gob
https://dev.to/ajcwebdev/a-first-look-at-oak-2gob<h2>
Outline
</h2>
<ul>
<li>Introduction</li>
<li>
Setup
<ul>
<li>Install Deno Executable</li>
<li>Install deployctl</li>
<li>Create Project Files</li>
</ul>
</li>
<li>
Create Deno Server
<ul>
<li>Run Deno Server</li>
</ul>
</li>
<li>
Create Oak Server
<ul>
<li>Application Class</li>
<li>Respond with HTML</li>
<li>Add Router</li>
</ul>
</li>
<li>
Deno Deploy
<ul>
<li>Run Oak Server with deployctl</li>
<li>Initialize GitHub Repository</li>
<li>Install the Deno Deploy GitHub App</li>
<li>Sign Up for Deno Deploy</li>
<li>Create Deno Deploy Project</li>
<li>Connect GitHub Repository</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong><em>All of this project's code can be found in the <a href="proxy.php?url=https://github.com/ajcwebdev/a-first-look/tree/main/backend/oak/">First Look monorepo</a> on my GitHub.</em></strong></p>
</blockquote>
<h2>
Introduction
</h2>
<p><a href="proxy.php?url=https://oakserver.github.io/oak/">Oak</a> is a middleware framework for Deno's native HTTP server and Deno Deploy. It is influenced by <a href="proxy.php?url=https://github.com/koajs/koa">Koa</a> (hence the anagram) and includes a middleware router inspired by <a href="proxy.php?url=https://github.com/koajs/router">@koa/router</a>.</p>
<h2>
Setup
</h2>
<p>Deno ships as a single executable with no dependencies. However, we will also need to install <code>deployctl</code> for Deno Deploy.</p>
<h3>
Install Deno Executable
</h3>
<p>There are various ways of installing Deno depending on your operating system. If you are using Mac or Linux, the official Shell install script is recommend.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">-fsSL</span> https://deno.land/x/install/install.sh | sh
</code></pre>
</div>
<p>You can find a list of different installation methods on the official <a href="proxy.php?url=https://deno.land/#installation">deno.land documentation</a> and the <a href="proxy.php?url=https://github.com/denoland/deno_install"><code>deno_install</code></a> repo.</p>
<h3>
Install deployctl
</h3>
<p>With <a href="proxy.php?url=https://deno.com/deploy/docs/deployctl"><code>deployctl</code></a> you can run your Deno Deploy scripts on your local machine. Your scripts are run in the Deno CLI, with the correct TypeScript types for Deno Deploy. After installing Deno, <code>deployctl</code> can be installed using the following command.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>deno <span class="nb">install</span> <span class="se">\</span>
<span class="nt">--allow-read</span> <span class="se">\</span>
<span class="nt">--allow-write</span> <span class="se">\</span>
<span class="nt">--allow-env</span> <span class="se">\</span>
<span class="nt">--allow-net</span> <span class="se">\</span>
<span class="nt">--allow-run</span> <span class="se">\</span>
<span class="nt">--no-check</span> <span class="se">\</span>
<span class="nt">--force</span> <span class="se">\</span>
https://deno.land/x/deploy/deployctl.ts
</code></pre>
</div>
<p>You may need to set the <code>PATH</code> variable as seen below but with your own path to the <code>deno</code> executable in place of <code>/Users/ajcwebdev</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"/Users/ajcwebdev/.deno/bin:</span><span class="nv">$PATH</span><span class="s2">"</span>
</code></pre>
</div>
<h3>
Create Project Files
</h3>
<p>All we need is a directory containing an <code>index.js</code> file.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>ajcwebdev-oak
<span class="nb">cd </span>ajcwebdev-oak
<span class="nb">touch </span>index.js
<span class="nb">echo</span> <span class="s1">'.DS_Store'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<h2>
Create Deno Server
</h2>
<p>Before diving into Oak, it's useful to see the underlying server code they are building upon. The <a href="proxy.php?url=https://deno.land/std">Deno Standard Library</a> has an <a href="proxy.php?url=https://deno.land/std/http">http</a> module with a basic hello world application. Save the following code in <code>index.js</code>:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight typescript"><code><span class="c1">// index.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">listenAndServe</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">https://deno.land/[email protected]/http/server.ts</span><span class="dl">"</span>
<span class="nf">listenAndServe</span><span class="p">(</span>
<span class="dl">"</span><span class="s2">:8080</span><span class="dl">"</span><span class="p">,</span>
<span class="p">()</span> <span class="o">=></span> <span class="k">new</span> <span class="nc">Response</span><span class="p">(</span><span class="dl">"</span><span class="s2">Hello from Deno on Localhost 8080</span><span class="dl">"</span><span class="p">)</span>
<span class="p">)</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">Server running on localhost:8080</span><span class="dl">"</span><span class="p">)</span>
</code></pre>
</div>
<h3>
Run Deno Server
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>deno run <span class="se">\</span>
<span class="nt">--allow-net</span> index.js <span class="se">\</span>
<span class="nt">--watch</span> <span class="se">\</span>
<span class="nt">--no-check</span>
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>Server running on localhost:8080
</code></pre>
</div>
<p>Open <a href="proxy.php?url=https://localhost:8080">localhost:8080</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qhbpey6rzsx7a9fc5al.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5qhbpey6rzsx7a9fc5al.png" alt="01-hello-from-deno-localhost-8080" width="800" height="135"></a></p>
<h2>
Create Oak Server
</h2>
<p>Oak is a third party module for Deno hosted on their <a href="proxy.php?url=https://deno.land/x">deno.land/x</a> service. To import one of these modules, use the following format for code URLs:<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>https://deno.land/x/IDENTIFIER@VERSION/FILE_PATH
</code></pre>
</div>
<p>If you leave out the version it will default to the most recent version released for the module. The Deno docs recommend pinning to a specific version to avoid unexpected breaking changes. At the time of this writing, the current version of Oak is <code>https://deno.land/x/[email protected]/mod.ts</code>.</p>
<h3>
Application Class
</h3>
<p>The <code>Application</code> class coordinates managing the HTTP server, running middleware, and handling errors that occur when processing requests. Two methods are generally used:</p>
<ul>
<li>Middleware is added via the <code>.use()</code> method.</li>
<li>The <code>.listen()</code> method starts the server and then processes requests with the registered middleware.</li>
</ul>
<p>Once the server is open, before it starts processing requests, the application will fire a <code>"listen"</code> event, which can be listened for via the <code>.addEventListener()</code> method.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// index.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Application</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">https://deno.land/x/[email protected]/mod.ts</span><span class="dl">"</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Application</span><span class="p">()</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">use</span><span class="p">((</span><span class="nx">ctx</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">body</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Hello from Oak on Localhost 8080</span><span class="dl">"</span>
<span class="p">})</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">listen</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Server running on localhost:8080`</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">listen</span><span class="p">({</span> <span class="na">port</span><span class="p">:</span> <span class="mi">8080</span> <span class="p">})</span>
</code></pre>
</div>
<p>The middleware is processed as a stack, where each middleware function can control the flow of the response. When the middleware is called, it is passed a context (<code>ctx</code>) and reference to the "next" method in the stack.</p>
<ul>
<li>
<code>.response</code> accesses the <code>Response</code> object to form the response sent back to the requestor.</li>
<li>The <code>.body</code> method returns a representation of the request body.</li>
</ul>
<p>Return to <a href="proxy.php?url=https://localhost:8080">localhost:8080</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vibugmkjovcgc0x8lfi.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0vibugmkjovcgc0x8lfi.png" alt="02-hello-from-oak-localhost-8080" width="800" height="153"></a></p>
<h3>
Respond with HTML
</h3>
<p>To respond with HTML instead of plain text, use <code>.headers.set</code> on <code>ctx.response</code> and set the <code>Content-Type</code> to <code>text/html</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// index.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Application</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">https://deno.land/x/[email protected]/mod.ts</span><span class="dl">"</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Application</span><span class="p">()</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">use</span><span class="p">((</span><span class="nx">ctx</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">body</span> <span class="o">=</span> <span class="dl">"</span><span class="s2"><h2>Hello from Oak on Localhost 8080</h2></span><span class="dl">"</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">text/html</span><span class="dl">"</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">listen</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Server running on localhost:8080`</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">listen</span><span class="p">({</span> <span class="na">port</span><span class="p">:</span> <span class="mi">8080</span> <span class="p">})</span>
</code></pre>
</div>
<p>Return to <a href="proxy.php?url=https://localhost:8080">localhost:8080</a> to see the change.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg09my7pcmf719kqzhjqp.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg09my7pcmf719kqzhjqp.png" alt="03-hello-with-html-localhost-8080" width="800" height="162"></a></p>
<h3>
Add Router
</h3>
<p>The <code>Router</code> class produces middleware which can be used with an <code>Application</code> to enable routing based on the pathname of the request.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// index.js</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">Application</span><span class="p">,</span> <span class="nx">Router</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">https://deno.land/x/[email protected]/mod.ts</span><span class="dl">"</span>
<span class="kd">const</span> <span class="nx">router</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Router</span><span class="p">()</span>
<span class="kd">const</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Application</span><span class="p">()</span>
<span class="nx">router</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">ctx</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">body</span> <span class="o">=</span> <span class="dl">"</span><span class="s2"><h2>Hello from Router on Localhost 8080</h2></span><span class="dl">"</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">text/html</span><span class="dl">"</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">router</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">/about</span><span class="dl">"</span><span class="p">,</span> <span class="p">(</span><span class="nx">ctx</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">body</span> <span class="o">=</span> <span class="dl">"</span><span class="s2"><h2>This page tells you about stuff</h2></span><span class="dl">"</span>
<span class="nx">ctx</span><span class="p">.</span><span class="nx">response</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">Content-Type</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">text/html</span><span class="dl">"</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="nx">router</span><span class="p">.</span><span class="nf">routes</span><span class="p">())</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="nx">router</span><span class="p">.</span><span class="nf">allowedMethods</span><span class="p">())</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">listen</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Server running on localhost:8080`</span><span class="p">)</span>
<span class="p">})</span>
<span class="nx">app</span><span class="p">.</span><span class="nf">listen</span><span class="p">({</span> <span class="na">port</span><span class="p">:</span> <span class="mi">8080</span> <span class="p">})</span>
</code></pre>
</div>
<p>Return to <a href="proxy.php?url=https://localhost:8080">localhost:8080</a> to see the change.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4egupo6ugjhozkdpd3rw.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4egupo6ugjhozkdpd3rw.png" alt="04-hello-from-router-localhost-8080" width="800" height="166"></a></p>
<p>Open <a href="proxy.php?url=https://localhost:8080/about">localhost:8080/about</a> to see the new about page.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle15a8omlgirjovvfw91.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fle15a8omlgirjovvfw91.png" alt="05-about-page-localhost-8080" width="800" height="164"></a></p>
<h2>
Deno Deploy
</h2>
<p><a href="proxy.php?url=https://deno.com/deploy">Deno Deploy</a> is a distributed system that runs JavaScript, TypeScript, and WebAssembly at the edge, worldwide. It is a multi-tenant JavaScript engine running in <a href="proxy.php?url=https://deno.com/deploy/docs/regions">25 data centers across the world</a>.</p>
<h3>
Run Oak Server with deployctl
</h3>
<p>You can run your script like it would run on Deno Deploy with <code>deployctl</code>. Include <code>--no-check</code> so TypeScript doesn't explode in a giant fireball of errors. You can also add the <code>--watch</code> flag to automatically restart the script when any modules in the module graph change.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>deployctl run index.js <span class="nt">--no-check</span> <span class="nt">--watch</span>
</code></pre>
</div>
<p>If you return to <a href="proxy.php?url=https://localhost:8080">localhost:8080</a> there will be no change in the application.</p>
<h3>
Initialize GitHub Repository
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"finally, another project named after a tree"</span>
gh repo create ajcwebdev-oak
git push <span class="nt">-u</span> origin main
</code></pre>
</div>
<h3>
Install the Deno Deploy GitHub App
</h3>
<p>We will be deploying this script from a URL on GitHub. Install the <a href="proxy.php?url=https://github.com/apps/deno-deploy">Deno Deploy GitHub App</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fee3rm7ipo08hdpoynu9k.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fee3rm7ipo08hdpoynu9k.png" alt="06-deno-deploy-github-app" width="800" height="389"></a></p>
<h3>
Sign Up for Deno Deploy
</h3>
<p><a href="proxy.php?url=https://dash.deno.com/signin">Sign up</a> for a Deno Deploy account and connect the account to your GitHub. You will be taken to a page for your projects.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frr6dgseaurjwmztb13d2.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frr6dgseaurjwmztb13d2.png" alt="07-deno-deploy-projects" width="800" height="584"></a></p>
<h3>
Create Deno Deploy Project
</h3>
<p>Click "New Project" and enter a name for your project.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fog4k861uc8mfxugb5kk7.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fog4k861uc8mfxugb5kk7.png" alt="08-create-a-new-project" width="800" height="556"></a></p>
<p>After creating your project you are given the option of using existing templates.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqagjh06bscbuvyl3hmhe.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqagjh06bscbuvyl3hmhe.png" alt="09-ajcwebdev-oak-project-page" width="800" height="471"></a></p>
<h3>
Connect GitHub Repository
</h3>
<p>Since we will not be using a template, scroll down to connect the GitHub repository we just created.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgluc2k2esfv4ny5hm8b.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbgluc2k2esfv4ny5hm8b.png" alt="10-deploy-from-github" width="800" height="244"></a></p>
<p>Paste the raw GitHub URL for your function.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faoq4i2myic9j9nhthzaf.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faoq4i2myic9j9nhthzaf.png" alt="11-git-integration" width="800" height="601"></a></p>
<p>Click "Link" and go to <a href="proxy.php?url=https://ajcwebdev-oak.deno.dev">ajcwebdev-oak.deno.dev</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqvoegko08vwyrr105oi.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffqvoegko08vwyrr105oi.png" alt="12-ajcwebdev-oak-deno-dev" width="800" height="150"></a></p>
<p>You can use the project overview page to review your project's settings, add/remove domains, or view the deployment logs.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdz492gxt09q32x24u1k.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdz492gxt09q32x24u1k.png" alt="13-project-overview-page" width="800" height="401"></a></p>
oakdenohttprouterA First Look at Nuxt 3ajcwebdevFri, 15 Oct 2021 08:03:36 +0000
https://dev.to/ajcwebdev/a-first-look-at-nuxt-3-1769
https://dev.to/ajcwebdev/a-first-look-at-nuxt-3-1769<h2>
Outline
</h2>
<ul>
<li>
Introduction
<ul>
<li>Migrating from Nuxt 2 to Nuxt 3</li>
<li>Try Online Example</li>
</ul>
</li>
<li>
Create Nuxt 3 Project from Scratch
<ul>
<li>App Component</li>
<li>Project Scripts</li>
<li>Start Development Server</li>
<li>Build for Production</li>
<li>Pages Directory</li>
</ul>
</li>
<li>
Server Engine
<ul>
<li>Server Directory for API Routes</li>
</ul>
</li>
<li>
Deployment
<ul>
<li>Deploy to Vercel</li>
<li>Deploy to Netlify</li>
</ul>
</li>
</ul>
<blockquote>
<p><strong><em>All of this project's code can be found in the <a href="proxy.php?url=https://github.com/ajcwebdev/a-first-look/tree/main/frontend/nuxt3/">First Look monorepo</a> on my GitHub.</em></strong></p>
</blockquote>
<h2>
Introduction
</h2>
<p>Nuxt is a Vue metaframework that aims to make web development intuitive and performant while keeping great developer experience in mind. The original version, created by <a href="proxy.php?url=https://github.com/nuxt/nuxt.js/commit/0072ed31da6ce39d21046e05898f956cff190390">Sébastien Chopin in October 2016</a>, was built to emulate the features of Next.js (such as file-system based routing, server-side rendering, and API routes) but with Vue instead of React. Version 3 has been <a href="proxy.php?url=https://nuxtjs.org/announcements/nuxt3-beta/">over a year in the making</a>.</p>
<blockquote>
<p><em>We are excited to open source Nuxt 3 after more than a year of intense development. On top of supporting Vue 3 and Vite, Nuxt 3 contains a new server engine, unlocking new full-stack capabilities to Nuxt server and beyond.</em></p>
<p><strong><em><a href="proxy.php?url=https://nuxtjs.org/announcements/nuxt3-beta/">Introducing Nuxt 3 Beta</a></em></strong></p>
</blockquote>
<p>Nuxt 3 is composed of the following <a href="proxy.php?url=https://github.com/nuxt/framework/tree/main/packages">core packages</a>:</p>
<div class="table-wrapper-paragraph"><table>
<thead>
<tr>
<th>Package</th>
<th>Purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="proxy.php?url=https://github.com/nuxt/framework/tree/main/packages/nuxt3">nuxt3</a></td>
<td>Core Engine</td>
</tr>
<tr>
<td><a href="proxy.php?url=https://github.com/nuxt/framework/tree/main/packages/nuxi">nuxi</a></td>
<td>Command line interface</td>
</tr>
<tr>
<td>
<a href="proxy.php?url=https://github.com/nuxt/framework/tree/main/packages/vite">vite-builder</a> or <a href="proxy.php?url=https://github.com/nuxt/framework/tree/main/packages/webpack">webpack-builder</a>
</td>
<td>Bundlers</td>
</tr>
<tr>
<td><a href="proxy.php?url=https://github.com/nuxt/framework/tree/main/packages/nitro">nitro</a></td>
<td>Server engine</td>
</tr>
<tr>
<td><a href="proxy.php?url=https://github.com/nuxt/framework/tree/main/packages/kit">kit</a></td>
<td>Development kit</td>
</tr>
<tr>
<td><a href="proxy.php?url=https://github.com/nuxt/framework/tree/main/packages/bridge">bridge</a></td>
<td>Nuxt 2 Bridge</td>
</tr>
</tbody>
</table></div>
<p>Together these packages provide a selection of libraries for managing many common concerns for developers building on the web today such as:</p>
<div class="table-wrapper-paragraph"><table>
<thead>
<tr>
<th>Library</th>
<th>Common Concern</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="proxy.php?url=https://v3.vuejs.org">Vue.js</a></td>
<td>JavaScript framework for reactivity and components</td>
</tr>
<tr>
<td>
<a href="proxy.php?url=https://vitejs.dev/">Vite</a> or <a href="proxy.php?url=https://webpack.js.org/">Webpack 5</a>
</td>
<td>Bundler for hot module replacement in dev and bundling for production</td>
</tr>
<tr>
<td><a href="proxy.php?url=https://esbuild.github.io">esbuild</a></td>
<td>Transpiler for supporting legacy browsers while still using current JavaScript syntax</td>
</tr>
<tr>
<td><a href="proxy.php?url=https://github.com/unjs/h3">h3</a></td>
<td>Server that can serve your application in development and support <a href="proxy.php?url=https://v3.vuejs.org/guide/ssr/introduction.html#what-is-server-side-rendering-ssr">server-side rendering</a> or API routes</td>
</tr>
<tr>
<td><a href="proxy.php?url=https://next.router.vuejs.org">vue-router</a></td>
<td>Routing library to handle client-side navigation</td>
</tr>
</tbody>
</table></div>
<p>In addition to curating and integrating these tools, Nuxt also provides directory structure conventions for managing pages and components.</p>
<h3>
Migrating from Nuxt 2 to Nuxt 3
</h3>
<blockquote>
<p><em>Note: If you don't have a Nuxt 2 project, skip ahead to the section, "Create Nuxt 3 Project from Scratch."</em></p>
</blockquote>
<p>The team has created two <a href="proxy.php?url=https://v3.nuxtjs.org/getting-started/migration">migration guides</a> and tooling in the form of <a href="proxy.php?url=https://v3.nuxtjs.org/bridge/overview/">Nuxt Bridge</a> to make migrating as smooth as possible . If you have an existing Nuxt 2 project, the team <strong>strongly recommends</strong> you begin by using Nuxt Bridge to experiment with new features while avoiding breaking changes. Bridge is a forward-compatibility layer that allows you to experience new Nuxt 3 features by installing and enabling a Nuxt module.</p>
<p>All Nuxt 2 modules should be forward compatible with Nuxt 3 as long as they migrate to bridge or if they are already following guidelines. All (upcoming) modules made with <code>@nuxt/kit</code> should be backward compatible with Nuxt 2 projects (even without bridge) as long as they are not depending on a Nuxt 3 / Bridge-only feature. Since Nuxt 3 natively supports TypeScript and ECMAScript Modules, it is useful to avoid CommonJS syntax such as <code>__dirname</code>, <code>__filename</code>, <code>require()</code>, and <code>module.exports</code> as much as possible.</p>
<h3>
Try Online Example
</h3>
<p>We will be building a Nuxt application from scratch. However, you can start playing with Nuxt 3 online in your browser on either <a href="proxy.php?url=https://stackblitz.com/github/nuxt/starter/tree/v3-stackblitz">StackBlitz</a> or <a href="proxy.php?url=https://codesandbox.io/s/github/nuxt/starter/tree/v3-codesandbox">CodeSandBox</a>.</p>
<h2>
Create Nuxt 3 Project from Scratch
</h2>
<p>Create a blank new directory and initialize a <code>package.json</code> file for defining our scripts and dependencies.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>ajcwebdev-nuxt3
<span class="nb">cd </span>ajcwebdev-nuxt3
yarn init <span class="nt">-y</span>
yarn add <span class="nt">-D</span> [email protected] vercel
</code></pre>
</div>
<blockquote>
<p><em>Version is current as of June 10, 2022, check the <a href="proxy.php?url=https://github.com/nuxt/framework/releases">release schedule on GitHub</a> for most recent version.</em></p>
</blockquote>
<p>The project will start with only two additional files:</p>
<ul>
<li>
<code>app.vue</code> to display our Vue application</li>
<li>
<code>.gitignore</code> to avoid accidentally committing stuff that shouldn't be committed
</li>
</ul>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> app.vue
<span class="nb">echo</span> <span class="s1">'node_modules\n.DS_Store\n*.log\n.nuxt\nnuxt.d.ts\n.output'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<p>We will create a directory for <code>pages</code> and an <code>api</code> later on.</p>
<h3>
App Component
</h3>
<p><code>app.vue</code> is the main component in your Nuxt 3 applications. This means that anything you add to this file (such as JavaScript or CSS) will be global and included in every page.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!-- app.vue --></span>
<span class="nt"><template></span>
<span class="nt"><div></span>
<span class="nt"><h2></span>ajcwebdev-nuxt3<span class="nt"></h2></span>
<span class="nt"></div></span>
<span class="nt"></template></span>
</code></pre>
</div>
<p>With Nuxt 3, the <code>pages</code> directory is optional. If a <code>pages</code> directory is not present, Nuxt won't include the <a href="proxy.php?url=https://next.router.vuejs.org/">vue-router</a> dependency. This is useful when working on single landing pages or other applications that don't not need routing.</p>
<h3>
Project Scripts
</h3>
<p>In your <code>package.json</code>, add the following scripts (<code>build</code>, <code>dev</code>, <code>generate</code>, and <code>preview</code>).<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight json"><code><span class="p">{</span><span class="w">
</span><span class="nl">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"ajcwebdev-nuxt3"</span><span class="p">,</span><span class="w">
</span><span class="nl">"license"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MIT"</span><span class="p">,</span><span class="w">
</span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"build"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nuxt build"</span><span class="p">,</span><span class="w">
</span><span class="nl">"dev"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nuxt dev"</span><span class="p">,</span><span class="w">
</span><span class="nl">"generate"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nuxt generate"</span><span class="p">,</span><span class="w">
</span><span class="nl">"preview"</span><span class="p">:</span><span class="w"> </span><span class="s2">"nuxt preview"</span><span class="w">
</span><span class="p">},</span><span class="w">
</span><span class="nl">"devDependencies"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="nl">"nuxt"</span><span class="p">:</span><span class="w"> </span><span class="s2">"3.0.0-rc.3"</span><span class="p">,</span><span class="w">
</span><span class="nl">"vercel"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^25.1.0"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p>Nuxi is the new CLI for Nuxt 3 and has four main commands:</p>
<ol>
<li>
<code>dev</code> - Start development server</li>
<li>
<code>build</code> - Make production assets</li>
<li>
<code>generate</code> - Generate static site</li>
<li>
<code>preview</code> - Preview production build</li>
</ol>
<h3>
Start Development Server
</h3>
<p>The <code>yarn dev</code> command starts your Nuxt app in development mode and includes hot module replacement. Include the <code>--open</code> flag to automatically open the browser after starting up.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn dev
</code></pre>
</div>
<p>The CLI will display links to the running application and performance metrics.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>Nuxt CLI v3.0.0-rc.3
> Local: http://localhost:3000/
> Network: http://192.168.1.78:3000/
ℹ Vite client warmed up in 288ms
ℹ Vite server warmed up in 50ms
✔ Vite server built in 269ms
✔ Nitro built in 171 ms
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:3000">localhost:3000</a> to see your application.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oc49oolv9d31ljml352.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9oc49oolv9d31ljml352.png" alt="01-ajcwebdev-nuxt3-localhost-3000" width="800" height="169"></a></p>
<h3>
Build for Production
</h3>
<p>The <code>yarn build</code> command builds your Nuxt application for production. It creates a <code>.output</code> directory with your application, server, and dependencies ready to be deployed.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn build
</code></pre>
</div>
<p>Nitro produces a standalone server dist that is independent of <code>node_modules</code>. The output is combined with both runtime code to run your Nuxt server in any environment and serve you static files.</p>
<h3>
Pages Directory
</h3>
<p>The <code>pages</code> directory is optional, meaning that if you only use <code>app.vue</code>, <code>vue-router</code> won't be included, reducing your application bundle size. However, if you do include it, Nuxt will automatically integrate <a href="proxy.php?url=https://next.router.vuejs.org/">Vue Router</a> and map the files in the <code>pages</code> directory into the routes of your application. Delete <code>app.vue</code> and create two files in a new <code>pages</code> directory.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">rm</span> <span class="nt">-rf</span> app.vue
<span class="nb">mkdir </span>pages
<span class="nb">echo</span> <span class="o">></span> pages/about.vue
<span class="nb">echo</span> <span class="o">></span> pages/index.vue
</code></pre>
</div>
<p>Include the previous home page's content in <code>pages/index.vue</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!-- pages/index.vue --></span>
<span class="nt"><template></span>
<span class="nt"><div></span>
<span class="nt"><h2></span>ajcwebdev-nuxt3<span class="nt"></h2></span>
<span class="nt"></div></span>
<span class="nt"></template></span>
</code></pre>
</div>
<p>We also created <code>pages/about.vue</code> for an about page. Include the following code to make sure that people know stuff about your things.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight html"><code><span class="c"><!-- pages/about.vue --></span>
<span class="nt"><template></span>
<span class="nt"><div></span>
<span class="nt"><h2></span>This page tells you stuff about things!<span class="nt"></h2></span>
<span class="nt"></div></span>
<span class="nt"></template></span>
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:3000/about">localhost:3000/about</a> to see the about page.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyngyw7ae27x213gfu4f.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flyngyw7ae27x213gfu4f.png" alt="02-about-page-localhost-3000" width="800" height="162"></a></p>
<h2>
Server Engine
</h2>
<p>Nuxt 3 is powered by a new server engine called Nitro. Nitro is used in development and production. It includes cross-platform support for Node.js, browser engines, service workers and serverless. It enables API routes, code-splitting, async-loaded chunks, and hybrid static/serverless modes. The Server API endpoints and middleware internally use <a href="proxy.php?url=https://github.com/unjs/h3">h3</a>.</p>
<ul>
<li>Handlers can directly return objects/arrays for JSON response</li>
<li>Handlers can return promises that are awaited (<code>res.end()</code> and <code>next()</code> are also supported)</li>
<li>Helper functions include body parsing, cookie handling, redirects, and headers</li>
</ul>
<p>Nitro allows 'direct' calling of routes via the globally-available <code>$fetch</code> helper. If run on the browser it will make an API call to the server. But it will call the relevant function if run on the server and save an additional API call. The <code>$fetch</code> API uses <a href="proxy.php?url=https://github.com/unjs/ohmyfetch">ohmyfetch</a> to:</p>
<ul>
<li>Automatically parse JSON responses (with access to raw responses if needed)</li>
<li>Automatically handle request body and params with correct <code>Content-Type</code> headers added</li>
</ul>
<h3>
Server Directory for API Routes
</h3>
<p>The <code>server</code> directory contains API endpoints and server middleware for your project. It is used to create any backend logic for your Nuxt application. Nuxt will automatically read in any files in the <code>~/server/api</code> directory to create API endpoints. Each file should export a default function that handles API requests.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir</span> <span class="nt">-p</span> server/api
<span class="nb">echo</span> <span class="o">></span> server/api/index.js
</code></pre>
</div>
<p>Add the following code to <code>index.js</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// server/api/index.js</span>
<span class="k">export</span> <span class="k">default </span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span><span class="s2">`
<h2>Hello from Nuxt 3</h2>
`</span><span class="p">)</span>
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:3000/api">localhost:3000/api</a> and make sure not to include a trailing slash or it will error out.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9g8nobz0clkvvmhw8e5o.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9g8nobz0clkvvmhw8e5o.png" alt="03-hello-api-route-localhost-3000" width="800" height="148"></a></p>
<p>We could also flesh out this route into a full HTML document.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// server/api/index.js</span>
<span class="k">export</span> <span class="k">default </span><span class="p">(</span><span class="nx">req</span><span class="p">,</span> <span class="nx">res</span><span class="p">)</span> <span class="o">=></span> <span class="p">(</span><span class="s2">`
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="A minimal HTML website served on a Nuxt API route.">
<title>Nuxt 3 API Route</title>
</head>
<body>
<header>
<h2>Hello from Nuxt 3</h2>
</header>
<footer>ajcwebdev '22</footer>
</body>
</html>
`</span><span class="p">)</span>
</code></pre>
</div>
<h2>
Deployment
</h2>
<p>What's the point of a framework if you can't deploy it on multiple Jamstack platforms?</p>
<h3>
Deploy to Vercel
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>yarn vercel
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>? Set up and deploy “~/ajcwebdev-nuxt3”? [Y/n] y
? Which scope do you want to deploy to? Anthony Campolo
? Link to existing project? [y/N] n
? What's your project's name? ajcwebdev-nuxt3
? In which directory is your code located? ./
Auto-detected Project Settings (Nuxt.js):
- Build Command: nuxt generate
- Output Directory: dist
- Development Command: nuxt
? Want to override the settings? [y/N] n
🔗 Linked to ajcwebdev/ajcwebdev-nuxt3 (created .vercel and added it to .gitignore)
🔍 Inspect: https://vercel.com/ajcwebdev/ajcwebdev-nuxt3/2hzPze5Wmzv9hDxNXGovirFLgw4X [5s]
✅ Production: https://ajcwebdev-nuxt3.vercel.app [copied to clipboard]
📝 Deployed to production. Run `vercel --prod` to overwrite later (https://vercel.link/2F).
💡 To change the domain or build command, go to https://vercel.com/ajcwebdev/ajcwebdev-nuxt3/settings
</code></pre>
</div>
<p>Open <a href="proxy.php?url=https://ajcwebdev-nuxt3.vercel.app/">ajcwebdev-nuxt3.vercel.app</a>.</p>
<h3>
Deploy to Netlify
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">echo</span> <span class="o">></span> netlify.toml
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight toml"><code><span class="nn">[build]</span>
<span class="py">command</span> <span class="p">=</span> <span class="s">"yarn build"</span>
<span class="py">publish</span> <span class="p">=</span> <span class="s">"dist"</span>
<span class="py">functions</span> <span class="p">=</span> <span class="s">".output/server"</span>
<span class="nn">[[redirects]]</span>
<span class="py">from</span> <span class="p">=</span> <span class="s">"/*"</span>
<span class="py">to</span> <span class="p">=</span> <span class="s">"/.netlify/functions/index"</span>
<span class="py">status</span> <span class="p">=</span> <span class="mi">200</span>
</code></pre>
</div>
<p>Create a GitHub repository with the GitHub CLI by running the following series of commands or visit <a href="proxy.php?url=https://repo.new">repo.new</a> and follow the instructions provided there.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"the nuxt best thing"</span>
gh repo create ajcwebdev-nuxt3 <span class="nt">--public</span> <span class="nt">--push</span> <span class="se">\</span>
<span class="nt">--source</span><span class="o">=</span><span class="nb">.</span> <span class="se">\</span>
<span class="nt">--description</span><span class="o">=</span><span class="s2">"An example Nuxt 3 application deployed on Netlify and Vercel"</span> <span class="se">\</span>
<span class="nt">--remote</span><span class="o">=</span>upstream
</code></pre>
</div>
<p>Connect your repo to your Netlify account.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz592uzltp4s5l3oyhzqv.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz592uzltp4s5l3oyhzqv.png" alt="04-connect-repo-to-netlify" width="800" height="309"></a></p>
<p>The build command and publish directory will be included automatically from the <code>netlify.toml</code> file.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F859hdgl9xdp8sxxmiosx.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F859hdgl9xdp8sxxmiosx.png" alt="05-netlify-site-settings" width="800" height="874"></a></p>
<p>Lastly, give yourself a custom domain.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8o7fjemh6o7f8n8hbhd.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8o7fjemh6o7f8n8hbhd.png" alt="06-add-custom-domain" width="800" height="301"></a></p>
<p>Open <a href="proxy.php?url=https://ajcwebdev-nuxt3.netlify.app/">ajcwebdev-nuxt3.netlify.app</a>.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrlsbltxfvo6rayxcbtl.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmrlsbltxfvo6rayxcbtl.png" alt="07-ajcwebdev-nuxt3-netlify-deploy" width="800" height="161"></a></p>
nuxtvuevitevercelWhy Am I Hung Up on the Term Fullstack?ajcwebdevTue, 05 Oct 2021 20:19:37 +0000
https://dev.to/ajcwebdev/why-am-i-hung-up-on-the-term-fullstack-52dm
https://dev.to/ajcwebdev/why-am-i-hung-up-on-the-term-fullstack-52dm<blockquote>
<p><em>Stacks evolve over time. But it’s not just what technologies they use, but what technology we even consider a part of a stack. What fullstack means morphs over time.</em></p>
<p><strong><em>Chris Coyier - <a href="proxy.php?url=https://css-tricks.com/what-does-it-mean-to-be-full-stack/">What Does it Mean to Be “Full Stack”?</a></em></strong></p>
</blockquote>
<p>Today I got into a Twitter #BEEF about the appropriateness of the term "fullstack framework." Some of you may know that I am the host of a podcast called <a href="proxy.php?url=https://fsjam.org">Fullstack Jamstack</a> and have made my name as a developer advocate for <a href="proxy.php?url=https://redwoodjs.com">RedwoodJS</a>, a self proclaimed "fullstack" JavaScript framework.</p>
<p>However, I do not make any claims to ownership of the term. My thoughts on the term and its definition are fiercely personal, and I welcome others to have their own passionate beliefs around it. I want to elicit dialogue around the term, not tell you what it means.</p>
<h2>
Why is this personal for me?
</h2>
<p>Before joining the RedwoodJS team I was learning how to code at Lambda School, learning a "fullstack web development" curriculum. This curriculum contained roughly:</p>
<ul>
<li>10% HTML/CSS</li>
<li>85% JavaScript/React</li>
<li>5% Node/Express/Postgres</li>
</ul>
<p>I felt this was an uneven split between the frontend and backend. It didn't seem accurate to label this as a "fullstack" curriculum, instead it seemed more accurate to call it a "frontend" curriculum with a very small amount of backend material included at the end.</p>
<h2>
What is the definition of "Full?"
</h2>
<p><iframe class="tweet-embed" id="tweet-1445444206948876293-893" src="proxy.php?url=https://platform.twitter.com/embed/Tweet.html?id=1445444206948876293">
</iframe>
// Detect dark theme
var iframe = document.getElementById('tweet-1445444206948876293-893');
if (document.body.className.includes('dark-theme')) {
iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1445444206948876293&theme=dark"
}
</p>
<p>I start with the postulate that the term "full" contains the most essential quality of "completeness." To say something is "full," is to say there is nothing left to add. This is why I would consider a stack without any database or storage to be incomplete.</p>
<p>How often do you use an application where everything you did with that application disappears after you take a break from using it? If you're just reading a blog post that's probably fine, not everything we do on the web requires persistence. But if you're writing that blog post, it's a different story.</p>
<h2>
Is there actually just a frontend and backend?
</h2>
<p>A recent turn of phrase has come up called the <a href="proxy.php?url=https://css-tricks.com/front-of-the-front-back-of-the-front/">"front of the backend"</a> to describe the illusive middle ground between the client side and the server side. Does this mean there is a "back of the frontend?"</p>
<p>There is the HTML and CSS for the content and styling and JavaScript (or some sort of JavaScript framework) for the client side interaction. But the data fetching logic and state management can be decoupled from the content and styling itself, forming its own layer of the application. This is the space Redux and React Query play in.</p>
<p>On the backend you have an operating system, server side language (or framework), and database for authentication, authorization, and persistence. But you can abstract a layer above that with an API mesh, serverless functions, or containers that let you ignore the actual operations of the underlying system.</p>
<h2>
What else should be included in the definition of "fullstack?"
</h2>
<p>What about accessibility, internationalization, localization, security, deployment, DevOps, automated testing, design systems, data collection, analytics, emails, etc? How wide does the definition go? Is it even reasonable for us to expect a single developer to <em>ever</em> be fullstack?</p>
<p>Unfortunately I don't have the answer to that question. But as long as we are going to be selling the term for our bootcamps, our frameworks, and our podcasts, then we need to think carefully about what we mean when we use the term.</p>
<p><em>Thank you to <a href="proxy.php?url=https://twitter.com/ralex1993">Alex</a> for his thoughtful notes and pushback on this piece.</em></p>
fullstackwebdevreactfrontendThree Ways to Deploy a Serverless GraphQL APIajcwebdevMon, 04 Oct 2021 19:56:03 +0000
https://dev.to/ajcwebdev/three-ways-to-deploy-a-serverless-graphql-api-3n6o
https://dev.to/ajcwebdev/three-ways-to-deploy-a-serverless-graphql-api-3n6o<h2>
Outline
</h2>
<ul>
<li>Introduction</li>
<li>Pros and Cons</li>
<li>
Deployment Providers
<ul>
<li>Netlify Functions</li>
<li>Serverless Framework</li>
<li>Amplify</li>
</ul>
</li>
<li>Serve Apollo Server Locally</li>
<li>Deploy Apollo Server Lambda with Netlify</li>
<li>Deploy Apollo Server Lambda with Serverless Framework</li>
<li>Deploy Apollo Server Lambda with Amplify</li>
<li>Serve GraphQL Yoga Locally</li>
<li>Deploy GraphQL Yoga with Netlify</li>
<li>Deploy GraphQL Yoga with Serverless Framework</li>
<li>Deploy GraphQL Yoga with Amplify</li>
</ul>
<blockquote>
<p><strong><em>All of this project's code can be found in the <a href="proxy.php?url=https://github.com/ajcwebdev/a-first-look/tree/main/deployment/serverless-graphql-api/">First Look monorepo</a> on my GitHub.</em></strong></p>
</blockquote>
<h2>
Introduction
</h2>
<p>There are a wide range of different options for GraphQL servers with varying deployment methods. Typical methods for hosting your server include virtual machines (AWS EC2, Digital Ocean Droplet) or containers (Cloud Run, AWS ECS). However, if your server does not need to persist state, then you can host it using a serverless function. This includes:</p>
<ul>
<li>Bundling your application into a zip file</li>
<li>Storing that static file somewhere with blob storage like an S3 bucket</li>
<li>Serving that file using an API gateway and serverless function like an AWS Lambda</li>
</ul>
<h2>
Pros and Cons
</h2>
<p>The benefits include removing large amounts of operations work such as managing the operating system of underlying VMs or creating optimized Dockerfiles. You also no longer need to worry about scaling your application or paying for idle time when your server is not being used.</p>
<p>Since the server state cannot be persisted, the drawbacks include removing the ability to use websockets or subscriptions. The functions will only work for basic <code>GET</code> and <code>POST</code> methods. You also need to write your code with the syntax of the underlying runtime which requires keeping in mind information such as the available Node version and compatible dependencies.</p>
<h2>
Deployment Providers
</h2>
<p>This article looks at three different methods of deploying two different GraphQL servers: Apollo Server and GraphQL Yoga. They will be deployed with Netlify Functions, Serverless Framework, and AWS Amplify. All three use the same underlying technology, AWS Lambda functions. They differ in terms of the conventions and abstractions around creating, developing, and deploying those functions.</p>
<h3>
Netlify Functions
</h3>
<p><a href="proxy.php?url=https://docs.netlify.com/functions/overview/">Netlify Functions</a> are scripts that you can write and deploy directly on Netlify. Netlify lets you deploy serverless Lambda functions without an AWS account, and with function management handled directly within Netlify. It requires the least amount of knowledge of AWS, but also gives you the least amount of control over the underlying infrastructure.</p>
<p>Lambda functions can be added to a project by creating a JavaScript file in a <a href="proxy.php?url=https://docs.netlify.com/functions/configure-and-deploy/#configure-the-functions-folder">configured functions directory</a>. The function endpoint is determined by its filename or the name of its dedicated parent directory. It exports a <code>handler</code> that receives an event object similar to what you would receive from AWS API Gateway.</p>
<h3>
Serverless Framework
</h3>
<p>The <a href="proxy.php?url=https://www.serverless.com/">Serverless Framework</a> is an open source framework for building applications on AWS Lambda. It provides a CLI for developing and deploying <a href="proxy.php?url=https://www.serverless.com/framework/docs/providers/aws/guide/intro/">AWS Lambda</a> functions, along with the AWS infrastructure resources they require.</p>
<p>The resources and functions are defined in a file called <code>serverless.yml</code> which includes:</p>
<ul>
<li>The <code>provider</code> for the Node <code>runtime</code> and AWS <code>region</code>
</li>
<li>The <code>handler</code> and <code>events</code> for your <code>functions</code>
</li>
</ul>
<p>Once the project is defined in code it can be deployed with the <code>sls deploy</code> command. This command creates a CloudFormation stack defining any necessary resources such as API gateways or S3 buckets.</p>
<h3>
Amplify
</h3>
<p><a href="proxy.php?url=https://aws.amazon.com/amplify/">AWS Amplify</a> is a set of tools and services to help frontend web and mobile developers build fullstack applications with AWS infrastructure. It includes a <a href="proxy.php?url=https://docs.amplify.aws/cli">CLI</a> for creating and deploying <a href="proxy.php?url=https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/stacks.html">CloudFormation stacks</a> along with a <a href="proxy.php?url=https://console.aws.amazon.com/amplify/home">Console</a> and <a href="proxy.php?url=https://sandbox.amplifyapp.com/getting-started">Admin UI</a> for managing frontend web apps, backend environments, CI/CD, and user data.</p>
<p>Amplify provides open source libraries in various languages including:</p>
<ul>
<li><a href="proxy.php?url=https://github.com/aws-amplify/amplify-js">JavaScript</a></li>
<li><a href="proxy.php?url=https://github.com/aws-amplify/aws-sdk-ios">iOS</a></li>
<li><a href="proxy.php?url=https://github.com/aws-amplify/aws-sdk-android">Android</a></li>
<li><a href="proxy.php?url=https://github.com/aws-amplify/amplify-flutter">Flutter</a></li>
</ul>
<p>The <code>amplify init</code> command creates a boilerplate project that is setup for generating CloudFormation templates. <code>amplify add api</code> configures a Lambda handler and API gateway to serve the function. <code>amplify push</code> uploads the stack templates to an S3 bucket and calls the CloudFormation API to create or update resources in the cloud.</p>
<h2>
Serve Apollo Server Locally
</h2>
<p><a href="proxy.php?url=https://github.com/apollographql/apollo-server">Apollo Server</a> is an open-source, spec-compliant GraphQL server. Apollo Server Core implements the core logic of Apollo Server.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>apollo-server
<span class="nb">cd </span>apollo-server
yarn init <span class="nt">-y</span>
yarn add apollo-server graphql
<span class="nb">touch </span>index.js
<span class="nb">echo</span> <span class="s1">'node_modules\n.DS_Store'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<h3>
ApolloServer
</h3>
<p><code>apollo-server</code> exports a base version of the <code>ApolloServer</code> constructor which requires two parameters, <code>typeDefs</code> for a schema definition and <code>resolvers</code> for a set of resolvers. The <code>listen</code> method is used to launch the server.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">ApolloServer</span><span class="p">,</span> <span class="nx">gql</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">apollo-server</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`type Query { hello: String }`</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">Query</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">"</span><span class="s2">Hello from Apollo on Localhost!</span><span class="dl">"</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApolloServer</span><span class="p">({</span> <span class="nx">typeDefs</span><span class="p">,</span> <span class="nx">resolvers</span> <span class="p">})</span>
<span class="nx">server</span><span class="p">.</span><span class="nf">listen</span><span class="p">().</span><span class="nf">then</span><span class="p">(</span>
<span class="p">({</span> <span class="nx">url</span> <span class="p">})</span> <span class="o">=></span> <span class="p">{</span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="s2">`Server ready at </span><span class="p">${</span><span class="nx">url</span><span class="p">}</span><span class="s2">`</span><span class="p">)</span> <span class="p">}</span>
<span class="p">)</span>
</code></pre>
</div>
<p><code>ApolloServer</code> is usually imported either from a batteries-included <code>apollo-server</code> package or from integration packages like <code>apollo-server-lambda</code>.</p>
<h3>
Run test queries on Apollo Server Locally
</h3>
<p>Start the server with <code>node index.js</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>node index.js
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:4000/">localhost:4000</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5a31nqppryo82yuo9f6.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5a31nqppryo82yuo9f6.png" alt="01-apollo-server-localhost-4000" width="800" height="214"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> http://localhost:4000/ <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
Apollo Server Final Project Structure
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>├── .gitignore
├── index.js
└── package.json
</code></pre>
</div>
<h2>
Deploy Apollo Server Lambda with Netlify
</h2>
<p>In Apollo Server 3, <code>apollo-server-lambda</code> is implemented as a wrapper around <code>apollo-server-express</code> and uses <a href="proxy.php?url=https://www.npmjs.com/package/@vendia/serverless-express"><code>serverless-express</code></a> to parse AWS Lambda events into Express requests. This will cause problems with Netlify Functions, so instead we will install v2.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir</span> <span class="nt">-p</span> apollo-server-netlify/netlify/functions
<span class="nb">cd </span>apollo-server-netlify
yarn init <span class="nt">-y</span>
yarn add apollo-server-lambda@2 graphql
<span class="nb">touch </span>netlify/functions/index.js
<span class="nb">echo</span> <span class="s1">'node_modules\n.DS_Store\n.netlify'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<h3>
ApolloServer
</h3>
<p>Each JavaScript file to be deployed as a synchronous serverless Lambda function must export a <code>handler</code> method. Netlify provides the <code>event</code> and <code>context</code> parameters when the serverless function is invoked.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// netlify/functions/index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">ApolloServer</span><span class="p">,</span> <span class="nx">gql</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">apollo-server-lambda</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`type Query { hello: String }`</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">Query</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">"</span><span class="s2">Hello from Apollo on Netlify!</span><span class="dl">"</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApolloServer</span><span class="p">({</span> <span class="nx">typeDefs</span><span class="p">,</span> <span class="nx">resolvers</span> <span class="p">})</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="nx">server</span><span class="p">.</span><span class="nf">createHandler</span><span class="p">()</span>
</code></pre>
</div>
<h3>
Run test queries on Apollo Server Lambda Netlify Locally
</h3>
<p>Start the development server with <code>netlify dev</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>netlify dev
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:8888/.netlify/functions/index/">localhost:8888/.netlify/functions/index</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frf93qml70lz9u9aqox8v.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frf93qml70lz9u9aqox8v.png" alt="02-apollo-server-lambda-netlify-localhost-8888" width="800" height="235"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> http://localhost:8888/.netlify/functions/index <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
Create GitHub Repo and Connect to Netlify
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"apollo server lambda netlify"</span>
gh repo create ajcwebdev-apollo-server-netlify
git push <span class="nt">-u</span> origin main
netlify deploy <span class="nt">--prod</span>
</code></pre>
</div>
<h3>
Run test queries on Apollo Server Lambda Netlify
</h3>
<p>Open <a href="proxy.php?url=https://ajcwebdev-apollo-server-netlify.netlify.app/.netlify/functions/index">ajcwebdev-apollo-server-netlify.netlify.app/.netlify/functions/index</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafv0xnk96bodgywk1fcf.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafv0xnk96bodgywk1fcf.png" alt="03-apollo-server-lambda-netlify-function" width="800" height="235"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> https://ajcwebdev-apollo-server-netlify.netlify.app/.netlify/functions/index <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
Apollo Server Lambda Netlify Final Project Structure
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>├── .gitignore
├── netlify
│ └── functions
│ └── index.js
└── package.json
</code></pre>
</div>
<h2>
Deploy Apollo Server Lambda with Serverless Framework
</h2>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>apollo-server-serverless
<span class="nb">cd </span>apollo-server-serverless
yarn init <span class="nt">-y</span>
yarn add apollo-server-lambda graphql
<span class="nb">touch </span>index.js serverless.yml
<span class="nb">echo</span> <span class="s1">'node_modules\n.DS_Store\n.serverless'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<h3>
ApolloServer
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">ApolloServer</span><span class="p">,</span> <span class="nx">gql</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">apollo-server-lambda</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`type Query { hello: String }`</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">Query</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">'</span><span class="s1">Hello from Apollo on Serverless Framework!</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApolloServer</span><span class="p">({</span> <span class="nx">typeDefs</span><span class="p">,</span> <span class="nx">resolvers</span> <span class="p">})</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="nx">server</span><span class="p">.</span><span class="nf">createHandler</span><span class="p">()</span>
</code></pre>
</div>
<h3>
Serverless Framework Configuration
</h3>
<p>The handler is formatted as <code><FILENAME>.<HANDLER></code>. I have included <code>us-west-1</code> for the region, feel free to enter the AWS region closest to your current location.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight yaml"><code><span class="c1"># serverless.yml</span>
<span class="na">service</span><span class="pi">:</span> <span class="s">ajcwebdev-apollo-server</span>
<span class="na">provider</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">aws</span>
<span class="na">runtime</span><span class="pi">:</span> <span class="s">nodejs14.x</span>
<span class="na">region</span><span class="pi">:</span> <span class="s">us-west-1</span>
<span class="na">functions</span><span class="pi">:</span>
<span class="na">graphql</span><span class="pi">:</span>
<span class="na">handler</span><span class="pi">:</span> <span class="s">index.handler</span>
<span class="na">events</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">http</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
<span class="na">method</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">cors</span><span class="pi">:</span> <span class="kc">true</span>
<span class="pi">-</span> <span class="na">http</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
<span class="na">method</span><span class="pi">:</span> <span class="s">get</span>
<span class="na">cors</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre>
</div>
<h3>
Upload to AWS with sls deploy
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>sls deploy
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight yaml"><code><span class="na">service</span><span class="pi">:</span> <span class="s">ajcwebdev-apollo-server</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">dev</span>
<span class="na">region</span><span class="pi">:</span> <span class="s">us-west-1</span>
<span class="na">stack</span><span class="pi">:</span> <span class="s">ajcwebdev-apollo-server-dev</span>
<span class="na">resources</span><span class="pi">:</span> <span class="m">12</span>
<span class="na">api keys</span><span class="pi">:</span>
<span class="s">None</span>
<span class="na">endpoints</span><span class="pi">:</span>
<span class="s">POST - https://q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/</span>
<span class="s">GET - https://q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/</span>
<span class="na">functions</span><span class="pi">:</span>
<span class="na">graphql</span><span class="pi">:</span> <span class="s">ajcwebdev-apollo-server-dev-graphql</span>
<span class="na">layers</span><span class="pi">:</span>
<span class="s">None</span>
</code></pre>
</div>
<p>You can see all of your AWS resources in the various AWS consoles.</p>
<p>CloudFormation</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F524j8hu5xotdnqxsu3pg.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F524j8hu5xotdnqxsu3pg.png" alt="04-apollo-server-serverless-cloudformation" width="800" height="783"></a></p>
<p>Lambda Function</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegy9rrqxubnhmj4nx1qy.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegy9rrqxubnhmj4nx1qy.png" alt="05-apollo-server-serverless-lambda-function" width="800" height="343"></a></p>
<p>Lambda Application</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjf6mgdmmljy51snrsycu.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjf6mgdmmljy51snrsycu.png" alt="06-apollo-server-serverless-lambda-application" width="800" height="406"></a></p>
<p>API Gateway</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1ujovq6la2xr820tw95.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm1ujovq6la2xr820tw95.png" alt="07-apollo-server-serverless-api-gateway" width="800" height="378"></a></p>
<h3>
Run test queries on Apollo Server Lambda Serverless
</h3>
<p>Open <a href="proxy.php?url=https://q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/">q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqonhmytwfiblu4ykwcm.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgqonhmytwfiblu4ykwcm.png" alt="08-apollo-server-lambda-serverless-framework" width="800" height="181"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> https://q6b9hu1h71.execute-api.us-west-1.amazonaws.com/dev/ <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
Apollo Server Lambda Serverless Final Project Structure
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>├── .gitignore
├── index.js
├── package.json
└── serverless.yml
</code></pre>
</div>
<h2>
Deploy Apollo Server Lambda with Amplify
</h2>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>apollo-server-amplify
<span class="nb">cd </span>apollo-server-amplify
amplify init
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>? Enter a name for the project ajcwebdevapollo
The following configuration will be applied:
Project information
| Name: ajcwebdevapollo
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: none
| Source Directory Path: src
| Distribution Directory Path: dist
| Build Command: npm run-script build
| Start Command: npm run-script start
? Initialize the project with the above configuration? Yes
Using default provider awscloudformation
? Select the authentication method you want to use: AWS profile
For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
? Please choose the profile you want to use default
</code></pre>
</div>
<p>CloudFormation</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7cbamaoz75ubycem7w0.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff7cbamaoz75ubycem7w0.png" alt="09-apollo-server-amplify-cloudformation" width="800" height="784"></a></p>
<h3>
Create backend with amplify add api
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>amplify add api
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: ajcwebdevapollo
? Provide a path (e.g., /book/{isbn}): /graphql
? Choose a Lambda source Create a new Lambda function
? Provide an AWS Lambda function name: apolloserver
? Choose the runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World
? Do you want to access other resources created in this project from your Lambda function? N
? Do you want to edit the local lambda function now? N
? Restrict API access: N
? Do you want to add another path? N
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">cd </span>amplify/backend/function/apolloserver/src
yarn add apollo-server-lambda graphql
<span class="nb">cd</span> ../../../../../
</code></pre>
</div>
<h3>
ApolloServer
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// amplify/backend/function/apollofunction/src/index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">ApolloServer</span><span class="p">,</span> <span class="nx">gql</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">apollo-server-lambda</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="nx">gql</span><span class="s2">`type Query { hello: String }`</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">Query</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">'</span><span class="s1">Hello from Apollo on Amplify!</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ApolloServer</span><span class="p">({</span>
<span class="nx">typeDefs</span><span class="p">,</span>
<span class="nx">resolvers</span><span class="p">,</span>
<span class="na">context</span><span class="p">:</span> <span class="p">({</span> <span class="nx">event</span><span class="p">,</span> <span class="nx">context</span> <span class="p">})</span> <span class="o">=></span> <span class="p">({</span>
<span class="na">headers</span><span class="p">:</span> <span class="nx">event</span><span class="p">.</span><span class="nx">headers</span><span class="p">,</span>
<span class="na">functionName</span><span class="p">:</span> <span class="nx">context</span><span class="p">.</span><span class="nx">functionName</span><span class="p">,</span>
<span class="nx">event</span><span class="p">,</span>
<span class="nx">context</span><span class="p">,</span>
<span class="p">}),</span>
<span class="p">})</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="nx">server</span><span class="p">.</span><span class="nf">createHandler</span><span class="p">()</span>
</code></pre>
</div>
<h3>
Upload to AWS with amplify push
</h3>
<p>Deploy the function containing the GraphQL API.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>amplify push
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>✔ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
┌──────────┬─────────────────┬───────────┬───────────────────┐
│ Category │ Resource name │ Operation │ Provider plugin │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Function │ apolloserver │ Create │ awscloudformation │
├──────────┼─────────────────┼───────────┼───────────────────┤
│ Api │ ajcwebdevapollo │ Create │ awscloudformation │
└──────────┴─────────────────┴───────────┴───────────────────┘
? Are you sure you want to continue? Yes
All resources are updated in the cloud
REST API endpoint: https://kl21tioy61.execute-api.us-east-1.amazonaws.com/dev
</code></pre>
</div>
<p>Lambda Function</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncvzptx1kapewdtfrd35.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncvzptx1kapewdtfrd35.png" alt="10-apollo-server-amplify-lambda-function" width="800" height="380"></a></p>
<p>Lambda Application</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr89y1gaacss2iqj17l5z.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr89y1gaacss2iqj17l5z.png" alt="11-apollo-server-amplify-lambda-application" width="800" height="392"></a></p>
<p>API Gateway</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpeorlldj908d8rr3y79g.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpeorlldj908d8rr3y79g.png" alt="12-apollo-server-amplify-api-gateway" width="800" height="235"></a></p>
<h3>
Run test queries on Apollo Server Lambda Amplify
</h3>
<p>Open <a href="proxy.php?url=https://kl21tioy61.execute-api.us-east-1.amazonaws.com/dev/graphql">kl21tioy61.execute-api.us-east-1.amazonaws.com/dev/graphql</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnntdr11bx4lv9mryswdz.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnntdr11bx4lv9mryswdz.png" alt="13-apollo-server-lambda-amplify" width="800" height="232"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> https://kl21tioy61.execute-api.us-east-1.amazonaws.com/dev/graphql <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
Apollo Server Lambda Amplify Final Project Structure
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>├── .gitignore
└── amplify
└── backend
├── api
│ └── ajcwebdevapollo
│ ├── ajcwebdevapollo-cloudformation-template.json
│ ├── api-params.json
│ └── parameters.json
└── function
└── apolloserver
├── apolloserver-cloudformation-template.json
├── function-parameters.json
└── src
├── event.json
├── index.js
└── package.json
</code></pre>
</div>
<h2>
Serve GraphQL Yoga Locally
</h2>
<p><a href="proxy.php?url=https://github.com/dotansimha/graphql-yoga">GraphQL Yoga</a> is a fully-featured GraphQL Server with a focus on easy setup and performance. Originally created by Prisma, it is now mained by Dotan and the Guild. It includes features aimed at providing a great developer experience including file uploading, GraphQL subscriptions support with WebSockets, and TypeScript typing.</p>
<p><code>graphql-yoga</code> is built on other packages that provide functionality required for building a GraphQL server such as web server frameworks like <a href="proxy.php?url=https://github.com/expressjs/express"><code>express</code></a> and <a href="proxy.php?url=https://github.com/apollographql/apollo-server"><code>apollo-server</code></a>, GraphQL subscriptions with <a href="proxy.php?url=https://github.com/apollographql/graphql-subscriptions"><code>graphql-subscriptions</code></a> and <a href="proxy.php?url=https://github.com/apollographql/subscriptions-transport-ws"><code>subscriptions-transport-ws</code></a>, GraphQL engine & schema helpers including <a href="proxy.php?url=https://github.com/graphql/graphql-js"><code>graphql.js</code></a> and <a href="proxy.php?url=https://github.com/apollographql/graphql-tools"><code>graphql-tools</code></a>, and an interactive GraphQL IDE with <a href="proxy.php?url=https://github.com/graphcool/graphql-playground"><code>graphql-playground</code></a>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>graphql-yoga
<span class="nb">cd </span>graphql-yoga
yarn init <span class="nt">-y</span>
yarn add graphql-yoga
<span class="nb">touch </span>index.js
<span class="nb">echo</span> <span class="s1">'node_modules\n.DS_Store'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<h3>
GraphQLServer
</h3>
<p><code>GraphQLServer</code> is a <code>constructor</code> with a <code>props</code> argument that accepts a wide array of fields. We will only be using a handful including:</p>
<ul>
<li>
<code>typeDefs</code> - Contains GraphQL type definitions in SDL or file path to type definitions</li>
<li>
<code>resolvers</code>- Contains resolvers for the fields specified in <code>typeDefs</code>
</li>
<li>
<code>schema</code> - An instance of <a href="proxy.php?url=http://graphql.org/graphql-js/type/#graphqlschema"><code>GraphQLSchema</code></a>
</li>
</ul>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">GraphQLServer</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">graphql-yoga</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="s2">`type Query { hello: String }`</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">Query</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">'</span><span class="s1">Hello from GraphQL Yoga on Localhost!</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">server</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GraphQLServer</span><span class="p">({</span> <span class="nx">typeDefs</span><span class="p">,</span> <span class="nx">resolvers</span> <span class="p">})</span>
<span class="nx">server</span><span class="p">.</span><span class="nf">start</span><span class="p">(</span>
<span class="p">()</span> <span class="o">=></span> <span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">Server is running on localhost:4000</span><span class="dl">'</span><span class="p">)</span>
<span class="p">)</span>
</code></pre>
</div>
<p>If you provide <code>typeDefs</code> and <code>resolvers</code> but omit the <a href="proxy.php?url=https://blog.graph.cool/graphql-server-basics-the-schema-ac5e2950214e"><code>schema</code></a>, <code>graphql-yoga</code> will construct the <code>GraphQLSchema</code> instance using <code>makeExecutableSchema</code> from <a href="proxy.php?url=https://github.com/apollographql/graphql-tools"><code>graphql-tools</code></a>.</p>
<h3>
Run test queries on GraphQL Yoga Locally
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>node index.js
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:4000">http://localhost:4000</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0hsyi1kz80saoiftem4.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi0hsyi1kz80saoiftem4.png" alt="14-graphql-yoga-localhost-4000" width="800" height="343"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> http://localhost:4000/ <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
GraphQL Yoga Final Project Structure
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>├── .gitignore
├── index.js
└── package.json
</code></pre>
</div>
<h2>
Deploy GraphQL Yoga with Netlify
</h2>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir</span> <span class="nt">-p</span> graphql-yoga-netlify/netlify/functions
<span class="nb">cd </span>graphql-yoga-netlify
yarn init <span class="nt">-y</span>
yarn add graphql-yoga
<span class="nb">touch </span>netlify/functions/index.js
<span class="nb">echo</span> <span class="s1">'node_modules\n.DS_Store\n.netlify'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<h3>
GraphQLServerLambda
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// netlify/functions/index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">GraphQLServerLambda</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">graphql-yoga</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="s2">`type Query { hello: String }`</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">Query</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">'</span><span class="s1">Hello from GraphQL Yoga on Netlify!</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">lambda</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GraphQLServerLambda</span><span class="p">({</span> <span class="nx">typeDefs</span><span class="p">,</span> <span class="nx">resolvers</span> <span class="p">})</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="nx">lambda</span><span class="p">.</span><span class="nx">handler</span>
</code></pre>
</div>
<h3>
Run test queries on GraphQL Yoga Netlify Locally
</h3>
<p>Start the development server with <code>netlify dev</code>.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>netlify dev
</code></pre>
</div>
<p>Open <a href="proxy.php?url=http://localhost:8888/.netlify/functions/index/">localhost:8888/.netlify/functions/index</a> and run the <code>hello</code> query.</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs6jl2mcxhoi4fvy6hx0.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzs6jl2mcxhoi4fvy6hx0.png" alt="15-graphql-yoga-netlify-localhost-8888" width="800" height="280"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> http://localhost:8888/.netlify/functions/index <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
Create GitHub Repo and Connect GraphQL Yoga to Netlify
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>git init
git add <span class="nb">.</span>
git commit <span class="nt">-m</span> <span class="s2">"graphql yoga netlify"</span>
gh repo create ajcwebdev-graphql-yoga-netlify
git push <span class="nt">-u</span> origin main
netlify deploy <span class="nt">--prod</span>
</code></pre>
</div>
<h3>
Run test queries on GraphQL Yoga Netlify
</h3>
<p>Open <a href="proxy.php?url=https://ajcwebdev-graphql-yoga-netlify.netlify.app/.netlify/functions/index">ajcwebdev-graphql-yoga-netlify.netlify.app/.netlify/functions/index</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzd0y3s9cv2zc5kq3mvi4.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzd0y3s9cv2zc5kq3mvi4.png" alt="16-graphql-yoga-netlify-function" width="800" height="324"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> https://ajcwebdev-graphql-yoga-netlify.netlify.app/.netlify/functions/index <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
GraphQL Yoga Netlify Final Project Structure
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>├── .gitignore
├── netlify
│ └── functions
│ └── index.js
└── package.json
</code></pre>
</div>
<h2>
Deploy GraphQL Yoga with Serverless Framework
</h2>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>graphql-yoga-serverless
<span class="nb">cd </span>graphql-yoga-serverless
yarn init <span class="nt">-y</span>
yarn add graphql-yoga
<span class="nb">touch </span>index.js serverless.yml
<span class="nb">echo</span> <span class="s1">'node_modules\n.DS_Store\n.serverless'</span> <span class="o">></span> .gitignore
</code></pre>
</div>
<h3>
GraphQLServerLambda
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">GraphQLServerLambda</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">graphql-yoga</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="s2">`type Query { hello: String }`</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">Query</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">'</span><span class="s1">Hello from GraphQL Yoga on Serverless Framework!</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">lambda</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GraphQLServerLambda</span><span class="p">({</span> <span class="nx">typeDefs</span><span class="p">,</span> <span class="nx">resolvers</span> <span class="p">})</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="nx">lambda</span><span class="p">.</span><span class="nx">graphqlHandler</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">playground</span> <span class="o">=</span> <span class="nx">lambda</span><span class="p">.</span><span class="nx">playgroundHandler</span>
</code></pre>
</div>
<h3>
Serverless Framework Configuration
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight yaml"><code><span class="c1"># serverless.yml</span>
<span class="na">service</span><span class="pi">:</span> <span class="s">yoga-example</span>
<span class="na">provider</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">aws</span>
<span class="na">runtime</span><span class="pi">:</span> <span class="s">nodejs14.x</span>
<span class="na">region</span><span class="pi">:</span> <span class="s">us-west-1</span>
<span class="na">functions</span><span class="pi">:</span>
<span class="na">graphql</span><span class="pi">:</span>
<span class="na">handler</span><span class="pi">:</span> <span class="s">index.handler</span>
<span class="na">events</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">http</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
<span class="na">method</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">cors</span><span class="pi">:</span> <span class="kc">true</span>
<span class="na">playground</span><span class="pi">:</span>
<span class="na">handler</span><span class="pi">:</span> <span class="s">index.playground</span>
<span class="na">events</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">http</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">/</span>
<span class="na">method</span><span class="pi">:</span> <span class="s">get</span>
<span class="na">cors</span><span class="pi">:</span> <span class="kc">true</span>
</code></pre>
</div>
<h3>
Upload to AWS with sls deploy
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>sls deploy
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight yaml"><code><span class="na">service</span><span class="pi">:</span> <span class="s">yoga-example</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">dev</span>
<span class="na">region</span><span class="pi">:</span> <span class="s">us-west-1</span>
<span class="na">stack</span><span class="pi">:</span> <span class="s">yoga-example-dev</span>
<span class="na">resources</span><span class="pi">:</span> <span class="m">16</span>
<span class="na">api keys</span><span class="pi">:</span>
<span class="s">None</span>
<span class="na">endpoints</span><span class="pi">:</span>
<span class="s">POST - https://vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/</span>
<span class="s">GET - https://vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/</span>
<span class="na">functions</span><span class="pi">:</span>
<span class="na">graphql</span><span class="pi">:</span> <span class="s">yoga-example-dev-graphql</span>
<span class="na">playground</span><span class="pi">:</span> <span class="s">yoga-example-dev-playground</span>
<span class="na">layers</span><span class="pi">:</span>
<span class="s">None</span>
</code></pre>
</div>
<h3>
Run test queries on GraphQL Yoga Serverless
</h3>
<p>Open <a href="proxy.php?url=https://vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/">vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr11pxpmah3ny3s7vajns.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr11pxpmah3ny3s7vajns.png" alt="17-graphql-yoga-serverless-framework" width="800" height="266"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> https://vptcz65b06.execute-api.us-west-1.amazonaws.com/dev/ <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
GraphQL Yoga Serverless Final Project Structure
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>├── .gitignore
├── index.js
├── package.json
└── serverless.yml
</code></pre>
</div>
<h2>
Deploy GraphQL Yoga with Amplify
</h2>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">mkdir </span>graphql-yoga-amplify
<span class="nb">cd </span>graphql-yoga-amplify
amplify init
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>? Enter a name for the project ajcwebdevgraphqlyoga
The following configuration will be applied:
Project information
| Name: ajcwebdevgraphqlyoga
| Environment: dev
| Default editor: Visual Studio Code
| App type: javascript
| Javascript framework: none
| Source Directory Path: src
| Distribution Directory Path: dist
| Build Command: npm run-script build
| Start Command: npm run-script start
? Initialize the project with the above configuration? Yes
Using default provider awscloudformation
? Select the authentication method you want to use: AWS profile
For more information on AWS Profiles, see:
https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html
? Please choose the profile you want to use default
</code></pre>
</div>
<h3>
Create backend with amplify add api
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>amplify add api
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>? Please select from one of the below mentioned services: REST
? Provide a friendly name for your resource to be used as a label for this category in the project: ajcwebdevyoga
? Provide a path (e.g., /book/{isbn}): /graphql
? Choose a Lambda source: Create a new Lambda function
? Provide the AWS Lambda function name: graphqlyoga
? Choose the function runtime that you want to use: NodeJS
? Choose the function template that you want to use: Hello World
? Do you want to access other resources created in this project from your Lambda function? N
? Do you want to edit the local lambda function now? N
? Restrict API access: N
? Do you want to add another path? N
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nb">cd </span>amplify/backend/function/graphqlyoga/src
yarn add graphql-yoga
<span class="nb">cd</span> ../../../../../
</code></pre>
</div>
<h3>
GraphQLServerLambda
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight javascript"><code><span class="c1">// amplify/backend/function/graphqlyoga/src/index.js</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">GraphQLServerLambda</span> <span class="p">}</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">graphql-yoga</span><span class="dl">'</span><span class="p">)</span>
<span class="kd">const</span> <span class="nx">typeDefs</span> <span class="o">=</span> <span class="s2">`type Query { hello: String }`</span>
<span class="kd">const</span> <span class="nx">resolvers</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">Query</span><span class="p">:</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="p">()</span> <span class="o">=></span> <span class="dl">'</span><span class="s1">Hello from GraphQL Yoga on Amplify!</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">}</span>
<span class="kd">const</span> <span class="nx">lambda</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">GraphQLServerLambda</span><span class="p">({</span> <span class="nx">typeDefs</span><span class="p">,</span> <span class="nx">resolvers</span> <span class="p">})</span>
<span class="nx">exports</span><span class="p">.</span><span class="nx">handler</span> <span class="o">=</span> <span class="nx">lambda</span><span class="p">.</span><span class="nx">handler</span>
</code></pre>
</div>
<h3>
Upload to AWS with amplify push
</h3>
<p>Deploy the function containing the GraphQL API.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>amplify push
</code></pre>
</div>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>✔ Successfully pulled backend environment dev from the cloud.
Current Environment: dev
┌──────────┬───────────────┬───────────┬───────────────────┐
│ Category │ Resource name │ Operation │ Provider plugin │
├──────────┼───────────────┼───────────┼───────────────────┤
│ Function │ graphqlyoga │ Create │ awscloudformation │
├──────────┼───────────────┼───────────┼───────────────────┤
│ Api │ ajcwebdevyoga │ Create │ awscloudformation │
└──────────┴───────────────┴───────────┴───────────────────┘
? Are you sure you want to continue? Yes
All resources are updated in the cloud
REST API endpoint: https://zmvy0jw9dc.execute-api.us-east-1.amazonaws.com/dev
</code></pre>
</div>
<p>CloudFormation</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yx0g1wtx26wnclkk39a.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yx0g1wtx26wnclkk39a.png" alt="18-graphql-yoga-amplify-cloudformation" width="800" height="745"></a></p>
<p>Lambda Function</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbtdj4u6t6elixltt8g5.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsbtdj4u6t6elixltt8g5.png" alt="19-graphql-yoga-amplify-lambda-function" width="800" height="382"></a></p>
<p>Lambda Application</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt8jw4vbdtdstpddrv5h.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzt8jw4vbdtdstpddrv5h.png" alt="20-graphql-yoga-amplify-lambda-application" width="800" height="362"></a></p>
<p>API Gateway</p>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfbnbk98xs26daea38cg.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftfbnbk98xs26daea38cg.png" alt="21-graphql-yoga-amplify-api-gateway" width="800" height="237"></a></p>
<h3>
Run test queries on GraphQL Yoga Amplify
</h3>
<p>Open <a href="proxy.php?url=https://zmvy0jw9dc.execute-api.us-east-1.amazonaws.com/dev/graphql">zmvy0jw9dc.execute-api.us-east-1.amazonaws.com/dev/graphql</a> and run the <code>hello</code> query.<br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight graphql"><code><span class="k">query</span><span class="w"> </span><span class="n">HELLO_QUERY</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hello</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre>
</div>
<p><a href="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzrjaxga93okfg8x4dqk.png" class="article-body-image-wrapper"><img src="proxy.php?url=https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwzrjaxga93okfg8x4dqk.png" alt="22-graphql-yoga-amplify" width="800" height="285"></a><br>
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code>curl <span class="nt">--request</span> POST <span class="se">\</span>
<span class="nt">--url</span> https://zmvy0jw9dc.execute-api.us-east-1.amazonaws.com/dev/graphql <span class="se">\</span>
<span class="nt">--header</span> <span class="s1">'content-type: application/json'</span> <span class="se">\</span>
<span class="nt">--data</span> <span class="s1">'{"query":"{ hello }"}'</span>
</code></pre>
</div>
<h3>
GraphQL Yoga Amplify Final Project Structure
</h3>
<div class="highlight js-code-highlight">
<pre class="highlight plaintext"><code>├── .gitignore
└── amplify
└── backend
├── api
│ └── ajcwebdevyoga
│ ├── ajcwebdevyoga-cloudformation-template.json
│ ├── api-params.json
│ └── parameters.json
└── function
└── graphqlyoga
├── amplify.state
├── graphqlyoga-cloudformation-template.json
├── function-parameters.json
└── src
├── event.json
├── index.js
└── package.json
</code></pre>
</div>
<p>You're still here? Wow, didn't think anyone would actually make it to the end.</p>
graphqlserverlesslambdaexpressThe Potential of web3ajcwebdevFri, 01 Oct 2021 22:14:49 +0000
https://dev.to/ajcwebdev/the-potential-of-web3-2cj2
https://dev.to/ajcwebdev/the-potential-of-web3-2cj2<p><em>After the recent influx of questions and attention around Web3, I've decided to republish a blogpost I originally wrote about the topic on February 8, 2018.</em></p>
<h2>
The Year Crypto Went Mainstream
</h2>
<p>2017 was finally the year crypto went mainstream. I’ve seen a lot of musicians transition from semi-indie famous to full blown celebrities, and when I’m trying to gauge how much exposure a musician truly has, my litmus test has always been, “Do my parents know who this is, and do teenagers know who this is?” 2017 is the year that crypto passed that litmus test.</p>
<h3>
Hype vs. Reality
</h3>
<p>Unfortunately the conversation has mostly centered around the speculative price volatility due to trading, rather than the underlying technology. This is a massive red herring. While it’s true that cryptoassets will continue to develop and generate extreme amounts of wealth in the coming years, the true potential for the blockchain is 3-fold:</p>
<ol>
<li><p>Create a sustainable structure for an open source project to be funded and nurtured throughout its development.</p></li>
<li><p>Secure your data from the bottom up by cryptographically hashing every transaction.</p></li>
<li><p>Allow for radical transparency in observing the amount of money moving throughout a system. The transactions may not have identifiable names on them, but every transaction is equally observable to every user of the system. We may never know who Satoshi is but everyone can see his coins.</p></li>
</ol>
<p>It’s almost too good to be true. At the moment when our tech institutions have failed us miserably there is a grassroots movement metastasizing out of the ground to solve all of the internet’s ills. It’s like a thousand Linux projects appeared overnight. But this is only one possible future for blockchains and cryptocurrencies. We collectively decide the future through our focus and attention.</p>
web3blockchaincryptoethereum