<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fatih Aygün</title>
    <description>The latest articles on DEV Community by Fatih Aygün (@cyco130).</description>
    <link>https://dev.to/cyco130</link>
    <image>
      <url>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%2F655067%2F2d3f374a-507b-4b46-b19a-a411a4569e5b.jpeg</url>
      <title>DEV Community: Fatih Aygün</title>
      <link>https://dev.to/cyco130</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cyco130"/>
    <language>en</language>
    <item>
      <title>Styling with UnoCSS</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Wed, 31 Aug 2022 17:35:58 +0000</pubDate>
      <link>https://dev.to/cyco130/styling-with-unocss-284h</link>
      <guid>https://dev.to/cyco130/styling-with-unocss-284h</guid>
      <description>&lt;p&gt;Rakkas &lt;a href="https://rakkasjs.org/guide/styling" rel="noopener noreferrer"&gt;supports&lt;/a&gt; all styling options available to React apps. Since we're building an app with cutting-edge technologies, I think it would be fitting to use some of the latest and greatest styling options. &lt;a href="https://tailwindcss.com" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt; seems to be the coolest kid in town nowadays. But we'll go even further and use &lt;a href="https://github.com/unocss/unocss" rel="noopener noreferrer"&gt;UnoCSS&lt;/a&gt;. It's a &lt;a href="https://antfu.me/posts/reimagine-atomic-css" rel="noopener noreferrer"&gt;reimagining&lt;/a&gt; of atomic CSS with an instant on-demand approach that is &lt;strong&gt;200x faster&lt;/strong&gt; than Tailwind JIT! Let's start by installing the required packages:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; unocss @unocss/reset


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;unocss&lt;/code&gt; is the main package and &lt;code&gt;@unocss/reset&lt;/code&gt; provides a collection of CSS resets, including the Tailwind CSS "Preflight", an opinionated set of base styles for Tailwind.&lt;/p&gt;

&lt;p&gt;Following the UnoCSS readme, we add to our &lt;code&gt;vite.config.ts&lt;/code&gt; file &lt;code&gt;import Unocss from "unocss/vite";&lt;/code&gt; and then add &lt;code&gt;UnoCSS()&lt;/code&gt; the the &lt;code&gt;plugins&lt;/code&gt; array:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;rakkas&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tsconfigPaths&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-tsconfig-paths&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Unocss&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unocss/vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;envDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nf"&gt;tsconfigPaths&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nf"&gt;rakkas&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudflare-workers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="nc"&gt;Unocss&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We also add the following imports at the top of our &lt;code&gt;src/routes/layout.tsx&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@unocss/reset/tailwind.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uno.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;ESLint will complain about not being able to resolve &lt;code&gt;uno.css&lt;/code&gt;. You can shut it up by adding the following to your &lt;code&gt;.eslintrc.cjs&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;import/no-unresolved&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ignore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uno.css&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you spin up the dev server, you'll see that our app is now completely unstyled. UnoCSS allows us to style our app with class names. Its default preset supports many atomic/functional CSS libraries all at once: &lt;code&gt;ml-3&lt;/code&gt; (Tailwind), &lt;code&gt;ms-2&lt;/code&gt; (Bootstrap), &lt;code&gt;ma4&lt;/code&gt; (Tachyons), and &lt;code&gt;mt-10px&lt;/code&gt; (Windi CSS) are all valid.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.ma4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ml-3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin-left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.75rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.ms-2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;margin-inline-start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5rem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.mt-10px&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I will start by removing the &lt;code&gt;&amp;lt;hr /&amp;gt;&lt;/code&gt; element and add some color, padding and a bottom border to our header:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"px-5 py-5 b-b bg-amber"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;px&lt;/code&gt; is shorthand for horizontal padding and &lt;code&gt;py&lt;/code&gt; is shorthand for vertical padding. &lt;code&gt;b-b&lt;/code&gt; is shorthand for a bottom border. &lt;code&gt;bg-amber&lt;/code&gt; is for setting the background color to amber. You can learn more on the &lt;a href="https://uno.antfu.me/" rel="noopener noreferrer"&gt;UnoCSS website&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I'm sure everyone reading this article is better at designing a good-looking website than I am. Here's what I could come up with after half an hour of tinkering:&lt;/p&gt;

&lt;p&gt;&lt;a href="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%2Fedbiwz97c21jswauw500.png" class="article-body-image-wrapper"&gt;&lt;img src="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%2Fedbiwz97c21jswauw500.png" alt="uBlog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It may not be great but I'm happy with the result. I did change the markup as I've seen fit. I also added a &lt;code&gt;Head&lt;/code&gt; component to set the page title in &lt;code&gt;index.page.tsx&lt;/code&gt; (&lt;code&gt;&amp;lt;Head title="uBlog" /&amp;gt;&lt;/code&gt;). You can see the results &lt;a href="https://ublog.rakkasjs.workers.dev/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope you enjoyed this tutorial series and also hope that you find Rakkas as easy to work with as I think it is. You can extend this little app by adding user profile pages, post deletion functionality, pagination, and more. Feel free to say hi on &lt;a href="https://rakkasjs.org/chat" rel="noopener noreferrer"&gt;Rakkas Discord&lt;/a&gt; chat server!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the progress up to this point on &lt;a href="https://github.com/rakkasjs/ublog/tree/article-6" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Using cookies</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Sun, 14 Aug 2022 17:14:00 +0000</pubDate>
      <link>https://dev.to/cyco130/using-cookies-2jeo</link>
      <guid>https://dev.to/cyco130/using-cookies-2jeo</guid>
      <description>&lt;p&gt;In the previous article we managed to sign a user in with GitHub. Now we have to &lt;em&gt;remember&lt;/em&gt; the signed-in user. There was also a &lt;code&gt;state&lt;/code&gt; parameter that we glossed over that was passed back and forth between our server and GitHub to make sure that the sign-in request was indeed initiated by us, and not by a malicious third party. &lt;code&gt;state&lt;/code&gt; is, in effect, a cross-site request forgery prevention token. We'll just generate a random ID and &lt;em&gt;remember&lt;/em&gt; it. Cookies are the most common way to remember something in a web application.&lt;/p&gt;

&lt;p&gt;As we discussed before, Rakkas relies on HatTip for handling HTTP so we will use the &lt;a href="https://github.com/hattipjs/hattip/tree/main/packages/middleware/cookie"&gt;&lt;code&gt;@hattip/cookie&lt;/code&gt;&lt;/a&gt; package to manage cookies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; @hattip/cookie
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we will add the cookie middleware to our &lt;code&gt;entry-hattip.ts&lt;/code&gt;. We'll use the &lt;code&gt;crypto.randomUUID()&lt;/code&gt; function to generate our &lt;code&gt;state&lt;/code&gt; token but &lt;code&gt;crypto&lt;/code&gt; is not globally available in Node. Luckily it is still available in the &lt;code&gt;crypto&lt;/code&gt; package under the name &lt;code&gt;webcrypto&lt;/code&gt; so we can easily polyifll it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRequestHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;cookie&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@hattip/cookie&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ServerSideLocals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KVNamespace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;createRequestHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;beforePages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./kv-mock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                    &lt;span class="c1"&gt;// Polyfill crypto&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;webcrypto&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="nx"&gt;globalThis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;crypto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;webcrypto&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV_POSTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;

                &lt;span class="c1"&gt;// We'll add more stuff here later&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cookie middleware makes things like &lt;code&gt;ctx.cookie&lt;/code&gt; and &lt;code&gt;ctx.setCookie&lt;/code&gt; available in our server-side code. So now we can generate our random state token and put it in a cookie at the spot we marked with "We'll add more stuff here later" comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;randomToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;randomUUID&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;randomToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// To make it immediately available,&lt;/span&gt;
    &lt;span class="c1"&gt;// We'll store it here too.&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;randomToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can use the cookie value instead of our &lt;code&gt;12345&lt;/code&gt; placeholder in &lt;code&gt;src/routes/layout.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and in the login page (&lt;code&gt;src/routes/login.page.tsx&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... rest of the code&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now if you visit our main page and click "Sign in with GitHub", the whole sign-in routine should still work, but this time with a proper random &lt;code&gt;state&lt;/code&gt; token instead of the placeholder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remembering the signed-in user
&lt;/h2&gt;

&lt;p&gt;We can use another cookie to store the GitHub access token. The only thing our login page has to do is to get the token and store it in a cookie. Then we can simply redirect to the main page again. Rakkas offers several ways to redirect but, amazingly, &lt;a href="https://stackoverflow.com/questions/4694089/sending-browser-cookies-during-a-302-redirect"&gt;some browsers still have problems setting cookies on redirections&lt;/a&gt;. So we will use HTML &lt;a href="https://en.wikipedia.org/wiki/Meta_refresh"&gt;meta refresh&lt;/a&gt; for our redirection.&lt;/p&gt;

&lt;p&gt;To be able to set a cookie from a page, we export a &lt;a href="https://rakkasjs.org/guide/status-and-headers"&gt;&lt;code&gt;headers&lt;/code&gt; function&lt;/a&gt;. So we will have to refactor our code a little. This is how our &lt;code&gt;login.page.tsx&lt;/code&gt; gonna look like with this implemented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HeadersFunction&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LoginPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Error: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Redirect immediately */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;meta&lt;/span&gt; &lt;span class="na"&gt;httpEquiv&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"refresh"&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"0; url=/"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Head&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Redirecting...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;HeadersFunction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/login/oauth/access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s2"&gt;`?client_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s2"&gt;`&amp;amp;client_secret=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                &lt;span class="s2"&gt;`&amp;amp;code=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;httpOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;secure&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PROD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;sameSite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;strict&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;maxAge&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// We won't be setting any headers,&lt;/span&gt;
                &lt;span class="c1"&gt;// setCookie will do it for us,&lt;/span&gt;
                &lt;span class="c1"&gt;// so an empty object is fine.&lt;/span&gt;
            &lt;span class="p"&gt;};&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Login failed for some reason&lt;/span&gt;
    &lt;span class="c1"&gt;// We'll redirect to set the `error` parameter&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`/login?error=Login%20failed`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when we sign in, we're redirected to the main page and the GitHub access token is stored in a cookie. We can now use the token to fetch the user's profile from GitHub on every request in &lt;code&gt;entry-hattip.ts&lt;/code&gt; and make it available in &lt;code&gt;ctx.locals.user&lt;/code&gt;. First, let's define our types:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;GitHubUser&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Just the bits we need&lt;/span&gt;
    &lt;span class="nl"&gt;login&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;avatar_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ServerSideLocals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KVNamespace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nl"&gt;user&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;GitHubUser&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then put the user's profile in &lt;code&gt;ctx.locals.user&lt;/code&gt; (right after the &lt;code&gt;state&lt;/code&gt; cookie handling code):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GitHubUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.github.com/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can read this data in our main layout to show the login status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LayoutProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MainLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;LayoutProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;uBlog&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;avatar_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                            &lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
                            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;
                            &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
                                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/login/oauth/authorize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="s2"&gt;`?client_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                                &lt;span class="s2"&gt;`&amp;amp;state=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
                            &lt;span class="si"&gt;}&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                            Sign in with GitGub
                        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hr&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yes, yes, ugly. We'll get there. Let's update our create form action handler in &lt;code&gt;index.page.tsx&lt;/code&gt; to set the &lt;code&gt;author&lt;/code&gt; metadata in the created post. We should also disallow creating posts if the user is not logged in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You must be signed in to post.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Retrieve the form data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Do some validation&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content is required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// It could be a file upload!&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content must be a string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content must be less than 280 characters&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Echo back the content to refill the form&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// We don't have login/signup yet,&lt;/span&gt;
            &lt;span class="c1"&gt;// so we'll just make up a user name&lt;/span&gt;
            &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cool, we can now tweet under our own user name!&lt;/p&gt;

&lt;p&gt;There's no point in showing the create post form if the user is not logged in, since we're not gonna allow it anyway. Let's update our page component to handle that too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Posts&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown author&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
                            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;postedAt&lt;/span&gt;
                                    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                                    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hr&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt;
                            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt;
                            &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                            &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

                    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sign out
&lt;/h2&gt;

&lt;p&gt;We need one last feature: the ability to sign out. We will add a "sign out" button that will post to a &lt;code&gt;/logout&lt;/code&gt; API route which sign the user out by deleting the access token cookie. The button (and the form) will look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/logout"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Sign out&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll add an API route to handler the action. Rakkas &lt;a href="https://rakkasjs.org/guide/api-routes"&gt;API routes&lt;/a&gt; are modules named &lt;code&gt;&amp;lt;path&amp;gt;.api.ts&lt;/code&gt; (or &lt;code&gt;.js&lt;/code&gt;). The export request handling functions which have the same name as the HTTP method they handle, but in lowercase. For example, the &lt;code&gt;POST&lt;/code&gt; handler will be named &lt;code&gt;post&lt;/code&gt;. &lt;code&gt;DELETE&lt;/code&gt; handlers, however, are named &lt;code&gt;del&lt;/code&gt; because &lt;code&gt;delete&lt;/code&gt; is a reserved word in JavaScript. According to this, we're supposed to name our logout route &lt;code&gt;src/routes/logout.api.ts&lt;/code&gt; and it will look simply like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestContext&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestContext&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleteCookie&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;Location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now we will be able to sign out!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying
&lt;/h2&gt;

&lt;p&gt;Now that we've added all the features we need, we can deploy our application. We'll test locally with Miniflare first but there is one more thing to take care of: GitHub API requires a user agent for all requests. It was working fine so far, because Rakkas uses &lt;code&gt;node-fetch&lt;/code&gt; to make requests and &lt;code&gt;node-fetch&lt;/code&gt; automatically sets the user agent. It's not the case for Miniflare or Cloudflare Workers. So we'll have to set it ourselves in &lt;code&gt;entry-hattip.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GitHubUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.github.com/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;// Put your own GitHub name here&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User-Agent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uBlog by cyco130&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the same header to the request in &lt;code&gt;login.page.tsx&lt;/code&gt;'s &lt;code&gt;headers&lt;/code&gt; function. Now we're set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build &lt;span class="c"&gt;# Build the application&lt;/span&gt;
npm run &lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--port&lt;/span&gt; 5173
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We told &lt;code&gt;miniflare&lt;/code&gt; to use port 5173, because that's the address we gave GitHub while registering our app. If all goes well, our app should run on Miniflare too!&lt;/p&gt;

&lt;p&gt;We're almost ready to deploy. But first, we have to change our GitHub app's callback URL to point at our deployment URL (should be something ending with &lt;code&gt;workers.dev&lt;/code&gt;). Actually a better idea is to register a second app and keep the first one for development. Register your app, generate a client key and add a &lt;code&gt;[vars]&lt;/code&gt; to your &lt;code&gt;wrangler.toml&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[vars]&lt;/span&gt;
&lt;span class="py"&gt;GITHUB_CLIENT_ID&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;your client ID&amp;gt;"&lt;/span&gt;
&lt;span class="py"&gt;GITHUB_CLIENT_SECRET&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;your client secret&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're ready to deploy with &lt;code&gt;npm run deploy&lt;/code&gt;! If all goes well, your app will be deployed to Cloudflare Workers and you should be able to sign in with GitHub, create posts with your username, and sign out. You can share it with your friends to test if it works for them too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small bugs
&lt;/h2&gt;

&lt;p&gt;If you played around enough with it, you may have noticed a small bug: If the Cloudflare edge that is running your app happens to be on a different time zone than you are, the server will render a different date than the client. The same will happen if your browser's locale is different than the server's. The easiest way to fix this is to always render the date on the client. Rakkas has a &lt;a href="https://rakkasjs.org/guide/client-rendering"&gt;&lt;code&gt;ClientOnly&lt;/code&gt;&lt;/a&gt; component that does exactly that. We'll fix it and redeploy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ClientOnly&lt;/span&gt; &lt;span class="na"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ClientOnly&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also, you may occasionally find that sometimes new tweets don't show up in the list unless you refresh your browser a few times. That's because Cloudflare Workers KV is an &lt;em&gt;eventually consistent&lt;/em&gt; store. So, occasionally, your changes may not be immediately visible. It may actually take up to a minute to fully synchronize. This is part of the nature of the store we're using and also happens quite rarely so we'll leave it alone for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;In the next article, we'll finish our do some styling and do the finishing touches. Then we'll discuss some ideas to take the project further.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the progress up to this point on &lt;a href="https://github.com/rakkasjs/ublog/tree/article-5"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Sign in with GitHub</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Sat, 13 Aug 2022 08:06:28 +0000</pubDate>
      <link>https://dev.to/cyco130/sign-in-with-github-13hp</link>
      <guid>https://dev.to/cyco130/sign-in-with-github-13hp</guid>
      <description>&lt;p&gt;GitHub offers a free API for authenticating users. It's based on &lt;a href="https://en.wikipedia.org/wiki/OAuth"&gt;OAuth&lt;/a&gt;, an open standard for authentication. OAuth is a fairly large subject but our use case is not that complicated. You can learn more on &lt;a href="https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow"&gt;GitHub's documentation&lt;/a&gt; but this is how it works in essence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We'll create a GitHub app and enter a callback URL there. We'll receive a client ID and a client secret (which are just random-looking strings).&lt;/li&gt;
&lt;li&gt;We'll add a "Sign in with GitHub" link to our page. The link will point to a GitHub URL that will include our client ID and a random string that we'll generate (called "state") in query parameters.&lt;/li&gt;
&lt;li&gt;GitHub will show the user a page depending on their authentication status:

&lt;ul&gt;
&lt;li&gt;The user will be shown the GitHub sign-in page (only if they are not already signed in).&lt;/li&gt;
&lt;li&gt;The user will be asked whether they want to authorize our app (only if they have not already authorized our app recently).&lt;/li&gt;
&lt;li&gt;If they accept (or had accepted recently), GitHub will redirect the user to the callback URL we defined in step 1.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The redirection will include a code and the state we sent in step 2 as query parameters. If the state doesn't match the random string that we sent, we'll know something fishy is going on and abort the process. Otherwise, we'll send a POST request to &lt;code&gt;https://github.com/login/oauth/access_token&lt;/code&gt; along with our client ID, client secret, and the code we received as a query parameter. If everything goes well, GitHub will reply with an access token.&lt;/li&gt;
&lt;li&gt;We'll use the access token in the &lt;code&gt;Authorization&lt;/code&gt; header every time we want to get the user's profile data from GitHub.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We have a plan. Let's start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a GitHub app
&lt;/h2&gt;

&lt;p&gt;Head over to &lt;a href="https://github.com/settings/developers"&gt;GitHub Developer Settings&lt;/a&gt;, click OAuth Apps on the left and then click the "New OAuth app" button. It's gonna ask you a few questions. Enter &lt;code&gt;http://localhost:5173&lt;/code&gt; for the homepage URL and &lt;code&gt;http://localhost:5173/login&lt;/code&gt; for the callback URL, and fill the rest as you like. We're giving &lt;code&gt;localhost&lt;/code&gt; addresses because we have to test our app before deploying to its final URL. You can just update the URLs when you deploy or create a new app and keep this one for testing and development.&lt;/p&gt;

&lt;p&gt;After you submit the form, you'll end up on a page where you will see your app's client ID. You will also see a "Generate a new client secret" button. Go ahead and generate one and copy both into a new file in your repository's root directory and save it with the name &lt;code&gt;.env&lt;/code&gt;. It should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your client ID&amp;gt;
&lt;span class="nv"&gt;GITHUB_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;your client secret&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's good practice to keep our app secrets and configuration in environment variables. Now add this file to your &lt;code&gt;.gitignore&lt;/code&gt; file so you don't accidentally push your secret to GitHub. To load this into the environment during development, we'll install the &lt;code&gt;dotenv&lt;/code&gt; package:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we'll import it and call it in our &lt;code&gt;vite.config.ts&lt;/code&gt;. The file will end up looking like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;rakkas&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tsconfigPaths&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-tsconfig-paths&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;envDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;tsconfigPaths&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;rakkas&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudflare-workers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we will be able to access the variables with, e.g., &lt;code&gt;process.env.GITHUB_CLIENT_ID&lt;/code&gt; in our server-side code. &lt;code&gt;process.env&lt;/code&gt; is a Node-specific global but Rakkas makes it available on Cloudflare Workers too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a "sign in" link
&lt;/h2&gt;

&lt;p&gt;Right now, we only have a single page. But it's not gonna be like that forever. We probably want to see the "sign in" link on the header of every page. Rakkas has a &lt;a href="https://rakkasjs.org/guide/layouts"&gt;layout system&lt;/a&gt; for shared elements like this. Layouts wrap nested layouts and pages under the same directory and its subdirectories. So if we create a &lt;code&gt;layout.tsx&lt;/code&gt; file in the &lt;code&gt;src/routes&lt;/code&gt; directory, it will wrap every single page on our app.&lt;/p&gt;

&lt;p&gt;We said the "sign in" link would point to a GitHub URL. That URL, according to GitHub's documentation, is &lt;code&gt;https://github.com/login/oauth/authorize?client_id=&amp;lt;CLIENT_ID&amp;gt;${clientId}&amp;amp;state=&amp;lt;STATE&amp;gt;&lt;/code&gt;. Our client ID is in &lt;code&gt;process.env.GITHUB_CLIENT_ID&lt;/code&gt; which is only accessible on the server-side. So we'll use &lt;code&gt;useServerSideQuery&lt;/code&gt; again to access it. We'll tackle the handling of the &lt;code&gt;state&lt;/code&gt; parameter later, let's give it &lt;code&gt;12345&lt;/code&gt; for now. So here's the first draft of our &lt;code&gt;src/routes/layout.tsx&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LayoutProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;MainLayout&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;LayoutProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12345&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}));&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;uBlog&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;
                    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;right&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                    &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
                        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/login/oauth/authorize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                        &lt;span class="s2"&gt;`?client_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                        &lt;span class="s2"&gt;`&amp;amp;state=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
                    &lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                    Sign in with GitGub
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hr&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;header&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you launch the dev server you'll see that we have a site header now. And the "Sign in with GitHub" link will take you to GitHub's authorization page. If you go ahead and authorize your app GitHub will redirect you to a URL that looks like &lt;code&gt;http://localhost:5173/login?code=&amp;lt;BUNCH_OF_RANDOM_LOOKING_CHARS&amp;gt;&amp;amp;state=12345&lt;/code&gt;. &lt;code&gt;http://localhost:5173/login&lt;/code&gt; is the URL we entered as the callback URL and the rest are the parameters sent by GitHub. Of course, you will get a 404 error because we haven't implemented that endpoint yet. Let's do that now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Login callback
&lt;/h2&gt;

&lt;p&gt;We'll create a &lt;code&gt;src/routes/login.page.tsx&lt;/code&gt; file to implement the login callback. In it, we will use the &lt;code&gt;code&lt;/code&gt; query parameter to get an access token from GitHub and then use that access token to get the user's profile data. We will use the &lt;code&gt;useServerSideQuery&lt;/code&gt; hook again because we don't want to expose our client secret to the client. Remember, the &lt;code&gt;useServerSideQuery&lt;/code&gt; callback runs on the server and will not be part of the client bundle. Let's see what the profile data looks like first by printing it as JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;LoginPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;state&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;12345&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://github.com/login/oauth/access_token&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                    &lt;span class="s2"&gt;`?client_id=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                    &lt;span class="s2"&gt;`&amp;amp;client_secret=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
                    &lt;span class="s2"&gt;`&amp;amp;code=&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Accept&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://api.github.com/user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="na"&gt;Authorization&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`token &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Error: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything goes well, you should see your GitHub user profile data in JSON format when you click on "Sign in with GitHub". Mine looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cyco130"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10846005&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"node_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MDQ6VXNlcjEwODQ2MDA1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"avatar_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://avatars.githubusercontent.com/u/10846005?v=4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gravatar_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"html_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/cyco130"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"followers_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/followers"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"following_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/following{/other_user}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gists_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/gists{/gist_id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"starred_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/starred{/owner}{/repo}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"subscriptions_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/subscriptions"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"organizations_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/orgs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"repos_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/repos"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"events_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/events{/privacy}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"received_events_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://api.github.com/users/cyco130/received_events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"site_admin"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Fatih Aygün"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"company"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lityum AŞ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"blog"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Istanbul"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"hireable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"bio"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Programmer, musician, amateur linguist."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"twitter_username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cyco130"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"public_repos"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"public_gists"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"followers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"following"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"created_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2015-02-04T09:24:28Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"updated_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2022-06-29T03:02:45Z"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Success! We've accomplished quite a lot! It's a good time to take a break!&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;In the next article, we'll finish our authentication feature. We will use cookies to remember who is who.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the progress up to this point on &lt;a href="https://github.com/rakkasjs/ublog/tree/article-4"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Create post form</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Wed, 10 Aug 2022 18:46:39 +0000</pubDate>
      <link>https://dev.to/cyco130/create-post-form-3hea</link>
      <guid>https://dev.to/cyco130/create-post-form-3hea</guid>
      <description>&lt;p&gt;In the two previous articles, we set up and deployed a project that can retrieve data from Cloudflare Workers KV store. Now we're gonna create a form for creating new posts.&lt;/p&gt;

&lt;p&gt;Rakkas has built-in support for &lt;a href="https://rakkasjs.org/guide/using-forms"&gt;form handling&lt;/a&gt;. We'll start with creating the form itself by adding the following lines to &lt;code&gt;src/routes/index.page.tsx&lt;/code&gt;, right after the closing &lt;code&gt;&amp;lt;/ul&amp;gt;&lt;/code&gt; tag of the post list and before the closing &lt;code&gt;&amp;lt;/main&amp;gt;&lt;/code&gt; tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fairly conventional so far. The cool part is the action handler. If you export a function named &lt;code&gt;action&lt;/code&gt; from a page file, Rakkas will call it when a form is submitted to that address. The code in the action function will always run on the server-side, similar to the code in the &lt;code&gt;useServerSideQuery&lt;/code&gt; callback. Let's add it to the bottom of the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ActionHandler type is defined in the `rakkasjs` package.&lt;/span&gt;
&lt;span class="c1"&gt;// Add it to your imports.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionHandler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Retrieve the form data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;content&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Do some validation&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content is required&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// It could be a file upload!&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content must be a string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content must be less than 280 characters&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requestContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// We don't have login/signup yet,&lt;/span&gt;
            &lt;span class="c1"&gt;// so we'll just make up a user name&lt;/span&gt;
            &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Arden Eberhardt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// This generates a random string as the post key&lt;/span&gt;
    &lt;span class="c1"&gt;// but we'll talk more about this later.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you spin up the dev server, you will see that you can add new posts now!&lt;/p&gt;

&lt;h2&gt;
  
  
  Improving the user experience
&lt;/h2&gt;

&lt;p&gt;Cool, but we have several UX problems here. First of all, we're not showing validation errors to the user.&lt;/p&gt;

&lt;p&gt;If the action handler returns an object with the &lt;code&gt;data&lt;/code&gt; key, that data will be available to the page component in the &lt;code&gt;actionData&lt;/code&gt; prop. It will be undefined if there were no form submissions. So we'll change the signature of the &lt;code&gt;HomePage&lt;/code&gt; component like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// PageProps type is defined in the `rakkasjs` package.&lt;/span&gt;
&lt;span class="c1"&gt;// Add it to your imports.&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;actionData&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll add an error message right above the submit button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you'll be able to see an error message if you try to submit an empty post or if the content's too long. But it's still not very user-friendly that the form is cleared when there is an error. One solution is to echo back the form data in the return value of the action handler and then use it to populate the form. So we'll change the part that returns the "too long" error like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="o"&gt;-&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content must be less than 280 characters&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;       &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;           &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content must be less than 280 characters&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;           &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Echo back the form data&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;       &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;   &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we'll use it to initialize our textarea element's default value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;textarea&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"content"&lt;/span&gt; &lt;span class="na"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;defaultValue&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;actionData&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you try again and submit a post that is too long, you will see that the form will not be cleared and you will be able to edit the content down to 280 characters to re-submit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sorting the posts
&lt;/h2&gt;

&lt;p&gt;You may have noticed that newly created posts are inserted at a random position in the list. It would be better if we saw them in the newest-first order. The KV store doesn't have a method for sorting by content or metadata. But it always returns the items in the alphabetical order of the keys. Instead of random keys, we could use the creation time but it would be the exact opposite of what we want since &lt;code&gt;2022-08-01T00:00:00.000Z&lt;/code&gt; comes &lt;em&gt;after&lt;/em&gt; &lt;code&gt;2020-08-01T00:00:00.000Z&lt;/code&gt; when sorted alphabetically.&lt;/p&gt;

&lt;p&gt;So we'll have to get creative here. The JavaScript &lt;code&gt;Date&lt;/code&gt; instances have a &lt;code&gt;getTime()&lt;/code&gt; method that returns a timestamp which is the number of milliseconds since January 1, 1970. You can also create a Date from a timestamp with, e.g. &lt;code&gt;new Date(0)&lt;/code&gt;. What's the date for the timestamp 9,999,999,999,999? &lt;code&gt;new Date(9_999_999_999_999)&lt;/code&gt; returns November 20, 2286. I'm fairly certain ublog will not be around for that long. So my idea is to use &lt;code&gt;9_999_999_999_999 - new Date().getTime()&lt;/code&gt; as our key.&lt;/p&gt;

&lt;p&gt;To make sure that the keys are small we'll use the base-36 encoding and to ensure alphabetical sorting, we'll left-pad the result with zeroes. The base-36 encoding of 9,999,999,999,999 is &lt;code&gt;3jlxpt2pr&lt;/code&gt; which is 9 characters long. So we will left-pad until the key is at least 9 characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;_999_999_999_999&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The keys should be unique but what if two users create posts at the same time? We can reduce the possibility of key collisions to "practically zero" by appending a random string at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;generateKey&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="nx"&gt;_999_999_999_999&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;getTime&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
        &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real application, you'd probably want to use a more sophisticated key generation routine like UUID v4 but this is fine for our purposes.&lt;/p&gt;

&lt;p&gt;Now if you spin up the dev server, you will see that the posts are sorted by creation time except for the mock ones. You can fix those by changing their made-up keys from &lt;code&gt;1&lt;/code&gt;-&lt;code&gt;3&lt;/code&gt; to &lt;code&gt;z1&lt;/code&gt;-&lt;code&gt;z3&lt;/code&gt; so that they always stay at the bottom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;That's it!&lt;/strong&gt; We can now add new posts to the list and view them in the newest-first order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing with Miniflare
&lt;/h2&gt;

&lt;p&gt;Since anyone can create posts now, it's best if we don't deploy this to Cloudflare Workers yet. But we can test our workers bundle with Miniflare by building with &lt;code&gt;npm run build&lt;/code&gt; and launching with &lt;code&gt;npm run local&lt;/code&gt;. Miniflare has built-in KV store support so everything should work as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;In the next article, we'll implement authentication (sign-in/sign-up) using the &lt;a href="https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps"&gt;GitHub OAuth API&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the progress up to this point on &lt;a href="https://github.com/rakkasjs/ublog/tree/article-3"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>cloud</category>
    </item>
    <item>
      <title>KV store</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Wed, 10 Aug 2022 14:58:00 +0000</pubDate>
      <link>https://dev.to/cyco130/part-2-kv-store-3b95</link>
      <guid>https://dev.to/cyco130/part-2-kv-store-3b95</guid>
      <description>&lt;p&gt;In the previous article, we set up our project and deployed a "Hello World" app to Cloudflare Workers. Now we're gonna look at storing and retrieving our data in &lt;a href="https://developers.cloudflare.com/workers/runtime-apis/kv/"&gt;Cloudflare Workers KV store&lt;/a&gt;. It's a simple but useful key-value store and it has a generous free tier that we can use for our project. We'll start by installing a few dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; @cloudflare/workers-types @miniflare/kv @miniflare/storage-memory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@cloudflare/workers-types&lt;/code&gt; provides global type definitions for the KV API. We'll add it to our &lt;code&gt;tsconfig.json&lt;/code&gt; file in &lt;code&gt;compilerOptions.types&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"compilerOptions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;existing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;compiler&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vite/client"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="nl"&gt;"types"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"vite/client"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@cloudflare/workers-types"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The KV API is only available on Cloudflare Workers. But, during development, Rakkas runs our app on Node.js. Fortunately, the &lt;a href="https://miniflare.dev"&gt;Miniflare&lt;/a&gt; project has a KV implementation for Node. The other two packages that we've installed (&lt;code&gt;@miniflare/kv&lt;/code&gt; and &lt;code&gt;@miniflare/storage-memory&lt;/code&gt;) are what we need to be able to use the KV API during development. Let's create a &lt;code&gt;src/kv-mock.ts&lt;/code&gt; file and create a local KV store to store our ublog posts ("twits") while testing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;KVNamespace&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@miniflare/kv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MemoryStorage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@miniflare/storage-memory&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;postStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;KVNamespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;MemoryStorage&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MOCK_POSTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Jane Doe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2022-08-10T14:34:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello ublog!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cody Reimer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2022-08-10T13:27:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;3&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Wow, this is pretty cool!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Zoey Washington&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2022-08-10T12:00:00.000Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// We'll add some mock posts&lt;/span&gt;
&lt;span class="c1"&gt;// Rakkas supports top level await&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// We'll do this in parallel with Promise.all,&lt;/span&gt;
    &lt;span class="c1"&gt;// just to be cool.&lt;/span&gt;
    &lt;span class="nx"&gt;MOCK_POSTS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see we also added some mock data because our application doesn't have a "create post" feature yet. This way, we can start fetching and showing some posts before we implement it.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;put&lt;/code&gt; method of the store accepts a key, a value, and some optional metadata. We'll use the content to store the actual post content, and the metadata to store the author and the date the post was created. The key has to be unique but other than that it is currently meaningless, we'll get to that later.&lt;/p&gt;

&lt;p&gt;Now we should make this store available to our application's server-side code. The best place to do it is the &lt;a href="https://rakkasjs.org/guide/hattip-entry"&gt;HatTip entry&lt;/a&gt; which is the main server-side entry point of a Rakkas application. It's an optional file which is not part of the generated boilerplate so we'll add it manually as &lt;code&gt;src/entry-hattip.ts&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createRequestHandler&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ServerSideLocals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;KVNamespace&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;createRequestHandler&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;middleware&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;beforePages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEV&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;postStore&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./kv-mock&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;KV_POSTS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Woah, that's a lot of unfamiliar stuff. Let's break it down.&lt;/p&gt;

&lt;p&gt;The HatTip entry is supposed to default export a HatTip request handler. So we create one with &lt;code&gt;createRequestHandler&lt;/code&gt;. &lt;code&gt;createRequestHandler&lt;/code&gt; accepts a bunch of options to customize the server's behavior. One of them is &lt;code&gt;middleware&lt;/code&gt; which is used to inject middleware functions into Rakkas's request handling pipeline. &lt;a href="https://github.com/hattipjs/hattip#middleware-system"&gt;HatTip middlewares&lt;/a&gt; are similar to Express middlewares in many ways. So the concept should be familiar if you've used Express before.&lt;/p&gt;

&lt;p&gt;We add our middleware before Rakkas processes our application's pages (&lt;code&gt;beforePages&lt;/code&gt;). It's, in fact, the earliest interception point. In the middleware, we inject our store into the request context object which will be available to our application's server-side code. The request context object has a &lt;code&gt;locals&lt;/code&gt; property dedicated to storing application-specific stuff like this.&lt;/p&gt;

&lt;p&gt;The bit starting with &lt;code&gt;declare module "rakkasjs"&lt;/code&gt; is &lt;a href="https://www.typescriptlang.org/docs/handbook/declaration-merging.html#merging-interfaces"&gt;a TypeScript technique&lt;/a&gt; for extending interfaces declared in other modules. In this case, we're extending the &lt;code&gt;ServerSideLocals&lt;/code&gt; interface which is the type of &lt;code&gt;ctx.locals&lt;/code&gt; where &lt;code&gt;ctx&lt;/code&gt; is the request context object.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;import.meta.env.DEV&lt;/code&gt; is a &lt;a href="https://vitejs.dev/guide/env-and-mode.html"&gt;Vite feature&lt;/a&gt;. Its value is &lt;code&gt;true&lt;/code&gt; during development and &lt;code&gt;false&lt;/code&gt; in production. Here, we use it to determine whether we should create a mock KV store or use the real one on Cloudflare Workers.&lt;/p&gt;

&lt;p&gt;For production, HatTip's Cloudflare Workers adapter makes the so-called &lt;a href="https://developers.cloudflare.com/workers/platform/bindings/"&gt;bindings&lt;/a&gt; available in &lt;a href="https://github.com/hattipjs/hattip/tree/main/packages/adapter/adapter-cloudflare-workers#contextplatform"&gt;&lt;code&gt;ctx.platform.env&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;ctx.platform&lt;/code&gt;'s type is &lt;code&gt;unknown&lt;/code&gt; because it changes depending on the environment. So we use &lt;code&gt;as any&lt;/code&gt; to appease the TypeScript compiler. &lt;code&gt;KV_POSTS&lt;/code&gt; is just a name we've chosen for the binding name of our posts store.&lt;/p&gt;

&lt;p&gt;Thanks to this fairly simple middleware, the KV store that will hold our posts will be available to our application as &lt;code&gt;ctx.locals.postStore&lt;/code&gt; where &lt;code&gt;ctx&lt;/code&gt; is the request context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fetching data from the KV store
&lt;/h2&gt;

&lt;p&gt;Now we'll spin up a dev server with &lt;code&gt;npm run dev&lt;/code&gt; and edit the &lt;code&gt;src/pages/index.page.tsx&lt;/code&gt; file to fetch and display our mock posts. Rakkas has a very cool data fetching hook called &lt;a href="https://rakkasjs.org/guide/use-server-side-query"&gt;&lt;code&gt;useServerSideQuery&lt;/code&gt;&lt;/a&gt;. With this hook, you can put your server-side code &lt;strong&gt;right inside your components&lt;/strong&gt; without having to create API endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;HomePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// This callback always runs on the server.&lt;/span&gt;
        &lt;span class="c1"&gt;// So we have access to the request context!&lt;/span&gt;

        &lt;span class="c1"&gt;// Get a list of the keys and metadata&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nl"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Get individual posts and move things around&lt;/span&gt;
        &lt;span class="c1"&gt;// a little to make it easier to render&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
                &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;locals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postStore&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}))&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Posts&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* post.key.metadata may not be available while testing for */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* reasons we'll cover later. That's why we need the nullish */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* checks here */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;author&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown author&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;i&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                            &lt;span class="ni"&gt;&amp;amp;nbsp;&lt;/span&gt;
                            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;
                                    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postedAt&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                                    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unknown date&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;hr&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it! Now you should see a list of mock posts if you visit &lt;code&gt;http://localhost:5173&lt;/code&gt;. Don't worry about the ugly looks yet. We'll cover styling later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building for production
&lt;/h2&gt;

&lt;p&gt;Now build your application for production and deploy it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run build
npm run deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, if you visit your production URL now, you will get an error. That's because we haven't created a KV store on Cloudflare Workers yet. We'll do that with the &lt;code&gt;wrangler&lt;/code&gt; CLI::&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler kv:namespace create KV_POSTS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything goes well, you should see a message like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Add the following to your configuration file in your kv_namespaces array:
{ binding = "KV_POSTS", id = "&amp;lt;YOUR_KV_NAMESPACE_ID&amp;gt;" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will do just that and we will add the following at the end of our &lt;code&gt;wrangler.toml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[kv_namespaces]]&lt;/span&gt;
&lt;span class="py"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"KV_POSTS"&lt;/span&gt;
&lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;YOUR_KV_NAMESPACE_ID&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we will deploy again with &lt;code&gt;npm run deploy&lt;/code&gt;. This time the error is gone but we'll still not see any posts. Let's add a few with &lt;code&gt;wrangler&lt;/code&gt; CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx wrangler kv:key put &lt;span class="nt"&gt;--binding&lt;/span&gt; KV_POSTS 1 &lt;span class="s2"&gt;"Hello world!"&lt;/span&gt;
npx wrangler kv:key put &lt;span class="nt"&gt;--binding&lt;/span&gt; KV_POSTS 2 &lt;span class="s2"&gt;"Ooh! Pretty nice!"&lt;/span&gt;
npx wrangler kv:key put &lt;span class="nt"&gt;--binding&lt;/span&gt; KV_POSTS 3 &lt;span class="s2"&gt;"Wrangler lets us add new values to KV!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, &lt;code&gt;wrangler&lt;/code&gt; CLI doesn't allow us to add metadata to our posts so we'll see "Unknown author" and "Unknown date" in the UI but other than that... &lt;strong&gt;IT WORKS, YAY!&lt;/strong&gt; We have a working data store for our app!&lt;/p&gt;

&lt;p&gt;You can also visit the &lt;a href="https://dash.cloudflare.com"&gt;Cloudflare Dashboard&lt;/a&gt; and go to Workers &amp;gt; KV to add/remove/edit your values in your store. If you do, you will notice that Cloudflare uses the same KV store mechanism to store your static assets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaning up
&lt;/h2&gt;

&lt;p&gt;If you're going to put your code in a public repo, you should not expose your KV store ID. Just make a copy of your &lt;code&gt;wrangler.toml&lt;/code&gt; as &lt;code&gt;wrangler.example.toml&lt;/code&gt; and redact the KV store ID from the copy. Then add &lt;code&gt;wrangler.toml&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; and run &lt;code&gt;git rm wrangler.toml --cached&lt;/code&gt; before committing. I'm not entirely sure whether this is required but there has been a data breach in the past involving the KV store ID so it's best to play safe.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;In the next article, we'll add a form to allow users to add new posts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You can find the progress up to this point on &lt;a href="https://github.com/rakkasjs/ublog/tree/article-2"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>react</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Project setup</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Wed, 10 Aug 2022 08:23:00 +0000</pubDate>
      <link>https://dev.to/cyco130/creating-a-twitter-clone-for-cloudflare-workers-with-rakkas-part-1-44i</link>
      <guid>https://dev.to/cyco130/creating-a-twitter-clone-for-cloudflare-workers-with-rakkas-part-1-44i</guid>
      <description>&lt;p&gt;This article is a walk-through of creating a Twitter clone with &lt;a href="https://github.com/rakkasjs/rakkasjs"&gt;Rakkas&lt;/a&gt;, a bleeding-edge React framework powered by &lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt;. Our Twitter clone, which we'll call &lt;strong&gt;ublog&lt;/strong&gt;, is going to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run on &lt;a href="https://workers.cloudflare.com/"&gt;Cloudflare Workers&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;store its data on &lt;a href="https://www.cloudflare.com/products/workers-kv/"&gt;Cloudflare Workers KV&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;and use GitHub for authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project setup
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Rakkas itself works on Node 14.20 or later but we'll need at least Node 16.7 for this tutorial&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We'll use the &lt;code&gt;create-rakkas-app&lt;/code&gt; package to create our boilerplate. The following options will enable all features except the demo app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-rakkas-app@latest ublog &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-demo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second step is to change into the project directory and install dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ublog &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point we should do a quick check to make sure everything was set up properly by launching a dev server:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will start a dev server on &lt;code&gt;localhost:5173&lt;/code&gt;. You can visit that address to see the "Hello World" message.&lt;/p&gt;

&lt;p&gt;Now you can edit the file &lt;code&gt;src/routes/index.page.tsx&lt;/code&gt; to see Rakkas's hot module reloading with fast refresh support in action. Your edits will be reflected instantly in the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customization
&lt;/h2&gt;

&lt;p&gt;The package name in the &lt;code&gt;package.json&lt;/code&gt; file is &lt;code&gt;-TODO-&lt;/code&gt; to remind us that it should be changed. So we'll go ahead change it to &lt;code&gt;ublog&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The generated boilerplate comes with a &lt;code&gt;.prettierrc&lt;/code&gt; file for configuring &lt;a href="https://prettier.io/"&gt;Prettier&lt;/a&gt;, a popular code formatter. The generated configuration is empty but I like customizing it a little to use tabs for indentation by adding &lt;code&gt;"useTabs": true&lt;/code&gt;. You can have your own preferences of course. After editing, we'll run the following command to reformat our sources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run format
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we should create a &lt;code&gt;.gitignore&lt;/code&gt; file and add &lt;code&gt;node_modules&lt;/code&gt; and &lt;code&gt;dist&lt;/code&gt; to it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
dist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and initialize our Git repo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git init &amp;amp;&amp;amp; git checkout -b main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up for Cloudflare Workers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rakkasjs.org/guide/cloudflare-workers"&gt;Rakkas documentation&lt;/a&gt; on Cloudflare Workers says we should create a &lt;code&gt;wrangler.toml&lt;/code&gt; file. This is copied verbatim from there except the project name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"ublog"&lt;/span&gt;
&lt;span class="py"&gt;compatibility_date&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2021-11-01"&lt;/span&gt;
&lt;span class="py"&gt;compatibility_flags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"streams_enable_constructors"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="py"&gt;main&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"dist/server/cloudflare-workers-bundle.js"&lt;/span&gt;
&lt;span class="py"&gt;usage_model&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'bundled'&lt;/span&gt;
&lt;span class="py"&gt;workers_dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="nn"&gt;[site]&lt;/span&gt;
&lt;span class="py"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"./dist/client"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we should install the HatTip adapter for Cloudflare Workers. &lt;a href="https://github.com/hattipjs/hattip"&gt;HatTip&lt;/a&gt; is a set of JavaScript libraries for building HTTP server applications that run on many platforms like Node.js, Cloudflare Workers, Vercel, Netlify, Deno, and more. Rakkas relies on it for serverless support.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-S&lt;/span&gt; @hattip/adapter-cloudflare-workers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll configure Rakkas to build for Cloudflare Workers by updating the options passed to Rakkas Vite plugin in the &lt;code&gt;vite.config.ts&lt;/code&gt; file. It will look like this in the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;rakkas&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;rakkasjs/vite-plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;tsconfigPaths&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;vite-tsconfig-paths&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;defineConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="nx"&gt;tsconfigPaths&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="nx"&gt;rakkas&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;adapter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cloudflare-workers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we'll see if everything works when we build our application by running &lt;code&gt;npm run build&lt;/code&gt;. If everything works well, Rakkas will tell you that it built your Cloudflare Workers bundle in &lt;code&gt;dist/server/cloudflare-workers-bundle.js&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Cloudflare Workers has a local emulator called &lt;code&gt;miniflare&lt;/code&gt;. We can use it to test our bundle locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; miniflare
npx miniflare &lt;span class="nt"&gt;--modules&lt;/span&gt; dist/server/cloudflare-workers-bundle.js
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will launch a local worker emulator on &lt;code&gt;localhost:8787&lt;/code&gt;. You should see the "Hello World" message when you visit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying
&lt;/h2&gt;

&lt;p&gt;If everything worked well so far, we can deploy our (as of yet useless) application to Cloudflare Workers using the &lt;code&gt;wrangler&lt;/code&gt; CLI. If you don't have a Cloudflare Workers account or if you're not logged in, the following command will open your browser so you can log in or create a free account before publishing your application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-D&lt;/span&gt; wrangler
npx wrangler publish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything works well, your application will be up and running on Cloudflare Workers and &lt;code&gt;wrangler&lt;/code&gt; will print the URL it's available at. If you go ahead and visit, you should see the "Hello World" message.&lt;/p&gt;

&lt;p&gt;Finally, let's create a few shortcuts before committing our changes. Let's add the following to the &lt;code&gt;scripts&lt;/code&gt; section of the &lt;code&gt;package.json&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;existing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;scripts&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"miniflare --modules dist/server/cloudflare-workers-bundle.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"wrangler publish"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we can simply do &lt;code&gt;npm run local&lt;/code&gt; to test locally and &lt;code&gt;npm run deploy&lt;/code&gt; to deploy.&lt;/p&gt;

&lt;p&gt;We can now commit our changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"chore: initialize boilerplate for Cloudflare Workers"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's next?
&lt;/h2&gt;

&lt;p&gt;In the next article, we'll learn how to use &lt;a href="https://www.cloudflare.com/products/workers-kv"&gt;Cloudflare Workers KV&lt;/a&gt; to store our data, including how to create a mock KV store in development.&lt;/p&gt;

&lt;p&gt;You can find the progress up to this point on &lt;a href="https://github.com/rakkasjs/ublog/tree/article-1"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Rakkas 0.6: Bleeding-edge React framework</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Mon, 08 Aug 2022 16:19:00 +0000</pubDate>
      <link>https://dev.to/cyco130/rakkas-06-bleeding-edge-react-framework-1jn6</link>
      <guid>https://dev.to/cyco130/rakkas-06-bleeding-edge-react-framework-1jn6</guid>
      <description>&lt;p&gt;💃 &lt;a href="https://github.com/rakkasjs/rakkasjs"&gt;Rakkas&lt;/a&gt;, the bleeding edge React framework powered by &lt;a href="https://vitejs.dev"&gt;Vite&lt;/a&gt;, has just released version 0.6.0. First release since December, this is the largest and least backward compatible update so far. But we believe the quantity and the quality of the new features justify the number of breaking changes. See if you agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  React 18
&lt;/h2&gt;

&lt;p&gt;Rakkas 0.6 was redesigned from the ground up to be compatible with React 18. It supports the new &lt;strong&gt;Concurrent Mode&lt;/strong&gt;, &lt;strong&gt;Streaming SSR&lt;/strong&gt;, and &lt;strong&gt;Suspense for data fetching&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Streaming SSR&lt;/strong&gt; means that Rakkas apps, when doing server-side rendering (SSR), now send chunks of a page's HTML as soon as they are available instead of waiting until everything is fully rendered. This way, the user starts seeing meaningful content as soon as possible instead of waiting in front of a blank screen. Streaming SSR integrates seamlessly with Suspense and the new data fetching system we'll describe below.&lt;/p&gt;

&lt;p&gt;If you're worried about streaming SSR interfering with SEO, you'll be relieved to know that Rakkas also implements &lt;a href="https://huckabuy.com/technical-seo/dynamic-rendering"&gt;dynamic rendering&lt;/a&gt;: It sends fully rendered pages with correct status codes, HTTP headers, and head tags to search bots while streaming HTML to normal browsers. In any case, you can opt out of streaming on a global, per-route, or per-request basis without giving up Suspense-based data fetching.&lt;/p&gt;

&lt;h2&gt;
  
  
  New data fetching system
&lt;/h2&gt;

&lt;p&gt;Instead of the page-centric data fetching functions that ran before the page was rendered (“fetch-then-render”), Rakkas 0.6 adopts a Suspense-based data fetching system (“render-as-you-fetch“). The new system comes in the form of &lt;code&gt;useQuery&lt;/code&gt; and &lt;code&gt;useMutation&lt;/code&gt; hooks that implement a Suspense-only subset of the &lt;a href="https://react-query.tanstack.com"&gt;react-query&lt;/a&gt; API.&lt;/p&gt;

&lt;h3&gt;
  
  
  API-less data fetching
&lt;/h3&gt;

&lt;p&gt;If you're familiar with Next.js, you know how cool &lt;code&gt;getServerSideProps&lt;/code&gt; is. With it, you can put, say, your database access code right next to your page component without worrying about implementing and maintaining a REST/GraphQL/RPC/whatever API. Next.js cleverly strips this function and its imports from the client bundle. During SSR, it's called directly. On the client, Next sends a request to the server instead of calling the function.&lt;/p&gt;

&lt;p&gt;We believe we've one-upped the ease of use of this data fetching method. Enter &lt;code&gt;useServerSideQuery&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./my-db-access-lib&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;UserCard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;userName&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// This is server-side code,&lt;/span&gt;
            &lt;span class="c1"&gt;// always validate user input!&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid request&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;userQuery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fullName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;caption&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't a page, just an ordinary React component that you can use on many pages. In Rakkas 0.6 pages aren't very special in terms of data fetching anymore. Just like &lt;code&gt;getServerSideProps&lt;/code&gt;, the function passed to &lt;code&gt;useServerSideQuery&lt;/code&gt; (&lt;code&gt;useSSQ&lt;/code&gt; for short) always runs on the server: On the client, a request is sent to the server instead of calling the function directly. Rakkas performs a code transform similar to Next's to strip the function and its imports from the client bundle.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;useSSQ&lt;/code&gt;'s return type is inferred, so, if you're using TypeScript, you get IDE completion just like you would with a local function call. Another nice feature is that, unlike other similar data fetching hooks, &lt;code&gt;useSSQ&lt;/code&gt; doesn't return loading or error states. Those are handled higher up in the component tree, with Suspense fallbacks and error boundaries respectively. This way, your component code stays clean and focused.&lt;/p&gt;

&lt;p&gt;One practical application of &lt;code&gt;useSSQ&lt;/code&gt; that may not be immediately obvious is circumventing CORS restrictions when calling third-party APIs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useServerSideQuery&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://some.cors.restricted.api.example.com&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works because the &lt;code&gt;fetch&lt;/code&gt; call inside &lt;code&gt;useServerSideQuery&lt;/code&gt; runs on the server-side (Rakkas makes &lt;code&gt;fetch&lt;/code&gt; globally available on all deployment targets) and, as such, is not affected by CORS restrictions. In effect, it creates an ad-hoc CORS proxy.&lt;/p&gt;

&lt;p&gt;Paired with Remix-inspired action handlers or &lt;code&gt;useServerSideMutation&lt;/code&gt;, &lt;code&gt;useSSQ&lt;/code&gt; lets you write code as if the server-client barrier didn't exist. But, of course, API routes are still available to allow you to implement REST, GraphQL, RPC etc. endpoints that you can access via &lt;code&gt;useQuery&lt;/code&gt; and &lt;code&gt;useMutation&lt;/code&gt; or other methods if you prefer to stick with traditional data fetching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Avoiding waterfalls
&lt;/h3&gt;

&lt;p&gt;Even though we said that in Rakkas 0.6 pages and layouts are not very special in terms of data fetching anymore, you can still have a &lt;code&gt;preload&lt;/code&gt; function to start fetching early. &lt;code&gt;preload&lt;/code&gt; functions of a page and its parent layouts all run in parallel to avoid late discovery of data dependencies and waterfalls. &lt;code&gt;preload&lt;/code&gt; also provides a way to inject SEO-critical title and meta tags into the page even when streaming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other new features
&lt;/h2&gt;

&lt;p&gt;Rakkas 0.6 is now built on &lt;a href="https://github.com/hattipjs/hattip"&gt;HatTip&lt;/a&gt;, an HTTP server library that abstracts away the differences between various JavaScript runtimes building on the web standards like &lt;code&gt;Request&lt;/code&gt;, &lt;code&gt;Response&lt;/code&gt;, and streams. Rakkas already ran on Node, Vercel Serverless Functions, Netlify Functions, and Cloudflare Workers. HatTip integration adds support for &lt;strong&gt;Vercel Edge&lt;/strong&gt;, and &lt;strong&gt;Netlify Edge&lt;/strong&gt;. There's also preliminary support for Deno (including &lt;strong&gt;Deno Deploy&lt;/strong&gt;). Bun support is work in progress.&lt;/p&gt;

&lt;p&gt;HatTip also makes it possible to integrate with &lt;strong&gt;Express&lt;/strong&gt; or other Connect-compatible Node.js server frameworks: Now you can use Express middleware and routes in your Rakkas app or build your app as an Express middleware to be used in other Express apps.&lt;/p&gt;

&lt;p&gt;The new &lt;strong&gt;route guards&lt;/strong&gt; feature allows you to do things like making some routes available to logged-in users only. &lt;strong&gt;Catch-all routes&lt;/strong&gt; and better &lt;strong&gt;404 handling&lt;/strong&gt; are also among the new features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If this is the first time you hear of Rakkas, it has other cool features like a lightning-fast development server (courtesy of Vite), file system routing, nested layouts, static site generation, and more.&lt;/p&gt;

&lt;p&gt;Rakkas uses experimental and/or beta features of React and Vite. As such, expect breaking changes until we hit 1.0. But &lt;a href="https://stackblitz.com/edit/rakkas-demo-ts?file=src%2Fpages%2Fpage.tsx"&gt;go ahead and give it a try&lt;/a&gt; and share your thoughts. &lt;a href="https://github.com/rakkasjs/rakkasjs/stargazers"&gt;Star us on Github&lt;/a&gt;, &lt;a href="https://rakkasjs.org/chat"&gt;join our Discord server&lt;/a&gt;, and &lt;a href="https://twitter.com/cyco130"&gt;follow me on Twitter&lt;/a&gt; for updates.&lt;/p&gt;

&lt;p&gt;If you have any questions, problems, or suggestions &lt;a href="https://github.com/rakkasjs/rakkasjs/issues/new"&gt;open a Github issue&lt;/a&gt;. If you want to &lt;a href="https://github.com/rakkasjs/rakkasjs/blob/main/CONTRIBUTING.md"&gt;contribute&lt;/a&gt;, fork and send a pull request. Check out the &lt;a href="https://github.com/rakkasjs/rakkasjs/issues"&gt;open issues&lt;/a&gt; to see how you can help. As always, &lt;strong&gt;all feedback is welcome&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>ssr</category>
      <category>vite</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>Brief History of Frontend Web Technologies</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Thu, 10 Feb 2022 06:51:29 +0000</pubDate>
      <link>https://dev.to/cyco130/brief-history-of-frontend-web-technologies-2k0b</link>
      <guid>https://dev.to/cyco130/brief-history-of-frontend-web-technologies-2k0b</guid>
      <description>&lt;p&gt;In the beginning there was &lt;strong&gt;HTML&lt;/strong&gt;. But it was dull and lifeless until the &lt;code&gt;&amp;lt;blink&amp;gt;&lt;/code&gt; tag came along and made it come alive. Then came &lt;strong&gt;JavaScript&lt;/strong&gt; to meet our increasingly sophisticated text blinking needs. A year later &lt;strong&gt;CSS&lt;/strong&gt; arrived to separate the concerns of document structure and text blinking in an elegant way. It helped us survive the browser wars relatively unscathed.&lt;/p&gt;

&lt;p&gt;In 2006, &lt;strong&gt;Sass&lt;/strong&gt; (and a little later &lt;strong&gt;Less&lt;/strong&gt; and &lt;strong&gt;Stylus&lt;/strong&gt;) appeared to make text blinking less painful. But the real highlight of the year was the grandiose entrance of &lt;strong&gt;jQuery&lt;/strong&gt;, which was, until quite recently, &lt;em&gt;the&lt;/em&gt; way to make text blink through DOM manipulation. The defiant &lt;code&gt;$&lt;/code&gt; function was the Spartacus which led JavaScript to a successful revolution that would end up enslaving their former master HTML.&lt;/p&gt;

&lt;p&gt;Then began the dark age of &lt;strong&gt;IE6 compatibility&lt;/strong&gt;. IE6 made consistent cross-browser text blinking unnecessarily complicated. All the other browsers had nicer blinking features, but people kept using the damn thing until big names abandoned it and took a firm stand ("we want blink!") to put the final nail in its coffin in 2010.&lt;/p&gt;

&lt;p&gt;2010 was also the year &lt;strong&gt;AngularJS&lt;/strong&gt; came along to make us question the ways of the one true JS library, the Holy jQuery. The mighty &lt;code&gt;$&lt;/code&gt; was challenged by &lt;code&gt;ng-&lt;/code&gt;, but stood its ground and still survives today.&lt;/p&gt;

&lt;p&gt;Three years later, in 2013, came &lt;code&gt;&amp;lt;React /&amp;gt;&lt;/code&gt; along with its &lt;strong&gt;JSX syntax&lt;/strong&gt;. By this time you needed JavaScript programs to compile your JavaScript programs into JavaScript programs... to make nicer and better performing blinking texts. Some thought of JSX as a counter-revolution by HTML loyalists, and some as the final blow to HTML's hegemony over JavaScript. Whichever is true, it did save the web: This was also the year that Firefox decided to kill the mighty &lt;code&gt;&amp;lt;blink&lt;/code&gt;&amp;gt; tag! If it wasn't for React, the PascalCase offspring of the long forgotten originator of all things web would not exist: the shiny new &lt;code&gt;&amp;lt;Blink&amp;gt;&lt;/code&gt; component!¹&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Did I forget what? Come on, need I mention the &lt;strong&gt;F word&lt;/strong&gt;? To its credit, it was the best way to make &lt;em&gt;images&lt;/em&gt; blink for quite a long while though!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;¹ The implementation thereof is left as an exercise for the reader.&lt;/p&gt;

</description>
      <category>jokes</category>
      <category>webdev</category>
      <category>react</category>
    </item>
    <item>
      <title>Three Cool Rakkas Features that Next.js Lacks</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Mon, 20 Dec 2021 13:02:13 +0000</pubDate>
      <link>https://dev.to/cyco130/three-cool-rakkas-features-that-nextjs-lacks-62m</link>
      <guid>https://dev.to/cyco130/three-cool-rakkas-features-that-nextjs-lacks-62m</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Frakkasjs%2Frakkasjs%2Fraw%2Fmain%2Flogo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Frakkasjs%2Frakkasjs%2Fraw%2Fmain%2Flogo.png" alt="Rakkas"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💃 &lt;a href="https://github.com/rakkasjs/rakkasjs" rel="noopener noreferrer"&gt;Rakkas&lt;/a&gt;, the lightning fast &lt;a href="https://nextjs.org" rel="noopener noreferrer"&gt;Next.js&lt;/a&gt; alternative powered by &lt;a href="https://vitejs.dev" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, has just released version 0.5.0 (check out &lt;a href="https://www.reddit.com/r/reactjs/comments/q7xtcg/rakkas_nextjs_alternative_powered_by_vite/" rel="noopener noreferrer"&gt;my previous post&lt;/a&gt; for an introduction). Although it replicates or offers alternatives for most Next.js features (like file-system router, server-side rendering, data fetching, and static site generation), Next.js still has many features that &lt;a href="https://rakkasjs.org/guide/feature-comparison" rel="noopener noreferrer"&gt;Rakkas doesn't support yet&lt;/a&gt;. This new release closes the gap, if by a little, by providing &lt;strong&gt;much improved SSR support&lt;/strong&gt; (thanks to Vite 2.7), implementing &lt;strong&gt;rendering modes&lt;/strong&gt; (static, server-side, or client-side) and support for &lt;strong&gt;deploying on serverless environments&lt;/strong&gt;. But it's not all about playing catch-up: Rakkas does offer &lt;strong&gt;a few cool features&lt;/strong&gt; of its own that &lt;strong&gt;Next.js lacks&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Deploy on Cloudflare Workers
&lt;/h2&gt;

&lt;p&gt;The new Rakkas release brings first-class support for deploying on &lt;strong&gt;&lt;a href="https://vercel.com/docs/concepts/functions/introduction#serverless-functions" rel="noopener noreferrer"&gt;Vercel serverless functions&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://www.netlify.com/products/functions/" rel="noopener noreferrer"&gt;Netlify functions&lt;/a&gt;&lt;/strong&gt;, and &lt;strong&gt;&lt;a href="https://workers.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt;&lt;/strong&gt;. Vercel, the makers of Next.js, is of course the go-to option for Next.js apps. Netlify is also a popular deployment target. But Cloudflare Workers is an &lt;strong&gt;entirely different beast&lt;/strong&gt;: It is a lighter-weight and cheaper solution compared to container-based serverless functions offerings. But the environment is much more similar to service workers in browsers than to Node. Next.js, being &lt;strong&gt;closely tied to Node&lt;/strong&gt;, doesn't support Cloudflare Workers as of yet. Following &lt;a href="https://kit.svelte.dev/" rel="noopener noreferrer"&gt;Svelte Kit&lt;/a&gt;'s lead, Rakkas was designed with this use case in mind from the get-go.&lt;/p&gt;

&lt;p&gt;Note that we're not talking about Cloudflare &lt;em&gt;Pages&lt;/em&gt;, which Next.js supports as a &lt;strong&gt;static&lt;/strong&gt; deployment target, but actual server-rendered (well, technically speaking “&lt;em&gt;edge&lt;/em&gt;-rendered”) React applications with support for API routes. This is a rare feature among React frameworks (I'm only aware of &lt;a href="https://flareact.com/" rel="noopener noreferrer"&gt;Flareact&lt;/a&gt; and &lt;a href="https://github.com/frandiox/vitedge" rel="noopener noreferrer"&gt;Vitedge&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Check out the updated &lt;a href="https://github.com/rakkasjs/rakkas-realworld" rel="noopener noreferrer"&gt;Rakkas RealWorld Demo&lt;/a&gt; to see how the same full-stack application is deployed on &lt;a href="https://rakkas-realworld.vercel.app/" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt;, &lt;a href="https://rakkas-realworld.netlify.app/" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt;, &lt;a href="https://rakkas-realworld.rakkasjs.workers.dev/" rel="noopener noreferrer"&gt;Cloudflare Workers&lt;/a&gt;, and &lt;a href="https://realworld.rakkasjs.org/" rel="noopener noreferrer"&gt;Node&lt;/a&gt;; all connected to the same Postgres database hosted on &lt;a href="https://supabase.com/" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;. In serverless versions, authentication requests are proxied to the Node server because CPU time limit may be too low to allow secure password hashing. They also use the &lt;a href="https://www.prisma.io/docs/concepts/components/prisma-data-platform#prisma-data-proxy" rel="noopener noreferrer"&gt;Prisma data proxy&lt;/a&gt; for accessing the database while the Node server uses Prisma directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Localize your URLs
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://rakkasjs.org/guide/localized-routes" rel="noopener noreferrer"&gt;This new Rakkas feature&lt;/a&gt; allows you to have localized URLs: Say you have a website with a “Home” page (&lt;code&gt;/en&lt;/code&gt;), an “About us” page (&lt;code&gt;/en/about-us&lt;/code&gt;) and a “Products” page (&lt;code&gt;/en/products&lt;/code&gt;). In a multilingual site, you'd probably want your French language URLs to be something like &lt;code&gt;/fr&lt;/code&gt;, &lt;code&gt;/fr/qui-sommes-nous&lt;/code&gt; and &lt;code&gt;/fr/produits&lt;/code&gt;. Don't get hung up on the exact URL structure, you have complete control: You can omit the prefixes in English pages, put different versions on a different subdomain (&lt;code&gt;fr.example.com&lt;/code&gt;) or domain (&lt;code&gt;example.fr&lt;/code&gt;), or opt out of URL translation (&lt;code&gt;/fr/about-us&lt;/code&gt;) for instance. All you need to provide is a function that parses the language from the URL and translates the localized URL into a default form that Rakkas will use to determine what to render.&lt;/p&gt;

&lt;p&gt;If you need an international landing pages (say &lt;code&gt;/&lt;/code&gt; redirects to &lt;code&gt;/en&lt;/code&gt; or &lt;code&gt;/fr&lt;/code&gt; based on the visitor's language preference), Rakkas also offers optional &lt;code&gt;Accept-Language&lt;/code&gt; and &lt;code&gt;navigator.languages&lt;/code&gt;  based language detection and redirection that can be overridden by a cookie. It even works on static sites where JavaScript redirection is used. Rakkas will render a list of links to the localized URLs when JavaScript is disabled.&lt;/p&gt;

&lt;p&gt;Next.js &lt;a href="https://nextjs.org/docs/advanced-features/i18n-routing" rel="noopener noreferrer"&gt;does have a feature&lt;/a&gt; to detect the language from the URL but the URLs cannot be rewritten: You will have &lt;code&gt;/fr/about&lt;/code&gt; and &lt;code&gt;/fr/products&lt;/code&gt; (or &lt;code&gt;example.fr/about&lt;/code&gt; etc.) which is, frankly, of limited use.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Nest your layouts
&lt;/h2&gt;

&lt;p&gt;Not many websites consist of completely independent pages. Most sites have shared elements like a header, a footer, and a navigation menu that are common to many pages. Rakkas provides a nested layout system to handle this use case and more. You can have a &lt;code&gt;layout.jsx&lt;/code&gt; (or &lt;code&gt;tsx&lt;/code&gt;) and all pages in the same directory and pages or nested layouts in its subdirectories will be wrapped in your layout component.&lt;/p&gt;

&lt;p&gt;Next.js has a &lt;a href="https://nextjs.org/docs/basic-features/layouts" rel="noopener noreferrer"&gt;similar feature&lt;/a&gt; but Rakkas layouts can nest arbitrarily by following the same filesystem-based organization as pages, they can have their own blocking data fetchers for static/server-side rendering, and they can provide shared data for lower level layouts and pages via the &lt;a href="https://rakkasjs.org/guide/layout-context" rel="noopener noreferrer"&gt;layout context&lt;/a&gt; feature. They can also abort the rendering of the current page by returning an error or redirecting to another page, useful for implementing login protected pages for instance.&lt;/p&gt;

&lt;p&gt;Pages don't even need to share a common URL path prefix to share a layout: You can put pages that should share a layout under a directory with a name that starts with an underscore (&lt;code&gt;_&lt;/code&gt;). For example you could have a directory structure like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pages/&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;layout.jsx&lt;/code&gt; (the main layout, common to all pages)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_app/&lt;/code&gt; (layout group for most pages)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;layout.jsx&lt;/code&gt; (common layout for most pages)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;page.jsx&lt;/code&gt; (path: &lt;code&gt;/&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;about.page.jsx&lt;/code&gt; (path: &lt;code&gt;/about&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;blog.page.jsx&lt;/code&gt; (path: &lt;code&gt;/blog&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;_admin/&lt;/code&gt; (layout group for admin pages)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;layout.jsx&lt;/code&gt; (common layout for admin pages)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;settings.page.jsx&lt;/code&gt; (path: &lt;code&gt;/settings&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;users.page.jsx&lt;/code&gt; (path: &lt;code&gt;/users&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;We all know Next.js is awesome. But Rakkas does put a few unique features on the table. It's still young and not yet ready for production use but &lt;a href="https://stackblitz.com/edit/rakkas-demo-ts?file=src%2Fpages%2Fpage.tsx" rel="noopener noreferrer"&gt;go ahead and give it a try&lt;/a&gt; and share your thoughts. &lt;a href="https://github.com/rakkasjs/rakkasjs/stargazers" rel="noopener noreferrer"&gt;Star us on Github&lt;/a&gt; and &lt;a href="https://twitter.com/intent/tweet?text=I%20gave%20%23RakkasJS%20a%20try!&amp;amp;url=https%3A%2F%2Fgithub.com%2Frakkasjs%2Frakkasjs" rel="noopener noreferrer"&gt;talk about Rakkas&lt;/a&gt; if you like what you see. If you have any questions, problems, or suggestions &lt;a href="https://github.com/rakkasjs/rakkasjs/issues/new" rel="noopener noreferrer"&gt;open a Github issue&lt;/a&gt;. If you want to &lt;a href="https://github.com/rakkasjs/rakkasjs/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contribute&lt;/a&gt;, fork and send a pull request. Check out the &lt;a href="https://github.com/rakkasjs/rakkasjs/issues" rel="noopener noreferrer"&gt;open issues&lt;/a&gt; to see how you can help. &lt;strong&gt;All feedback is welcome&lt;/strong&gt;, positive or negative.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>vite</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>How to get client-side navigation right</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Mon, 13 Dec 2021 20:50:51 +0000</pubDate>
      <link>https://dev.to/cyco130/how-to-get-client-side-navigation-right-2adh</link>
      <guid>https://dev.to/cyco130/how-to-get-client-side-navigation-right-2adh</guid>
      <description>&lt;p&gt;How many times have you ctrl-clicked (or cmd-clicked) on a link to open it in a new tab but it opened in the current tab or didn't open at all? How many times have you clicked on a link in a long document and when you clicked back it took you to the top of the document instead of where you had left? Client-side navigation bugs are so widespread that it's hard to believe the technique is almost 20 years old! So I decided to write down all the issues I've ever encountered and build &lt;a href="https://github.com/cyco130/knave"&gt;a library&lt;/a&gt; that tries to solve them once and for all.&lt;/p&gt;

&lt;p&gt;Normally, when you click on a link, your browser loads a new page from the URL specified in the &lt;code&gt;href&lt;/code&gt; attribute of your link (an &lt;code&gt;a&lt;/code&gt; or &lt;code&gt;area&lt;/code&gt; element).  &lt;strong&gt;Client-side navigation&lt;/strong&gt; refers to the practice of using JavaScript to control page transitions &lt;strong&gt;without a full reload&lt;/strong&gt;, which usually results in a snappier user experience. Despite its popularity, many implementations are broken or lacking: history manipulation, scroll restoration, ctrl + click / cmd + click / right click behavior, loading state handling etc. are often buggy or non-existent. In many cases this actually makes the user experience &lt;em&gt;worse&lt;/em&gt; than classic navigation by breaking user expectations.&lt;/p&gt;

&lt;p&gt;Having appeared in early 2000s, the practice has ushered in the era of Single Page Applications (SPAs). Earliest attempts used the &lt;code&gt;#hash&lt;/code&gt; part of the URL and the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onhashchange"&gt;&lt;code&gt;window.onhashchange&lt;/code&gt;&lt;/a&gt; event. Since it is normally used for scrolling to a specific section of a document, a hash-only navigation doesn't cause a full page reload. Developers took advantage of this to implement client-side navigation with history (back/forward buttons) support. In early 2010s, the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/History_API"&gt;history API&lt;/a&gt; support landed in popular browsers which allowed using real URL paths instead of hashes.&lt;/p&gt;

&lt;p&gt;Despite a whole decade that has passed since the arrival of the history API, there are still a myriad of challenges to be solved when implementing client-side navigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Intercepting history changes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate"&gt;&lt;code&gt;window.onpopstate&lt;/code&gt;&lt;/a&gt; event is fired when the user clicks on the back/forward buttons or one of the &lt;code&gt;back&lt;/code&gt;, &lt;code&gt;forward&lt;/code&gt; or &lt;code&gt;go&lt;/code&gt; methods of the &lt;code&gt;history&lt;/code&gt; API is called. &lt;code&gt;location&lt;/code&gt; and &lt;code&gt;history.state&lt;/code&gt; (which is a place you can store extra data about the current location) are updated &lt;em&gt;before&lt;/em&gt; the &lt;code&gt;onpopstate&lt;/code&gt; event is fired.&lt;/p&gt;

&lt;p&gt;Unfortunately this event is not fired when &lt;code&gt;history.pushState&lt;/code&gt; or &lt;code&gt;history.replaceState&lt;/code&gt; is called. This means that a client-side library solution must provide its own navigation function, because, barring &lt;a href="https://stackoverflow.com/a/4585031"&gt;horrible hacks&lt;/a&gt;, it has no way to be notified when the user of the library calls these methods.&lt;/p&gt;

&lt;p&gt;It's not fired when the user clicks on a link either. This means that we have to listen to the click events to prevent the default behavior and handle the navigation ourselves.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dedicated &lt;code&gt;Link&lt;/code&gt; component vs global click handler
&lt;/h2&gt;

&lt;p&gt;Preventing the browser's default behavior when the user clicks on a link can be achieved in two ways: 1) by providing a dedicated &lt;code&gt;Link&lt;/code&gt; component that renders an &lt;code&gt;a&lt;/code&gt; element with an attached an &lt;code&gt;onclick&lt;/code&gt; handler, or 2) by attaching a global &lt;code&gt;onclick&lt;/code&gt; handler to the &lt;code&gt;body&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;The first approach has the advantage of being &lt;strong&gt;explicit&lt;/strong&gt;: There are no surprises. Next.js and React Router both follow this approach. Opting out of client-side navigation is trivial: Just use a plain &lt;code&gt;a&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;The second approach is &lt;strong&gt;implicit&lt;/strong&gt; but it is easier to use in most cases: Sometimes you don't control the HTML contents of a page. Maybe it was rendered from Markdown residing in a database or a CMS. It may be hard or impossible to control the rendered &lt;code&gt;a&lt;/code&gt; elements in such cases. SvelteKit uses this second approach. Opting out of client-side navigation is still possible: We can interpret, for instance, the presence of a &lt;code&gt;rel="external"&lt;/code&gt; attribute as a signal for letting the browser handle the navigation. The downside of the second approach is that one has to be careful about event handling order. If you attach an &lt;code&gt;onclick&lt;/code&gt; handler to the &lt;code&gt;a&lt;/code&gt; element, it will run &lt;em&gt;after&lt;/em&gt; the global one which may not be what you want. You have to use &lt;code&gt;{ capture: true }&lt;/code&gt; if you want to alter the click behavior of a link.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A third, hybrid approach is also possible&lt;/strong&gt;: We can implement a &lt;code&gt;LinkContainer&lt;/code&gt; component that captures the &lt;code&gt;onclick&lt;/code&gt; events of the &lt;code&gt;a&lt;/code&gt; elements that it contains. It solves the “pre-rendered HTML that we don't control” problem while remaining fairly explicit.&lt;/p&gt;

&lt;p&gt;Whichever approach we choose, a &lt;code&gt;Link&lt;/code&gt; component is still useful for styling active (or pending) links differently, a nice feature to have in navigation menus for instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing when not to interfere
&lt;/h2&gt;

&lt;p&gt;When listening to &lt;code&gt;onclick&lt;/code&gt; events, it is important to know when to leave the handling to the browser. The following cases should be considered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was &lt;code&gt;preventDefault()&lt;/code&gt; called before our handler?&lt;/li&gt;
&lt;li&gt;Does the &lt;code&gt;a&lt;/code&gt; element have a &lt;code&gt;href&lt;/code&gt; attribute at all?&lt;/li&gt;
&lt;li&gt;Was it a left click? Right click and middle click usually have other functions.&lt;/li&gt;
&lt;li&gt;Were any of the modifier keys pressed? Ctrl, shift, alt, meta, command etc. keys are used to trigger alternative functions like opening in a new tab or window.&lt;/li&gt;
&lt;li&gt;Does the &lt;code&gt;a&lt;/code&gt; element have a &lt;code&gt;target&lt;/code&gt; attribute whose value is not &lt;code&gt;_self&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Does the &lt;code&gt;a&lt;/code&gt; element have a &lt;code&gt;download&lt;/code&gt; attribute?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of these conditions is met, we should let the browser handle the event.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pending navigation
&lt;/h2&gt;

&lt;p&gt;Very simple apps can render a new page synchronously but transitioning from one page to another usually has to be asynchronous in real-world use cases. Modern bundlers support code splitting and pages are natural code splitting boundaries. Loading the code for the next page is an asynchronous operation. Also, you usually need to fetch some data before rendering a page. This is also an asynchronous operation.&lt;/p&gt;

&lt;p&gt;During classic navigation, most browsers keep showing the old page along with some kind of loading state indicator until the new one loads. This is much more useful than showing a blank loading page. Ideally, a client-side navigation solution should replicate this behavior.&lt;/p&gt;

&lt;p&gt;The requirement of supporting asynchronous navigation causes a very subtle complication: Inevitably there will be a moment where &lt;code&gt;location.href&lt;/code&gt; does not match currently rendered page contents. This may cause mismatches in links with relative URLs: Say you are on page &lt;code&gt;/foo&lt;/code&gt; and you initiate a client-side navigation to &lt;code&gt;/foo/bar&lt;/code&gt;. If there is a link whose &lt;code&gt;href&lt;/code&gt; is &lt;code&gt;baz&lt;/code&gt; (a relative link), it will be pointing to &lt;code&gt;/foo/baz&lt;/code&gt; instead of &lt;code&gt;/baz&lt;/code&gt; while the navigation is underway. One way to solve this problem is to have a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base"&gt;&lt;code&gt;base&lt;/code&gt; element&lt;/a&gt; in the document head whose &lt;code&gt;href&lt;/code&gt; property is always kept in sync with the currently rendered location.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scroll restoration
&lt;/h2&gt;

&lt;p&gt;Classic navigation has support for scroll restoration: When the user navigates back or forward, the browser will restore the scroll position. This behavior has to be simulated when using client-side navigation.&lt;/p&gt;

&lt;p&gt;Modern browsers have support for &lt;code&gt;history.scrollRestoration&lt;/code&gt; which can be set to &lt;code&gt;manual&lt;/code&gt; or &lt;code&gt;auto&lt;/code&gt;. The former is the default value and means that the browser will not restore the scroll position. You might think that you can set it to &lt;code&gt;auto&lt;/code&gt; and be done with it. Unfortunately, this is not the case if you have to support asynchronous rendering like we discussed above. The scroll position needs to be restored &lt;em&gt;after&lt;/em&gt; the new page has been rendered in its entirety. Consider this scenario: You're at the bottom of a page that has contents that don't fit in the viewport (&lt;code&gt;/long&lt;/code&gt;). You navigate to a page that does fit (&lt;code&gt;/short&lt;/code&gt;). When you click back, automatic scroll restoration will try to scroll to the original position but unless you are able to render &lt;code&gt;/long&lt;/code&gt; synchronously, it will fail because the contents of &lt;code&gt;/short&lt;/code&gt; will be showing while &lt;code&gt;/long&lt;/code&gt; is still loading and they fit the page so there's nowhere to scroll to.&lt;/p&gt;

&lt;p&gt;This problem severely reduces the usefulness of &lt;code&gt;history.scrollRestoration&lt;/code&gt;. A decent client-side navigation solution must set it to &lt;code&gt;manual&lt;/code&gt; and handle scroll restoration manually, &lt;em&gt;after&lt;/em&gt; the new page has been fully rendered. One way to approach this is to assign a unique ID to each location, keeping track of it in &lt;code&gt;history.state&lt;/code&gt; and use it as a &lt;code&gt;sessionStorage&lt;/code&gt; key to store the scroll position.&lt;/p&gt;

&lt;p&gt;One more point to remember when implementing scroll restoration is to be careful about not breaking the normal behavior of &lt;code&gt;#hash&lt;/code&gt; links.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blocking navigation
&lt;/h2&gt;

&lt;p&gt;Classic navigation has limited support for navigation blocking in the form of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload"&gt;&lt;code&gt;onbeforeunload&lt;/code&gt; event&lt;/a&gt;. When set up correctly, it will show a confirmation dialog before navigating away from the current page. This is useful to remind the user that they might lose unsaved data.&lt;/p&gt;

&lt;p&gt;When using client-side navigation, we can show a custom dialog box in some cases. This requires “canceling” the navigation when the user decides to stay on the page. The challenge here is that, when the user clicks the back or forward button, &lt;code&gt;location.href&lt;/code&gt; is already updated by the time the &lt;code&gt;onpopstate&lt;/code&gt; event is called. This means that we don't know if we should go back or forward to cancel the navigation. To solve this, we can use &lt;code&gt;history.state&lt;/code&gt; to keep track of the current location's history index and compare it to the last rendered index in order to compute a delta value to pass to &lt;code&gt;history.go&lt;/code&gt; for “taking back” the navigation attempt. Then we can show a dialog box to ask the user if they really want to leave the page. If the answer is no, we stop, if the answer is yes, we redo the navigation using &lt;code&gt;history.go(-delta)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We still need an &lt;code&gt;onbeforeunload&lt;/code&gt; fallback in case the user clicks on a hard link or simply closes the tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knave
&lt;/h2&gt;

&lt;p&gt;Having failed in finding a simple library that provides all of these features, I've created &lt;a href="https://github.com/cyco130/knave"&gt;&lt;code&gt;knave&lt;/code&gt;&lt;/a&gt;, a framework-agnostic client-side navigation library in order to address all of these challenges once and for all. The &lt;code&gt;knave-react&lt;/code&gt; package contains its React bindings. PRs that implement bindings for other frameworks are welcome.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>react</category>
      <category>ux</category>
    </item>
    <item>
      <title>Rakkas: Next.js alternative powered by Vite</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Thu, 14 Oct 2021 11:04:41 +0000</pubDate>
      <link>https://dev.to/cyco130/rakkas-nextjs-alternative-powered-by-vite-4n78</link>
      <guid>https://dev.to/cyco130/rakkas-nextjs-alternative-powered-by-vite-4n78</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/rakkasjs/rakkasjs"&gt;Rakkas&lt;/a&gt;, the lightning fast &lt;a href="https://nextjs.org"&gt;Next.js&lt;/a&gt; alternative powered by &lt;a href="https://vitejs.dev"&gt;Vite&lt;/a&gt;, has just released version 0.4.0.&lt;/p&gt;

&lt;p&gt;If you're not familiar with Vite, it's an instance of a new class of tools, sometimes called &lt;strong&gt;non-bundlers&lt;/strong&gt;, that leverage modern browsers' native EcmaScript modules support to &lt;strong&gt;do away with the bundling&lt;/strong&gt; during development. This results in &lt;strong&gt;instant server start&lt;/strong&gt; and &lt;strong&gt;much faster updates&lt;/strong&gt; than traditional bundlers like &lt;a href="https://webpack.js.org"&gt;Webpack&lt;/a&gt;, &lt;a href="https://rollupjs.org"&gt;Rollup&lt;/a&gt;, or &lt;a href="https://parceljs.org"&gt;Parcel&lt;/a&gt;. These new tools (another example is &lt;a href="https://www.snowpack.dev"&gt;Snowpack&lt;/a&gt;) improve developer experience so much that, once you try, you will never want to go back to your old tooling.&lt;/p&gt;

&lt;p&gt;Vite (and therefore Rakkas) supports &lt;strong&gt;TypeScript&lt;/strong&gt;, &lt;strong&gt;JSX&lt;/strong&gt;, &lt;strong&gt;hot module replacement&lt;/strong&gt; with &lt;strong&gt;React fast refresh&lt;/strong&gt;, &lt;strong&gt;CSS modules&lt;/strong&gt;, &lt;strong&gt;PostCSS&lt;/strong&gt;, &lt;strong&gt;CSS preprocessors&lt;/strong&gt; (just install &lt;code&gt;sass&lt;/code&gt;, &lt;code&gt;less&lt;/code&gt;, or &lt;code&gt;stylus&lt;/code&gt;), and handling of &lt;strong&gt;static assets&lt;/strong&gt; out of the box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rakkas&lt;/strong&gt; is a &lt;strong&gt;MIT-licensed&lt;/strong&gt; &lt;a href="https://reactjs.org"&gt;React&lt;/a&gt; framework &lt;em&gt;similar to&lt;/em&gt; Next.js, it's not a clone. In fact, if you're familiar with &lt;a href="https://svelte.dev"&gt;Svelte&lt;/a&gt; -a user interface framework (like React or Vue)- many of Rakkas's features are inspired by &lt;a href="https://kit.svelte.dev"&gt;SvelteKit&lt;/a&gt;, Svelte's web application building tool.&lt;/p&gt;

&lt;p&gt;Important features of Rakkas are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;⚡  Lightning fast development server&lt;/li&gt;
&lt;li&gt;📁  &lt;a href="https://rakkasjs.org/guide/navigation"&gt;Intuitive file system-based routing&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🖥️  Hassle free server-side rendering&lt;/li&gt;
&lt;li&gt;📄  &lt;a href="https://rakkasjs.org/guide/static-site-generation"&gt;Static site generation&lt;/a&gt; &lt;strong&gt;(NEW!)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;☸️  SPA-style &lt;a href="https://rakkasjs.org/guide/navigation"&gt;client-side navigation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⬇️  Simple but effective &lt;a href="https://rakkasjs.org/guide/data-fetching"&gt;data fetching system&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⚙️  &lt;a href="https://rakkasjs.org/guide/api-routes"&gt;API routes&lt;/a&gt; to build and organize your backend&lt;/li&gt;
&lt;li&gt;📚  Extensive &lt;a href="https://rakkasjs.org/guide"&gt;documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The file system router maps your source files to web pages. For example, if you want a  page &lt;code&gt;/about&lt;/code&gt;, you just default-export a React component from &lt;code&gt;src/pages/about.page.jsx&lt;/code&gt; or &lt;code&gt;src/pages/about/page.jsx&lt;/code&gt;. Route parameters are also encoded in the file name: &lt;code&gt;src/pages/users/[userName].page.jsx&lt;/code&gt; is rendered for &lt;code&gt;/users/jane&lt;/code&gt;, &lt;code&gt;/users/joe&lt;/code&gt;, and so forth. In this case, your component receives a &lt;code&gt;params&lt;/code&gt; prop and the user name is available as &lt;code&gt;params.userName&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Rakkas supports nested &lt;a href="https://rakkasjs.org/guide/layouts"&gt;layouts&lt;/a&gt; for elements common to more than one page, like headers, footers, navigation bars etc. Each page or layout can &lt;a href="https://rakkasjs.org/guide/data-fetching"&gt;fetch its own data&lt;/a&gt; using an isomorphic &lt;code&gt;fetch&lt;/code&gt; function. For advanced use cases, you can implement your own &lt;a href="https://rakkasjs.org/guide/load-helpers"&gt;data fetching helper&lt;/a&gt; functions or classes. The implementation can be different for the server and the client, opening up the possibility of avoiding the network roundtrip and serialization/deserialization while doing SSR.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://rakkasjs.org/guide/api-routes"&gt;API routes&lt;/a&gt; have a file system router similar to pages: &lt;code&gt;/src/api/users.endpoint.js&lt;/code&gt; implements the endpoint &lt;code&gt;/api/users&lt;/code&gt;. Functions exported from this file handle similarly named HTTP methods (i.e. &lt;code&gt;get&lt;/code&gt; for &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;post&lt;/code&gt; for &lt;code&gt;POST&lt;/code&gt; etc.) by taking a request object and returning a response object. You can also define middlewares for impementing abstractions relating to more than one endpoint.&lt;/p&gt;

&lt;p&gt;If you're reading this on a Chromium-based desktop browser, &lt;a href="https://stackblitz.com/edit/rakkas-demo-ts?file=src%2Fpages%2Fpage.tsx"&gt;you can try Rakkas online on StackBlitz&lt;/a&gt;. For a full-on experience, Rakkas project generator &lt;code&gt;create-rakkas-app&lt;/code&gt; comes with lots of features, all optional: &lt;strong&gt;TypeScript&lt;/strong&gt;, &lt;strong&gt;ESLint&lt;/strong&gt;, &lt;strong&gt;StyleLint&lt;/strong&gt;, &lt;strong&gt;Prettier&lt;/strong&gt;, &lt;strong&gt;Cypress&lt;/strong&gt;, and &lt;strong&gt;jest&lt;/strong&gt; (for unit testing and/or API testing). It supports &lt;code&gt;yarn&lt;/code&gt; and &lt;code&gt;pnpm&lt;/code&gt; in addition to &lt;code&gt;npm&lt;/code&gt;. To get started, just create a new project directory and run &lt;code&gt;npx create-rakkas-app@latest&lt;/code&gt;, then follow the prompt. The optional demo app demonstrates a toy fullstack todo app that communicates via a simple REST API.&lt;/p&gt;

&lt;p&gt;If you want to go further, there are integration examples with other popular tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/rakkasjs/rakkasjs/tree/main/examples/styled-components"&gt;Styled Components&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rakkasjs/rakkasjs/tree/main/examples/tailwind"&gt;Tailwind CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rakkasjs/rakkasjs/tree/main/examples/mdx"&gt;MDX&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rakkasjs/rakkasjs/tree/main/examples/apollo-graphql"&gt;Apollo GraphQL (both client and server)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is also a &lt;a href="https://github.com/rakkasjs/rakkas-realworld"&gt;RealWorld port&lt;/a&gt; (Rakkas implementation of the &lt;a href="https://github.com/gothinkster/realworld"&gt;RealWorld specification&lt;/a&gt;), a simple but complete fullstack application demonstrating how to approach building a REST API, accessing your database (via &lt;a href="https://www.prisma.io"&gt;Prisma&lt;/a&gt;), handling authentication, testing, and more.&lt;/p&gt;

&lt;p&gt;Rakkas is still young and is not ready for production yet. But &lt;a href="https://stackblitz.com/edit/rakkas-demo-ts?file=src%2Fpages%2Fpage.tsx"&gt;go ahead and give it a try&lt;/a&gt; and share your thoughts. &lt;a href="https://github.com/rakkasjs/rakkasjs/stargazers"&gt;Star us on Github&lt;/a&gt; and &lt;a href="https://twitter.com/intent/tweet?text=I%20gave%20%23RakkasJS%20a%20try!&amp;amp;url=https%3A%2F%2Fgithub.com%2Frakkasjs%2Frakkasjs"&gt;talk about Rakkas&lt;/a&gt; if you like what you see. If you have any questions, problems, or suggestions &lt;a href="https://github.com/rakkasjs/rakkasjs/issues/new"&gt;open a Github issue&lt;/a&gt;. If you want to &lt;a href="https://github.com/rakkasjs/rakkasjs/blob/main/CONTRIBUTING.md"&gt;contribute&lt;/a&gt;, fork and send a pull request. &lt;strong&gt;All feedback is welcome&lt;/strong&gt;, positive or negative.&lt;/p&gt;

</description>
      <category>vite</category>
      <category>nextjs</category>
      <category>react</category>
    </item>
    <item>
      <title>Rakkas can now power fullstack RealWorld</title>
      <dc:creator>Fatih Aygün</dc:creator>
      <pubDate>Sun, 22 Aug 2021 20:47:10 +0000</pubDate>
      <link>https://dev.to/cyco130/rakkas-can-now-power-fullstack-realworld-3c59</link>
      <guid>https://dev.to/cyco130/rakkas-can-now-power-fullstack-realworld-3c59</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/rakkasjs/rakkasjs"&gt;&lt;strong&gt;Rakkas&lt;/strong&gt;&lt;/a&gt; “the dancing web framework” is a web framework built on &lt;strong&gt;&lt;a href="https://reactjs.org/"&gt;React&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://vitejs.dev/"&gt;Vite&lt;/a&gt;&lt;/strong&gt;. &lt;strong&gt;&lt;a href="https://github.com/gothinkster/realworld"&gt;RealWorld&lt;/a&gt;&lt;/strong&gt; is a demo app specification with more than 100 implementations in various frontend and backend frameworks. The just released &lt;strong&gt;version 0.3.0&lt;/strong&gt; of Rakkas now has a feature set rich enough to power a &lt;strong&gt;fullstack&lt;/strong&gt; implementation of RealWorld which works even with &lt;strong&gt;JavaScript disabled&lt;/strong&gt; (a good test of Rakkas's SSR abilities). &lt;a href="https://realworld.rakkasjs.org/"&gt;See it in action!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This port served as a test bed while developing the new version of Rakkas: We &lt;strong&gt;fixed the bugs&lt;/strong&gt; we stumbled upon, we &lt;strong&gt;added features&lt;/strong&gt; we missed, and we &lt;strong&gt;redesigned APIs&lt;/strong&gt; we deemed unergonomic.&lt;/p&gt;

&lt;p&gt;It also served as a test bed for establishing Rakkas best practices: &lt;strong&gt;Session management&lt;/strong&gt;, &lt;strong&gt;authentication&lt;/strong&gt;, unit testing, API testing, end-to-end testing, data validation, database access, and many other things that need to be done in a real world application has been tackled. See the &lt;a href="https://github.com/rakkasjs/rakkas-realworld"&gt;github repo&lt;/a&gt; for a more in depth explanation of how everything works.&lt;/p&gt;

&lt;p&gt;The new version also comes with &lt;strong&gt;&lt;a href="https://rakkasjs.org/guide"&gt;much expanded documentation&lt;/a&gt;&lt;/strong&gt; and the next version will focus on &lt;strong&gt;static site generation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although still in early development and not ready for production use, we feel that this release puts Rakkas on the map as an increasingly &lt;strong&gt;promising contender&lt;/strong&gt; for building real-world web applications. So go ahead and give it a try: &lt;a href="https://stackblitz.com/edit/rakkas-demo-ts?file=src/pages/page.tsx"&gt;you can even do it in your browser&lt;/a&gt;!&lt;/p&gt;

</description>
      <category>react</category>
      <category>vite</category>
      <category>ssr</category>
    </item>
  </channel>
</rss>
