<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Mind of Lloyd - Lloyd Miller's Blog]]></title><description><![CDATA[I'm a freelance web developer that just absolutely loves Laravel and the JAMstack. On this blog I will write about coding and other topics that are tech-related.]]></description><link>https://blog.lloydmiller.dev</link><generator>RSS for Node</generator><lastBuildDate>Mon, 20 Apr 2026 19:26:01 GMT</lastBuildDate><atom:link href="https://blog.lloydmiller.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Creating Admitr (Day 1)]]></title><description><![CDATA[Welcome to the first entry in this series. I'll be detailing my journey in making my first consumer-facing product: Admitr. Admitr is an idea I came up with last holiday, and since then it's gone through some iterations in my head. With the growth of...]]></description><link>https://blog.lloydmiller.dev/creating-admitr-day-1</link><guid isPermaLink="true">https://blog.lloydmiller.dev/creating-admitr-day-1</guid><category><![CDATA[Laravel]]></category><category><![CDATA[React]]></category><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[side project]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Fri, 21 May 2021 16:02:59 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1621607519162/Ew-rIBj5A.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Welcome to the first entry in this series. I'll be detailing my journey in making my first consumer-facing product: <a target="_blank" href="https://admitr.com">Admitr</a>. Admitr is an idea I came up with last holiday, and since then it's gone through some iterations in my head. With the growth of the creator economy, I believed Admitr could be something big. I lost some motivation around February but decided to resume last week. To hold myself accountable, I'll be documenting my journey in making this app. I have another SaaS idea that I plan to document as well.</p>
<p>Right now, my motive for finishing is not really to make a viable business but to just use this as a proof-of-concept. If a viable business comes from my efforts, well so be it. </p>
<p><strong>The idea: </strong> I'm creating an app that will allow users to make money through the platforms they already use, such as Zoom and Instagram. Of course, there's more to it, but that's the gist.</p>
<p><strong>My first stack:</strong> I started this project with a HUGE stack: Laravel and NuxtJS. In actuality, each of these technologies is full-stack. So why put them together? </p>
<p>Well, I love Laravel. Plus, Laravel has far superior tools with authentication and API building. Ok so why not just use Blade as your frontend?</p>
<p>Well, I have nothing against Blade, it's just that I hardly ever use it and I preferred to use a JavaScript framework in the frontend to make frontend logic much easier to work with. </p>
<p>I used NuxtJS on a previous project and wanted to use it again. It was my first serious foray into Vue and I liked it. I enjoyed building with this stack until I needed to do state management. It was then that I realized that my knowledge in Vue wasn't robust enough. I also realized how little I cared to learn more, so I decided to switch to React.</p>
<ul>
<li>On the flip side, it was from this stack that I wrote one of my first blog posts <a target="_blank" href="https://blog.lloydmiller.dev/zoom-oauth-with-a-nuxtjs-app-part-1">first blog post on Hashnode</a>!</li>
</ul>
<p><strong>My second stack:</strong> I switched this project to Laravel and NEXT.js. Why NEXT?</p>
<p>I love React frameworks like NEXT.js and Gatsby and I've seen the power of JAMstack sites last year. I really wanted to use NEXT.js in a real-world project and I thought this would've been the perfect time. NEXT has so many great tools, including the fact that I could mix SSG and SSR pages. With NEXT I could make the website performant without much work.</p>
<p>To be honest, working through this stack was comparatively a breeze. However, it was here that I lost all motivation completely. The reason is pretty simple: it was too much work! One of the problems was with implementing APIs like Zoom. Zoom needs a backend to interact with, but using my Laravel API to do this would've been more work, so I ended up just using the server-side of NEXT for this. It wasn't too hard since it's basically Node, but it's a lot of work.</p>
<p>Translating from Vue to React was one thing, but when I realized the scale of work to implement proper authentication in both the frontend and backend and other things that make an app an app, my mind just decided to take a rest... indefinitely.</p>
<p><strong>The stack now:</strong> I tried Laravel Jetstream and Laravel Breeze when they were released. Both times I tried the Inertia stack and both times I hated the experience. I hated how I had to go to multiple places to learn how to use this stack. However, in March, I watched a live stream of someone building an app with Laravel and Inertia and I decided to give it another shot (FYI, Inertia JS is the glue that connects the React/Vue/Svelte frontend with the backend without any need for an API).</p>
<p>I still was not a fan of the docs when I tried it again, but I understood a lot more than before. Not long after, something I thought would never happen, happened: I enjoyed it! It wasn't long until I <a target="_blank" href="https://blog.lloydmiller.dev/chat-room-app-with-react-and-laravel-breeze-and-twilios-conversations-api">wrote my first blog post</a> about making a Laravel Breeze project with Inertia. This is why I decided to restart the project with this stack.</p>
<p>There are a few pros of using this stack:</p>
<ul>
<li>Natural routing with PHP. No need to install additional React libraries for that.</li>
<li>No headaches with complex state and session management as PHP and Laravel have inbuilt tools for that.</li>
<li>Laravel Breeze makes authentication a breeze.</li>
<li>A dev can easily choose between React and Vue for the frontend. There's no more hard work to get React to fit in now because all we have to do is run <code>$ php artisan breeze:install react</code>.</li>
<li>Laravel Mix is excellent, and we can install just about any styling system and libraries with relative ease, such as Emotion and PostCSS.</li>
</ul>
<p><strong>What now?:</strong> This is the first part of an n-part series detailing my journey in making this project. I like the concept of creating products in the open and I hope I can help it become mainstream. In the next few days, I will be writing a post about the advantages of the LIR stack (Laravel-Inertia-React) compared with other React frameworks and stacks. I'll also be writing part 2 soon detailing the current setup so look out for that. </p>
]]></content:encoded></item><item><title><![CDATA[How I Made a Navbar Component in Gatsby (React) with TypeScript, and Twin Macro]]></title><description><![CDATA[If you don't know what Twin Macro is, check it out! It's a great library that blends Tailwind CSS and Styled Component systems, like Emotion. I love using Twin because it lets me use Tailwind while separating my styling from my markup and methods. If...]]></description><link>https://blog.lloydmiller.dev/how-i-made-a-navbar-component-in-gatsby-react-with-typescript-and-twin-macro</link><guid isPermaLink="true">https://blog.lloydmiller.dev/how-i-made-a-navbar-component-in-gatsby-react-with-typescript-and-twin-macro</guid><category><![CDATA[TypeScript]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[React]]></category><category><![CDATA[Tailwind CSS]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Thu, 29 Apr 2021 22:09:32 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619728870374/IkGcQVUYT.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>If you don't know what Twin Macro is, <a target="_blank" href="https://github.com/ben-rogerson/twin.macro">check it out</a>! It's a great library that blends Tailwind CSS and Styled Component systems, like Emotion. I love using Twin because it lets me use Tailwind while separating my styling from my markup and methods. If you're someone who likes using styled-components for your React projects, let me show you how I did it. By the way, even though I'm using Gatsby, just know that Twin Macro is compatible with create-react-app and Next.js.</p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li>For a beginner's look into Gatsby with TypeScript, <a target="_blank" href="https://blog.lloydmiller.dev/learning-typescript-with-gatsby">please check this article out</a>.</li>
<li>If you haven't already, install the Gatsby CLI by running <code>$ npm install -g gatsby-cli</code>. </li>
<li>Create a new repository by running <code>$ npx gatsby new {APP_NAME}</code>. You could also do this by running <code>$ npm init gatsby</code>. Doing it this way will bring up some prompts that you could follow through to set your project up. Select <code>None</code> when it asks you for your preferred design system.</li>
<li>Install Emotion by running <code>$ npm install @emotion/react @emotion/styled gatsby-plugin-emotion</code>.<ul>
<li>Add <code>gatsby-plugin-emotion</code> to your list of plugins in <strong><em>gatsby-config.js</em></strong>.</li>
<li>Add Twin as a babel preset by going to <strong><em>package.json</em></strong> and add this snippet: </li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-string">"babelMacros"</span>: {
    <span class="hljs-string">"twin"</span>: {
        <span class="hljs-string">"preset"</span>: <span class="hljs-string">"emotion"</span>
    }
},
</code></pre><ul>
<li>Run <code>$ tsc --init --jsx react</code> to build the <strong><em>tsconfig.json</em></strong> file. You'll see a bunch of commented and uncommented lines.<ul>
<li>Look for the following properties and uncomment them to get your file to look something like this:</li>
</ul>
</li>
</ul>
<pre><code>{
  <span class="hljs-attr">"include"</span>: [<span class="hljs-string">"./src/**/*"</span>],
  <span class="hljs-attr">"compilerOptions"</span>: {
    <span class="hljs-attr">"target"</span>: <span class="hljs-string">"esnext"</span>,
    <span class="hljs-attr">"module"</span>: <span class="hljs-string">"esnext"</span>,
    <span class="hljs-attr">"lib"</span>: [<span class="hljs-string">"dom"</span>, <span class="hljs-string">"es2017"</span>],
    <span class="hljs-attr">"jsx"</span>: <span class="hljs-string">"react"</span>,
    <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"esModuleInterop"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"experimentalDecorators"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"emitDecoratorMetadata"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"noEmit"</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">"skipLibCheck"</span>: <span class="hljs-literal">true</span>
  }
}
</code></pre><ul>
<li>Create an empty Tailwind config file by running <code>$ npx tailwindcss-cli@latest init</code>.</li>
</ul>
<p><strong>First Steps:</strong></p>
<ul>
<li>Because we're going to use the <code>styled</code> module from Twin Macro, we're going to have to make some adjustments so that the compiler doesn't scream at us. Let's add some types for React by running <code>$ npm install --save-dev @types/react</code>.</li>
<li>Make a declaration file in the root called <strong><em>twin.d.ts</em></strong> and paste the following snippet in it: </li>
</ul>
<pre><code><span class="hljs-keyword">import</span> <span class="hljs-string">'twin.macro'</span>
<span class="hljs-keyword">import</span> styledImport <span class="hljs-keyword">from</span> <span class="hljs-string">'@emotion/styled'</span>
<span class="hljs-keyword">import</span> { css <span class="hljs-keyword">as</span> cssImport } <span class="hljs-keyword">from</span> <span class="hljs-string">'@emotion/react'</span>

<span class="hljs-keyword">declare</span> <span class="hljs-keyword">module</span> 'twin.macro' {
    <span class="hljs-keyword">const</span> styled: <span class="hljs-keyword">typeof</span> styledImport
    <span class="hljs-keyword">const</span> css: <span class="hljs-keyword">typeof</span> cssImport
}
</code></pre><ul>
<li>In your <strong><em>tsconfig.json</em></strong> file, add <code>"jsxImportSource": "@emotion/react",</code> to <code>"compilerOptions"</code>. <ul>
<li>Change <code>"jsx": "react"</code> to <code>"jsx": "react-jsx"</code>.</li>
<li>We'll then import our declaration file by changing the <code>include</code> line, like so: <code>"include": ["./src/**/*", "twin.d.ts"],</code>.</li>
</ul>
</li>
</ul>
<p><strong>The Work Begins:</strong></p>
<ul>
<li>Add a layout file making <strong><em>layout.tsx</em></strong> in <strong><em>src/components/</em></strong> and add this snippet to it: </li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { GlobalStyles } <span class="hljs-keyword">from</span> <span class="hljs-string">'twin.macro'</span>

<span class="hljs-keyword">const</span> Layout = <span class="hljs-function">(<span class="hljs-params">{ children, ...rest }</span>) =&gt;</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> {<span class="hljs-attr">...rest</span>}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">GlobalStyles</span> /&gt;</span>
        {children}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Layout
</code></pre><ul>
<li>Go to <strong><em>src/pages/index.js</em></strong> and rename it to <strong><em>src/pages/index.tsx</em></strong>. Add this snippet:</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React from 'react
<span class="hljs-keyword">import</span> Layout from '../components/layout'

const <span class="hljs-type">IndexPage</span> = () =&gt; {
    <span class="hljs-keyword">return</span> (
        &lt;<span class="hljs-type">Layout</span>&gt;
            &lt;p&gt;<span class="hljs-type">Hello</span> <span class="hljs-type">World!</span>&lt;/p&gt;
        &lt;/<span class="hljs-type">Layout</span>&gt;
    )
}

export <span class="hljs-keyword">default</span> <span class="hljs-type">IndexPage</span>
</code></pre><ul>
<li>Create a Navbar component by making a <strong><em>Navbar</em></strong> folder and make an <strong><em>index.tsx</em></strong> file and a <strong><em>styles.tsx</em></strong> file.<ul>
<li>We'll be importing <code>tw</code> and <code>styled</code> which will help us make styled-components with Tailwind.</li>
</ul>
</li>
</ul>
<p><strong>The Work Continues:</strong></p>
<ul>
<li>In the <strong><em>styles.tsx</em></strong> file, add this code snippet: </li>
</ul>
<pre><code><span class="hljs-keyword">import</span> tw, {styled} <span class="hljs-keyword">from</span> <span class="hljs-string">'twin.macro'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Nav = tw.nav<span class="hljs-string">`
    relative flex flex-wrap items-center justify-between px-4 py-5 
    md:px-24 lg:px-8 
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> InnerNav = tw.div<span class="hljs-string">`
    container px-4 mx-auto flex flex-wrap items-center justify-between
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> InnerMost = tw.div<span class="hljs-string">`
    w-full relative flex justify-between lg:w-auto lg:static lg:block 
    lg:justify-start
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> DropDown = styled.div(<span class="hljs-function">(<span class="hljs-params">props: <span class="hljs-built_in">any</span></span>) =&gt;</span> [
    tw<span class="hljs-string">`lg:flex flex-grow items-center`</span>,
    props.open ? tw<span class="hljs-string">`flex`</span> : tw<span class="hljs-string">`hidden`</span>
]) 

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Logo = tw.a<span class="hljs-string">`
    leading-relaxed inline-block mr-4 whitespace-nowrap 
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> NavButton = tw.button<span class="hljs-string">`
    text-black cursor-pointer text-xl leading-none px-3 py-1 border border-solid 
    border-transparent rounded bg-transparent block lg:hidden outline-none 
    focus:outline-none
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> DropMenu = tw.ul<span class="hljs-string">`
    flex flex-col lg:flex-row list-none lg:ml-auto md:w-full md:items-center
    lg:justify-end
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ListItem = tw.li<span class="hljs-string">`
    block
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> InnerItem = tw(ThisLink)<span class="hljs-string">`
    px-3 py-2 flex items-center font-sans font-semibold text-lg tracking-wide 
    text-gray-700 hover:text-gray-900 
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> ExtraButton = tw.a<span class="hljs-string">`
    text-black bg-transparent border border-solid border-gray-900 
    hover:bg-black hover:text-white active:bg-gray-600 font-sans font-semibold 
    uppercase text-sm px-4 py-2 rounded outline-none focus:outline-none mr-1 
    mb-1 mt-4 lg:mt-0 lg:ml-24
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Bars = tw.div<span class="hljs-string">`
    mt-2
`</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Bar = tw.div<span class="hljs-string">`
    w-8 h-1 bg-black mb-1 align-middle
`</span>
</code></pre><ul>
<li>If you look at the code above, you'll see one <code>styled</code> module. This enables us to use props, which we'll need to pass our <code>open</code> data to.<ul>
<li><strong>NOTE:</strong> Don't pay too much to the names of these components. You can name them however you wish, as long as you can follow them and the people that will read your code can follow them.</li>
</ul>
</li>
<li>Now we need to get our components into <strong><em>index.tsx</em></strong>. This is where our markup will be housed.<ul>
<li>If you look at the code below, you'll realize that we're using <code>StaticImage</code>. This component is used when we're not loading dynamic images.</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> {Nav, InnerNav, InnerMost, DropDown, Logo, NavButton,
    DropMenu, ListItem, InnerItem, ExtraButton, Bars, Bar
} <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles'</span>
<span class="hljs-keyword">import</span> {StaticImage} <span class="hljs-keyword">from</span> <span class="hljs-string">'gatsby-plugin-image'</span>


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

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Nav</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">InnerNav</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">InnerMost</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Logo</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">StaticImage</span> 
                            <span class="hljs-attr">src</span>=<span class="hljs-string">"../../images/logo.png"</span>
                            <span class="hljs-attr">alt</span>=<span class="hljs-string">"logo"</span>
                            <span class="hljs-attr">width</span>=<span class="hljs-string">{180}</span>
                            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"none"</span>
                        /&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">Logo</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">NavButton</span>
                            <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
                        &gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">Bars</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">Bar</span> /&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">Bar</span> /&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">Bar</span> /&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">Bars</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">NavButton</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">InnerMost</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">DropDown</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">DropMenu</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">ListItem</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">InnerItem</span>
                                    <span class="hljs-attr">href</span>=<span class="hljs-string">"/products"</span> 
                                    <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Products"</span>
                                &gt;</span>
                                    Products
                                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ListItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ListItem</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">InnerItem</span>
                                    <span class="hljs-attr">href</span>=<span class="hljs-string">"/pricing"</span> 
                                    <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Product pricing"</span>
                                &gt;</span>
                                    Pricing
                                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ListItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ListItem</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">InnerItem</span>
                                    <span class="hljs-attr">href</span>=<span class="hljs-string">"/about"</span> 
                                    <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"About us"</span>
                                &gt;</span>
                                    About Us
                                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ListItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ListItem</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">InnerItem</span>
                                    <span class="hljs-attr">href</span>=<span class="hljs-string">"/faq"</span> 
                                    <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"FAQs"</span>
                                &gt;</span>
                                    FAQs
                                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ListItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ExtraButton</span> 
                                <span class="hljs-attr">href</span>=<span class="hljs-string">"/contact-us"</span>
                            &gt;</span>
                                CONTACT US
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ExtraButton</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">DropMenu</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">DropDown</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerNav</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Nav</span>&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
    )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar
</code></pre><ul>
<li>There are a few things that we need to do now. Currently, our navbar is responsive because of our CSS (Tailwind) styles. However, if we were to click on the hamburger button, nothing will happen. Hence, we'll need to tell React to listen for this click, and we'll need to define a method to expand our menu when clicked.<ul>
<li>We'll add React's <code>onClick</code> event listener which will fire off our event handler, <code>handleClick()</code>. </li>
<li>We'll then need to need to define our state that will say whether our navbar is open or not. The <code>handleClick()</code> method will then be in charge of facilitating our state change.</li>
<li>Finally, we'll pass our state to the extended component in our styles that has conditional styles depending on whether our navbar is open or not. We'll do this with a prop called <code>open</code>. Our markup will now look like this:</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React, {useState} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> {Nav, InnerNav, InnerMost, DropDown, Logo, NavButton,
    DropMenu, ListItem, InnerItem, ExtraButton, Bars, Bar
} <span class="hljs-keyword">from</span> <span class="hljs-string">'./styles'</span>
<span class="hljs-keyword">import</span> {StaticImage} <span class="hljs-keyword">from</span> <span class="hljs-string">'gatsby-plugin-image'</span>


<span class="hljs-keyword">const</span> Navbar = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [navbarOpen, setNavbarOpen] = useState(<span class="hljs-literal">false</span>)

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handleClick</span>(<span class="hljs-params"></span>)</span>{
        setNavbarOpen(!navbarOpen)
    }

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Nav</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">InnerNav</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">InnerMost</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">Logo</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">StaticImage</span> 
                            <span class="hljs-attr">src</span>=<span class="hljs-string">"../../images/logo.png"</span>
                            <span class="hljs-attr">alt</span>=<span class="hljs-string">"logo"</span>
                            <span class="hljs-attr">width</span>=<span class="hljs-string">{180}</span>
                            <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"none"</span>
                        /&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">Logo</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">NavButton</span>
                            <span class="hljs-attr">type</span>=<span class="hljs-string">"button"</span>
                            <span class="hljs-attr">onClick</span>=<span class="hljs-string">{handleClick}</span>
                        &gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">Bars</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">Bar</span> /&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">Bar</span> /&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">Bar</span> /&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">Bars</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">NavButton</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">InnerMost</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">DropDown</span> <span class="hljs-attr">open</span>=<span class="hljs-string">{navbarOpen}</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">DropMenu</span>&gt;</span>
                        <span class="hljs-tag">&lt;<span class="hljs-name">ListItem</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">InnerItem</span>
                                    <span class="hljs-attr">href</span>=<span class="hljs-string">"/products"</span> 
                                    <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Products"</span>
                                &gt;</span>
                                    Products
                                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ListItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ListItem</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">InnerItem</span>
                                    <span class="hljs-attr">href</span>=<span class="hljs-string">"/pricing"</span> 
                                    <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Product pricing"</span>
                                &gt;</span>
                                    Pricing
                                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ListItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ListItem</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">InnerItem</span>
                                    <span class="hljs-attr">href</span>=<span class="hljs-string">"/about"</span> 
                                    <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"About us"</span>
                                &gt;</span>
                                    About Us
                                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ListItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ListItem</span>&gt;</span>
                                <span class="hljs-tag">&lt;<span class="hljs-name">InnerItem</span>
                                    <span class="hljs-attr">href</span>=<span class="hljs-string">"/faq"</span> 
                                    <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"FAQs"</span>
                                &gt;</span>
                                    FAQs
                                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ListItem</span>&gt;</span>
                            <span class="hljs-tag">&lt;<span class="hljs-name">ExtraButton</span> 
                                <span class="hljs-attr">href</span>=<span class="hljs-string">"/contact-us"</span>
                            &gt;</span>
                                CONTACT US
                            <span class="hljs-tag">&lt;/<span class="hljs-name">ExtraButton</span>&gt;</span>
                        <span class="hljs-tag">&lt;/<span class="hljs-name">DropMenu</span>&gt;</span>
                    <span class="hljs-tag">&lt;/<span class="hljs-name">DropDown</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">InnerNav</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">Nav</span>&gt;</span>
        <span class="hljs-tag">&lt;/&gt;</span></span>
    )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Navbar
</code></pre><p><strong>The Work Ends:</strong></p>
<ul>
<li>Let's go back to our <strong><em>styles.tsx</em></strong> file. This is where we'll get deeper into TypeScript. In React, we have the <code>FC</code> (<code>Function Component</code>) generic type which can be used on arrow functions to define and extend arrow function components. <code>FC</code> allows us to pass props into our component and helps us define default types for it as well. It also includes the <code>children</code> prop type by default, but we will be explicitly defining this in our component.<ul>
<li>Next, we make a type called <code>ButtonProps</code> where we'll define the props that we know will be passed to our button component (type, children, onClick). For <code>onClick</code>, we'll define it with a function that uses React's default <code>MouseEvent</code> interface. TypeScript pros have a saying that you should <code>never</code> use the <code>any</code> type, but for brevity, we'll just give <code>children</code> an <code>any</code> type.</li>
<li>With the help of <code>FC</code>, we can then pass in our defined props to define our extended component. </li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React, {FC, MouseEvent} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> tw, {styled} <span class="hljs-keyword">from</span> <span class="hljs-string">'twin.macro'</span>

<span class="hljs-keyword">type</span> ButtonProps = {
    <span class="hljs-keyword">type</span>: <span class="hljs-built_in">string</span>,
    children?: <span class="hljs-built_in">any</span>,
    onClick?: <span class="hljs-function">(<span class="hljs-params">event?: MouseEvent&lt;HTMLButtonElement&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>,
}

<span class="hljs-keyword">const</span> ThisButton: FC&lt;ButtonProps&gt; = <span class="hljs-function">(<span class="hljs-params">{onClick, <span class="hljs-keyword">type</span>, children, ...rest}</span>) =&gt;</span> 
    &lt;button onClick={onClick} {...rest}&gt;{children}&lt;/button&gt;

...

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> NavButton = tw(ThisButton)<span class="hljs-string">`
    text-black cursor-pointer text-xl leading-none px-3 py-1 border border-solid 
    border-transparent rounded bg-transparent block lg:hidden outline-none 
    focus:outline-none
`</span>
</code></pre><ul>
<li>Now in your <strong><em>layout.tsx</em></strong> file, import the <code>Navbar</code> component and place it right below the <code>GlobalStyles</code> component.</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> { GlobalStyles } <span class="hljs-keyword">from</span> <span class="hljs-string">'twin.macro'</span>
<span class="hljs-keyword">import</span> Navbar <span class="hljs-keyword">from</span> <span class="hljs-string">'./Navbar'</span>

<span class="hljs-keyword">const</span> Layout = <span class="hljs-function">(<span class="hljs-params">{ children, ...rest }</span>) =&gt;</span> (
    <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> {<span class="hljs-attr">...rest</span>}&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">GlobalStyles</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">Navbar</span> /&gt;</span>
        {children}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Layout
</code></pre><p><strong>Conclusion:</strong></p>
<ul>
<li>If you try it out, you will notice that clicking on one of the links in the menu will bring you to a new page with the menu still open. To remedy this, in our <strong><em>Navbar/index.tsx</em></strong> file, we could pass an <code>onClick</code> event listener to each link component, which in our case is <code>&lt;InnerItem /&gt;</code>.<ul>
<li>If we need to, we can define our links like so:</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React, {FC, MouseEvent} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> tw, {styled} <span class="hljs-keyword">from</span> <span class="hljs-string">'twin.macro'</span>

<span class="hljs-keyword">type</span> LinkProps = {
    href: <span class="hljs-built_in">string</span>,
    <span class="hljs-string">"aria-label"</span>?: <span class="hljs-built_in">string</span>,
    onClick?: <span class="hljs-function">(<span class="hljs-params">event?: MouseEvent&lt;HTMLAnchorElement&gt;</span>) =&gt;</span> <span class="hljs-built_in">void</span>,
}

<span class="hljs-keyword">const</span> ThisLink: FC&lt;LinkProps&gt; = <span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span> &lt;a {...props} /&gt;
...

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> InnerItem = tw(ThisLink)<span class="hljs-string">`
    px-3 py-2 flex items-center font-sans font-semibold text-lg tracking-wide 
    text-gray-700 hover:text-gray-900 
`</span>
</code></pre><ul>
<li>Don't feel distressed if you don't get these concepts right away. It just takes some practice, which to be honest. There are so many things that I still need to learn about TypeScript, and so far I'm liking the journey.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Build a Simple Chat Room App in React with Laravel Breeze and Twilio's Conversations API]]></title><description><![CDATA[Many developers know that full-stack apps with a Vue frontend can be quickly spun up with Laravel Jetstream. What many don’t know is that recently, the Laravel team made it easy to make an Inertia app with Laravel Breeze. In this article, we'll make ...]]></description><link>https://blog.lloydmiller.dev/chat-room-app-with-react-and-laravel-breeze-and-twilios-conversations-api</link><guid isPermaLink="true">https://blog.lloydmiller.dev/chat-room-app-with-react-and-laravel-breeze-and-twilios-conversations-api</guid><category><![CDATA[Web Development]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[APIs]]></category><category><![CDATA[coding]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Wed, 28 Apr 2021 23:52:36 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619652171258/oYr7DQwHT.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Many developers know that full-stack apps with a Vue frontend can be quickly spun up with Laravel Jetstream. What many don’t know is that recently, the Laravel team made it easy to make an Inertia app with Laravel Breeze. In this article, we'll make a simple Discord-like app called Twilcord” that will let multiple users join a room via a phone number or username. Instead of the default Vue frontend, we’ll be using React. </p>
<p><strong>The Gist:</strong></p>
<ul>
<li>After downloading and setting up Breeze in our project, we'll install the Inertia library for React by running <code>$ npm install @inertiajs/inertia-react</code>. Then we'll import it and redo our <strong><em>app.js</em></strong> file, like so:</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> {InertiaApp} <span class="hljs-keyword">from</span> <span class="hljs-string">'@inertiajs/inertia-react'</span>
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> {render} <span class="hljs-keyword">from</span> <span class="hljs-string">'react-dom'</span>

<span class="hljs-keyword">const</span> app = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'app'</span>)

render (
    &lt;InertiaApp
        initialPage={<span class="hljs-built_in">JSON</span>.parse(app.dataset.page)}
        resolveComponent={<span class="hljs-function"><span class="hljs-params">name</span> =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">`./Pages/<span class="hljs-subst">${name}</span>`</span>).then(
  <span class="hljs-function"><span class="hljs-params">module</span> =&gt;</span> <span class="hljs-built_in">module</span>.<span class="hljs-keyword">default</span>
        )}
    /&gt;,
    app
)
</code></pre><ul>
<li>We'll need to import Twilio's SDK by running <code>composer require twilio/sdk</code> and ensure that we have our Twilio phone number, account SID, and auth token. We can get these from the <a target="_blank" href="https://www.twilio.com/console">Console</a>.</li>
<li>In our Twilio service, we can create a new conversation, create participants, create a new message, and return an array of the messages belonging to that conversation.</li>
<li>In order for us to be able to "return back" with our data to the React frontend, we'll need to share Laravel Session data with Inertia. We do this by making a few adjustments in the <code>AppServiceProvider</code>.</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Providers</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Session</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">ServiceProvider</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Inertia</span>\<span class="hljs-title">Inertia</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppServiceProvider</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ServiceProvider</span>
</span>{
    ...

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">boot</span>(<span class="hljs-params"></span>)
    </span>{
        Inertia::share(<span class="hljs-string">'flash'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
            <span class="hljs-keyword">return</span> [
                <span class="hljs-string">'message'</span> =&gt; Session::get(<span class="hljs-string">'message'</span>)
            ];
        });
    }
}
</code></pre><ul>
<li>Because we're keeping this app simple without real-time capabilities, we will "cheat" by fetching our messages every three seconds. We'll track the length of our array to see when a new message was added and update accordingly.</li>
</ul>
<pre><code>    <span class="hljs-comment">// Fetch messages</span>
    <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getMessages</span>(<span class="hljs-params"></span>)</span>{
        <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`/convo/<span class="hljs-subst">${chat.sid}</span>/messages`</span>)
        <span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> response.json()

        <span class="hljs-keyword">if</span> (json.messages.length &gt; arrLength){
            setMessages(json.messages)
            setArrLength(json.messages.length)
        }
    }

    <span class="hljs-comment">// Fetch messages every three seconds and clean up once component unmounts</span>
    useEffect(<span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> interval = <span class="hljs-built_in">setInterval</span>(<span class="hljs-function">() =&gt;</span> {
            getMessages()
        }, <span class="hljs-number">3000</span>)

        <span class="hljs-keyword">return</span> <span class="hljs-function">() =&gt;</span> <span class="hljs-built_in">clearInterval</span>(interval)
    }, [])
</code></pre><p>This tutorial is just meant to show the basics of Laravel Breeze, Inertia, setting React up with Laravel and Inertia, and Twilio's Conversations API. A more detailed and real-world tutorial will be written up soon. But in the meantime, if you think you're up to it: </p>
<p><a target="_blank" href="https://www.twilio.com/blog/build-simple-chat-room-app-react-laravel-breeze-php-twilio-conversations-api">Click here for the full tutorial!</a></p>
]]></content:encoded></item><item><title><![CDATA[Write Your Laravel Validation Logic Like a Senior Dev, Part 3]]></title><description><![CDATA[In one of my projects, I needed some DateTime-related rules for Laravel, such as preventing the creation of an item after a certain time. Laravel does come with some DateTime validation rules out-of-the-box, but sometimes you want more control than w...]]></description><link>https://blog.lloydmiller.dev/laravel-validation-logic-like-a-senior-dev-rules-with-datetime-trait</link><guid isPermaLink="true">https://blog.lloydmiller.dev/laravel-validation-logic-like-a-senior-dev-rules-with-datetime-trait</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Thu, 22 Apr 2021 21:07:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619073840161/tQDmLPy-I.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In one of my projects, I needed some DateTime-related rules for Laravel, such as preventing the creation of an item after a certain time. Laravel does come with some <a target="_blank" href="https://laravel.com/docs/8.x/validation#rule-before">DateTime validation rules</a> out-of-the-box, but sometimes you want more control than what you can get. In this article, I'll discuss how I used a custom DateTime Trait in my Rules object to create and extend a powerful custom rule. Below, I'll show you how to create an extended Rule object that prevents an event from being added after the date has passed. I know part 3 to Laravel Validation was long overdue, so let's get down to business, shall we?</p>
<p><strong>Prerequisites:</strong></p>
<ul>
<li>Please read <a target="_blank" href="https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-1">part 1</a> and <a target="_blank" href="https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-2">part 2</a> of this series. Setup information can be found in those articles.</li>
<li>It will be great to know what traits are, and how to make them. If you don't know already, I <a target="_blank" href="https://blog.lloydmiller.dev/making-traits-in-laravel-with-custom-console-commands">explain it in detail here</a>.</li>
<li>Our app will be headless (no frontend), so Postman will be needed.</li>
</ul>
<p><strong>First Steps:</strong></p>
<ul>
<li>We'll need to make a <code>DateTime</code> Trait that will have all our methods concerning <code>DateTime</code>. Please follow the steps in the <a target="_blank" href="https://blog.lloydmiller.dev/making-traits-in-laravel-with-custom-console-commands">article mentioned earlier</a> if you don't know how to create one. Name your Trait <code>DateTimeTrait</code>. We'll be importing the <code>Carbon</code> library to help us handle DateTime functions.</li>
<li>Let's make a method that will get the current time in an Eastern timezone. You can use whatever timezone you wish, just make sure it's <a target="_blank" href="https://www.php.net/manual/en/timezones.php">already supported by PHP</a>. After that, we'll create a function that can convert any time that is passed to our trait to Eastern time.</li>
</ul>
<pre><code>
<span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Traits</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Carbon</span>\<span class="hljs-title">Carbon</span>;

<span class="hljs-keyword">trait</span> DateTimeTrait {
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getCurrentTime</span>(<span class="hljs-params"></span>)</span>{
        $current = Carbon::now(<span class="hljs-string">'America/New_York'</span>);
        <span class="hljs-keyword">return</span> $current;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">convertTimetoEastern</span>(<span class="hljs-params">$date, $tz</span>)</span>{
        $newdate = Carbon::createFromFormat(<span class="hljs-string">'Y-m-d H:i:s'</span>, $date, $tz)
                            -&gt;setTimezone(<span class="hljs-string">'America/New_York'</span>);
        <span class="hljs-keyword">return</span> $newdate; 
    }
}
</code></pre><ul>
<li>For this article, the most important DateTime thing we'll need to do is to tell if the time of the event has passed the current time or not. We can do this by making a method that determines the difference between the current time and the event time.<ul>
<li>In the below code, you'll see <code>Carbon::parse()</code>. This method is another way of instantiating Carbon and passing a time string to the object so that it can be manipulated.</li>
<li>With the new Carbon-readable date, we can use the <code>diffInSeconds()</code> method to get the difference in seconds.  This method accepts a boolean as an optional second parameter to say whether we want an absolute value or not. Because we want our value to be positive or negative, we set it to false.</li>
</ul>
</li>
</ul>
<pre><code>    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getDateDifferenceFromNow</span>(<span class="hljs-params">$event_date, $timezone</span>)</span>{
        $date = Carbon::parse(
            <span class="hljs-keyword">$this</span>-&gt;convertTimetoEastern($event_date, $timezone)
        );
        $difference = $date-&gt;diffInSeconds(<span class="hljs-keyword">$this</span>-&gt;getCurrentTime(), <span class="hljs-literal">false</span>);
        <span class="hljs-keyword">return</span> $difference;
    }
</code></pre><p><strong>The Work Begins:</strong></p>
<ul>
<li>The first thing we'll need to do is create a separate Form Request class that will send JSON responses to our API. Please refer to the <a target="_blank" href="https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-1">first part of this series</a> to learn how to do that. This Form Request will be called <code>EventRequest</code> and it will handle our validation. </li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">APIFormRequest</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">EventRequest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">APIFormRequest</span>
</span>{
    <span class="hljs-comment">/**
     * Determine if the user is authorized to make this request.
     *
     * <span class="hljs-doctag">@return</span> bool
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">authorize</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }

    <span class="hljs-comment">/**
     * Get the validation rules that apply to the request.
     *
     * <span class="hljs-doctag">@return</span> array
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rules</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> [
            <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'required'</span>,
            <span class="hljs-string">'start_time'</span> =&gt; <span class="hljs-string">'required'</span>,
            <span class="hljs-string">'end_time'</span> =&gt; <span class="hljs-string">'date_format:Y-m-d H:i:s|after:start_time'</span>,
            <span class="hljs-string">'venue'</span> =&gt; <span class="hljs-string">'required'</span>,
            <span class="hljs-string">'price'</span> =&gt; <span class="hljs-string">'required'</span>,
            <span class="hljs-string">'timezone'</span> =&gt; <span class="hljs-string">'required|timezone'</span>
        ];
    }
}
</code></pre><ul>
<li>We'll make a custom rule called <code>TimeTooLate</code> by running <code>php artisan make:rule TimeTooLate</code>. We'll then import our newly-made DateTimeTrait. <ul>
<li>Our new Rule class will have a constructor, a <code>passes()</code> method, and a <code>message()</code> method. </li>
<li>The <code>passes()</code> method takes in two parameters, <code>$attribute</code> and <code>$value</code>. The <code>$attribute</code> describes the name of the field that we'll be working with, which in this case, is <code>start_time</code>. The <code>$value</code> describes the value that is attached to that field. The <code>$value</code> param will be used in the <code>passes()</code> method to determine whether the rule passes or not. </li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Rules</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Contracts</span>\<span class="hljs-title">Validation</span>\<span class="hljs-title">Rule</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Traits</span>\<span class="hljs-title">DateTimeTrait</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">TimeTooLate</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">Rule</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">DateTimeTrait</span>;

    <span class="hljs-comment">/**
     * Create a new rule instance.
     *
     * <span class="hljs-doctag">@return</span> void
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $timezone</span>)
    </span>{
        <span class="hljs-comment">//</span>
    }

    <span class="hljs-comment">/**
     * Determine if the validation rule passes.
     *
     * <span class="hljs-doctag">@param</span>  string  $attribute
     * <span class="hljs-doctag">@param</span>  mixed  $value
     * <span class="hljs-doctag">@return</span> bool
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">passes</span>(<span class="hljs-params">$attribute, $value</span>)
    </span>{
        <span class="hljs-comment">//</span>
    }

    <span class="hljs-comment">/**
     * Get the validation error message.
     *
     * <span class="hljs-doctag">@return</span> string
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">message</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">'The validation error message.'</span>;
    }
}
</code></pre><p><strong>The Work Continues:</strong></p>
<ul>
<li>We'll also need to pass the timezone to the TimeTooLate rule, so we'll utilize our constructor. We can strictly type our parameter so that it only accepts a string.</li>
</ul>
<pre><code><span class="hljs-keyword">public</span> $tz;

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> $timezone</span>)
</span>{
    <span class="hljs-keyword">$this</span>-&gt;tz = $timezone;
}
</code></pre><ul>
<li>In our <code>passes()</code> method, we need to make sure that it returns <code>false</code> so that <code>message()</code> can return an error message. With the <code>getDateDifferenceFromNow()</code> method in our <code>DateTimeTrait</code>, a positive integer is returned if the event date has surpassed the current date and time. </li>
</ul>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">passes</span>(<span class="hljs-params">$attribute, $value</span>)
</span>{
    $difference = <span class="hljs-keyword">$this</span>-&gt;getDateDifferenceFromNow($value, <span class="hljs-keyword">$this</span>-&gt;tz);
    <span class="hljs-keyword">return</span> $difference &gt;= <span class="hljs-number">0</span> ? <span class="hljs-literal">false</span> : <span class="hljs-literal">true</span>;
}
</code></pre><ul>
<li>Once this returns false, the <code>message()</code> method will fire. You can put anything here. Also, usually, you could access the name of the $attribute and bind it to your returned string by using <code>:attribute</code>. We won't use it here, but you're free to use it if you wish.</li>
</ul>
<pre><code><span class="hljs-built_in">public</span> <span class="hljs-keyword">function</span> message()
{
    <span class="hljs-keyword">return</span> <span class="hljs-string">'It\'</span>s too late <span class="hljs-keyword">to</span> <span class="hljs-keyword">add</span> this event now<span class="hljs-string">';
}</span>
</code></pre><p><strong>The Work Ends:</strong></p>
<ul>
<li>We'll now need to implement our custom rule into our Request. First, import the TimeTooLate rule. To use it in our rules, we'll change the value for <code>start_time</code> into an array and include TimeTooLate as an element. We'll instantiate TimeTooLate, and pass in the timezone.</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">APIFormRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Rules</span>\<span class="hljs-title">TimeTooLate</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ShowRequest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ApiFormRequest</span>
</span>{
    <span class="hljs-comment">/**
     * Determine if the user is authorized to make this request.
     *
     * <span class="hljs-doctag">@return</span> bool
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">authorize</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }

    <span class="hljs-comment">/**
     * Get the validation rules that apply to the request.
     *
     * <span class="hljs-doctag">@return</span> array
     */</span>
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rules</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> [
            <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'required'</span>,
            <span class="hljs-string">'start_time'</span> =&gt; [<span class="hljs-string">'required'</span>, 
                            <span class="hljs-string">'date_format:Y-m-d H:i:s'</span>, 
                            <span class="hljs-keyword">new</span> TimeTooLate(request(<span class="hljs-string">'timezone'</span>))],
            <span class="hljs-string">'end_time'</span> =&gt; <span class="hljs-string">'date_format:Y-m-d H:i:s|after:start_time'</span>,
            <span class="hljs-string">'venue'</span> =&gt; <span class="hljs-string">'required'</span>,
            <span class="hljs-string">'price'</span> =&gt; <span class="hljs-string">'required'</span>,
            <span class="hljs-string">'timezone'</span> =&gt; <span class="hljs-string">'required|timezone'</span>
        ];
    }
</code></pre><ul>
<li>Instead of creating a controller, let's create an API route that will have a closure that will validate our request. So go to <strong><em>routes/api.php</em></strong> and make one route where we'll test our enhanced validation mechanism.</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Request</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">EventRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Route</span>;

Route::post(<span class="hljs-string">'event/new'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">EventRequest $request</span>)</span>{
    $validated = $request-&gt;validated();
    dd($validated);
});
</code></pre><ul>
<li>Run <code>$ php artisan serve</code>. </li>
<li>Open up Postman, and enter some data into the <code>form-data</code> section of the <code>Body</code> tab that matches the fields we're trying to validate. Make sure that your <code>start_time</code> is some time in the past. Once you run the request, it should look something like this:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1619124638035/sFZmrlr88.png" alt="Screenshot 2021-04-22 164552.png" /></p>
<p><strong>Conclusion:</strong></p>
<ul>
<li>If you didn't get that error, that means you didn't do the proper setup. You'll need to make the abstract class that we discussed in our <a target="_blank" href="https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-1">first Laravel Validation article</a>.</li>
<li>There are so many possibilities you can accomplish by extending Rule objects. They're even better when you attach Traits into the mix, a group of methods that are not bound by the classes they are in.</li>
<li>There is at least one more part to come for this Laravel Validation series. In the next article, we'll discuss how you can implement After Hook validation. </li>
</ul>
<div class="hn-embed-widget" id="buyme"></div>]]></content:encoded></item><item><title><![CDATA[Why and How to Make Traits in Laravel]]></title><description><![CDATA[One of my favorite PHP features to use in my Laravel projects is Traits. Traits enable you to inherit multiple behaviors in PHP OOP since PHP only allows classes to inherit from one parent. I'm about to show you one of my favorite ways in which I imp...]]></description><link>https://blog.lloydmiller.dev/making-traits-in-laravel-with-custom-console-commands</link><guid isPermaLink="true">https://blog.lloydmiller.dev/making-traits-in-laravel-with-custom-console-commands</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[coding]]></category><category><![CDATA[100DaysOfCode]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Thu, 22 Apr 2021 06:35:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1619062541679/tXfS8i5o4.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>One of my favorite PHP features to use in my Laravel projects is Traits. Traits enable you to inherit multiple behaviors in PHP OOP since PHP only allows classes to inherit from one parent. I'm about to show you one of my favorite ways in which I implement them.</p>
<p><strong>The Why:</strong></p>
<ul>
<li>Traits provide a very easy way for code reuse in PHP applications.</li>
<li>You can use traits pretty much anywhere in your application where you use classes.</li>
<li>Static methods are supported. Usually, when using Traits, you'll initialize them by placing <code>use TraitName;</code> in the class (usually on top). However, you can create your trait methods statically, so you can save a line and use your trait methods, eg. <code>TraitName::method()</code>.</li>
<li>Abstract methods are supported.</li>
</ul>
<p><strong>The How:</strong> There's no default Artisan command to create Traits in Laravel, so you'll have to create them manually. It's as simple as creating <strong><em>MyTrait.php</em></strong> in <strong><em>app/Traits</em></strong>, and setting the namespace to <code>namespace App\Traits</code>. However, this can be a pain if you're creating multiple Traits, so I'll show you how I automate Trait creation.</p>
<ul>
<li>Create your command by running <code>$ php artisan make:command MakeTrait</code>. This will create a file in <strong><em>app/Console/Commands/</em></strong>.</li>
<li>Create a stub, which is the file template that will be used to create the Trait, by running <code>$ php artisan stub:publish</code>.<ul>
<li>This will create a <strong><em>Stubs/</em></strong> folder in your root with a bunch of stub files. Create a new <strong><em>trait.stub</em></strong> file and make it look like this: </li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Traits</span>;

<span class="hljs-keyword">trait</span> {{trait_name}} 
{
    <span class="hljs-comment">/**
    * Create regular or static methods here
    */</span>
}
</code></pre><ul>
<li>You'll need to make a few changes to your new <code>MakeTrait</code> command file. Instead of extending <code>Command</code>, you'll be extending <code>GeneratorCommand</code>. Also, you'll want to delete the <code>__construct()</code> and <code>handle()</code> methods since you're not going to need them. </li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Commands</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">GeneratorCommand</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MakeTrait</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">GeneratorCommand</span>
</span>{
    <span class="hljs-comment">/**
     * The name and signature of the console command.
     *
     * <span class="hljs-doctag">@var</span> string
     */</span>
    <span class="hljs-keyword">protected</span> $signature = <span class="hljs-string">'command:name'</span>;

    <span class="hljs-comment">/**
     * The console command description.
     *
     * <span class="hljs-doctag">@var</span> string
     */</span>
    <span class="hljs-keyword">protected</span> $description = <span class="hljs-string">'Command description'</span>;
}
</code></pre><ul>
<li>For your <code>$signature</code> property, make it equal to <code>'make:trait {name};</code>.</li>
<li>For your <code>$description</code> property, make it equal to <code>'Create a new Trait.'</code>;</li>
<li>Add a new <code>$type</code> property and let it be equal to 'Trait'. This property is used to tell you if the class type has been successfully created or if it already exists.</li>
<li>Add a <code>getStub()</code> method to retrieve your stub. Also, make a <code>getDefaultNamespace()</code> method that will get the default namespace for the class. </li>
<li>Finally, add a <code>replaceClass($stub, $name)</code> that will override the method that's in <code>GeneratorCommand</code> so that Laravel knows what to do with <code>{{trait_name}}</code>. Usually, it will look for <code>DummyCommand</code> or <code>{{class}}</code>. So in all, your command should look like this:</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">Commands</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Console</span>\<span class="hljs-title">GeneratorCommand</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MakeTrait</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">GeneratorCommand</span>
</span>{
    <span class="hljs-comment">/**
     * The name and signature of the console command.
     *
     * <span class="hljs-doctag">@var</span> string
     */</span>
    <span class="hljs-keyword">protected</span> $signature = <span class="hljs-string">'make:trait {name}'</span>;

    <span class="hljs-comment">/**
     * The console command description.
     *
     * <span class="hljs-doctag">@var</span> string
     */</span>
    <span class="hljs-keyword">protected</span> $description = <span class="hljs-string">'Create a new Trait.'</span>;

    <span class="hljs-keyword">protected</span> $type = <span class="hljs-string">'Trait'</span>;

    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getStub</span>(<span class="hljs-params"></span>)</span>{
        <span class="hljs-keyword">return</span> base_path(<span class="hljs-string">'stubs/trait.stub'</span>);
    }

    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getDefaultNamespace</span>(<span class="hljs-params">$rootNamespace</span>)</span>{
        <span class="hljs-keyword">return</span> $rootNamespace . <span class="hljs-string">'\Traits'</span>;
    }

    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">replaceClass</span>(<span class="hljs-params">$stub, $name</span>)</span>{
        $class = str_replace(<span class="hljs-keyword">$this</span>-&gt;getNamespace($name).<span class="hljs-string">'\\'</span>, <span class="hljs-string">''</span>, $name);

        <span class="hljs-keyword">return</span> str_replace(<span class="hljs-string">'{{trait_name}}'</span>, $class, $stub);
    }
}
</code></pre><ul>
<li>Now create a new trait by running <code>$ php artisan make:trait FooBarTrait</code>. You'll see your new Trait in <strong><em>app/Traits</em></strong>.</li>
</ul>
<p><strong>Conclusion:</strong></p>
<ul>
<li>In <strong><em>app/Console</em></strong> you will see a <strong><em>Kernel.php</em></strong> file. There's a property there called <code>$commands</code> where you can place the path for a command that may not be found by Laravel. By default, it will look directly in the <strong><em>Commands</em></strong> folder, as you may see in the <code>commands()</code> method below.</li>
<li>One Trait that I always make in my projects is a <code>DayTimeTrait</code>. In a future article, I'll show you an advanced usage with Rules.</li>
</ul>
<div class="hn-embed-widget" id="buyme"></div>]]></content:encoded></item><item><title><![CDATA[Uploading a Laravel and React Project to Heroku]]></title><description><![CDATA[I've been gone for a while, but I'm back!
During my 'break' I gave Inertia with Laravel another chance and ended up liking it. I'm still not a fan of how Laravel devs still have to go to multiple places for docs, but it's what we have so far until th...]]></description><link>https://blog.lloydmiller.dev/uploading-a-laravel-and-react-project-to-heroku</link><guid isPermaLink="true">https://blog.lloydmiller.dev/uploading-a-laravel-and-react-project-to-heroku</guid><category><![CDATA[Laravel]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[React]]></category><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[hosting]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Tue, 13 Apr 2021 17:33:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1618326971992/-YRQkOQi5.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I've been gone for a while, but I'm back!</p>
<p>During my 'break' I gave Inertia with Laravel another chance and ended up liking it. I'm still not a fan of how Laravel devs still have to go to multiple places for docs, but it's what we have so far until the Laravel team or someone brave puts everything in one place. When I was getting reacquainted, I needed to upload my project to production. I'm a frequent user of Heroku because of their free plan, but getting PHP and Node to work together took a bit of work. Here's how I did it.</p>
<p><strong>Pre-requisites:</strong></p>
<ul>
<li>Make a Heroku account. <a target="_blank" href="https://signup.heroku.com/php">It's totally free</a>.</li>
<li>Download and <a target="_blank" href="https://devcenter.heroku.com/articles/heroku-cli">install the Heroku CLI</a>.<ul>
<li>Carefully follow the instructions and ensure that you have your login credentials set. You don't have to create a repository yet.</li>
<li>You must have git installed. If you don't, <a target="_blank" href="https://git-scm.com/book/en/v2/Getting-Started-Installing-Git">do it</a>.</li>
</ul>
</li>
<li>Make a new Laravel project in your projects folder by running <code>$ laravel new laravel-inertia-project</code> in your terminal.</li>
</ul>
<p><strong>First Steps:</strong> In this tutorial, we'll be using Laravel Breeze, which is the simplest way to implement a Vue or React frontend in our Laravel project.</p>
<ul>
<li>Install Laravel Breeze by running <code>$ composer require laravel/breeze --dev</code>. Then we'll install Breeze and its Inertia stack by running these two commands:</li>
</ul>
<pre><code>$ php artisan breeze:<span class="hljs-keyword">install</span> <span class="hljs-comment">--inertia</span>
$ npm <span class="hljs-keyword">install</span>
</code></pre><ul>
<li><p>Once that's done, we'll need to set up React. Laravel Breeze and Inertia will automatically install Vue, so we'll need to do a bit of work here. </p>
<ul>
<li><p>Run:</p>
<pre><code>$ <span class="hljs-built_in">npm</span> install react react-dom
$ <span class="hljs-built_in">npm</span> install @inertiajs/inertia @inertiajs/inertia-react
</code></pre></li>
<li><p>Go to <strong><em>resources/js/app.js</em></strong> and make some changes so that we can build our own frontend instead of using Vue.</p>
</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> {InertiaApp} <span class="hljs-keyword">from</span> ‘<span class="hljs-meta">@inertiajs</span>/inertia-react’
<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> ‘react’
<span class="hljs-keyword">import</span> {render} <span class="hljs-keyword">from</span> ‘react-dom’

<span class="hljs-keyword">const</span> app = <span class="hljs-built_in">document</span>.getElementById(‘app’)

render (
    &lt;InertiaApp
        initialPage={<span class="hljs-built_in">JSON</span>.parse(app.dataset.page)}
        resolveComponent={<span class="hljs-function"><span class="hljs-params">name</span> =&gt;</span> <span class="hljs-keyword">import</span>(<span class="hljs-string">`./Pages/<span class="hljs-subst">${name}</span>`</span>).then(
  <span class="hljs-function"><span class="hljs-params">module</span> =&gt;</span> <span class="hljs-built_in">module</span>.<span class="hljs-keyword">default</span>
        )}
    /&gt;,
    app
)
</code></pre><ul>
<li>To ensure that Babel can understand the little JSX code that we’ll be writing, we’ll need to install a Babel preset by running <code>$ npm install --save-dev @babel/preset-react</code>. We can then use this preset by creating a <strong><em>.babelrc</em></strong> file in the root with this code:</li>
</ul>
<pre><code class="lang-bash">{
    <span class="hljs-string">"presets"</span>: [<span class="hljs-string">"@babel/preset-react"</span>]
}
</code></pre>
<ul>
<li>We'll need to go to <strong><em>resources/js</em></strong> and delete the <strong><em>Pages</em></strong> and <strong><em>Components</em></strong> folders you see there, and just replace it with a <strong><em>Pages</em></strong> folder with a file called <strong><em>HelloWorld.js</em></strong> in it.</li>
<li>In <strong><em>HelloWorld.js</em></strong>, we'll make a simple Hello World app:</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">const</span> HelloWorld = <span class="hljs-function">() =&gt;</span> (
   <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Hello world!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>
)

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> HelloWorld
</code></pre><ul>
<li>Next, go to <strong><em>routes/web.php</em></strong>, and delete the routes that you see there. Then, we'll use Inertia to render our Hello World page without sending any data.</li>
</ul>
<pre><code>Route::get(<span class="hljs-string">'/'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">return</span> Inertia::render(<span class="hljs-string">'HelloWorld'</span>, []);
}
</code></pre><ul>
<li>Finally, go to <strong><em>webpack.mix.js</em></strong> and ensure that you see <code>mix.js('resources/js/app.js', 'public/js'</code>. If you see <code>.vue()</code> attached to <code>mix</code>, delete it or you'll get errors. Now compile your assets by running <code>$ npm run dev</code>.</li>
</ul>
<p><strong>The Work Begins:</strong></p>
<ul>
<li>Run <code>$ git init</code> to set git up in your project.  Then add your files to git by running <code>$ git add .</code>.</li>
<li>Create a new Heroku app by running <code>$ heroku create</code>. </li>
<li>Before we commit and push, we need to create a <strong><em>Procfile</em></strong>. This file will tell Heroku what commands to run when the application starts up. For PHP, the file will only have one line: <code>web: vendor/bin/heroku-php-apache2 public/</code>. Create <strong><em>Procfile</em></strong>  in your root folder and copy-paste the line in the file, or run <code>$ echo "web: vendor/bin/heroku-php-apache2 public/" &gt; Procfile</code> in your terminal.
<strong>NOTE:</strong> It's best to create the file first before writing to it. You'll see less errors this way.</li>
</ul>
<p><strong>The Work Continues:</strong></p>
<ul>
<li>Before we can push our work up to Heroku, we'll need to create two buildpacks. Buildpacks are scripts that make the right environment to run a programming language. Because we have React and Inertia, we'll need a buildpack for Node as well as PHP.<ul>
<li>For PHP, run <code>$ heroku buildpacks:set heroku/php</code>.</li>
<li>For Node, run <code>$ heroku buildpacks:add --index 1 heroku/nodejs</code>.</li>
<li><strong>NOTE:</strong> The <code>--index 1</code> basically states that this buildpack will run first. The buildpack for the primary language <a target="_blank" href="https://devcenter.heroku.com/articles/using-multiple-buildpacks-for-an-app">must always be run last</a>.  </li>
</ul>
</li>
<li>We'll need to ensure Heroku does not ignore our <code>devDependencies</code> in <strong><em>package.json</em></strong>, which it <a target="_blank" href="https://devcenter.heroku.com/changelog-items/1376">does by default</a>. Run <code>$ heroku config:set NPM_CONFIG_PRODUCTION=false</code>. </li>
</ul>
<p><strong>The Work Ends:</strong></p>
<ul>
<li>Now we can commit and push our project up to Heroku:</li>
</ul>
<pre><code>$ git commit -m <span class="hljs-string">"First commit to Heroku"</span>
$ git push heroku main
</code></pre><ul>
<li><p><strong>NOTE:</strong> If for some reason the <code>heroku</code> remote wasn't added, run <code>$ heroku git:remote -a {APP_NAME}</code>. Go to your Heroku dashboard to see the name if you lost it.</p>
</li>
<li><p>For Laravel to work, we'll need an <code>APP_KEY</code>. One is already set locally, but we need one for Heroku. Run <code>$ heroku config:set APP_KEY={key}</code>.</p>
</li>
<li>Run <code>$ heroku open</code> to open and run the app. It should open to <code>https://{APP_NAME}.netlify.app</code> and you should see "Hello World" on your screen.</li>
</ul>
<p><strong>Conclusion:</strong></p>
<ul>
<li>If you need to debug, <a target="_blank" href="https://devcenter.heroku.com/articles/logging">Heroku provides logs</a> for your app. Run <code>$ heroku config:set LOG_CHANNEL=errorlog</code> so your stack trace errors are logged there.</li>
<li>I love Heroku because it's one of the best ways to host PHP apps for free. </li>
<li>If you're on the free plan of Heroku, your app will 'sleep' at different intervals. If you don't like this behavior, especially as you're building out your app, you can just subscribe to their 'Hobby' plan which costs a dollar less than a Disney Plus subscription.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[I'm Learning TypeScript!]]></title><description><![CDATA[I finally gave in! After seeing a (sometimes annoying) barrage of tweets and posts urging developers to switch to TypeScript, I decided to just give it a shot. And you know what, I like it! I prefer the backend more, so I freeze up when I need to lea...]]></description><link>https://blog.lloydmiller.dev/learning-typescript-with-gatsby</link><guid isPermaLink="true">https://blog.lloydmiller.dev/learning-typescript-with-gatsby</guid><category><![CDATA[Web Development]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[TypeScript]]></category><category><![CDATA[Gatsby]]></category><category><![CDATA[learning]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Wed, 17 Mar 2021 21:04:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615998279223/VCm9eBqvQ.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I finally gave in! After seeing a (sometimes annoying) barrage of tweets and posts urging developers to switch to TypeScript, I decided to just give it a shot. And you know what, I like it! I prefer the backend more, so I freeze up when I need to learn frontend stuff. But since last year I realized that I do love working with JavaScript frameworks like Gatsby, so learning TypeScript always felt inevitable. So, below I've written some stuff I've learned so far. This is not a tutorial, but there are some things below that could help you.</p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li>The first thing that I did was install TypeScript globally by running <code>npm install -g typescript</code>.</li>
<li>After that, I <a target="_blank" href="https://www.gatsbyjs.com/docs/tutorial/part-zero/">installed my Gatsby project</a> and set it up.</li>
<li>Now I needed to get TypeScript working with Gatsby. The Gatsby CLI is able to automatically set up TypeScript, but I stupidly declined, so I had to set it up manually.<ul>
<li>I installed the <strong><em>gatsby-plugin-typescript</em></strong> library for Gatsby.</li>
<li>After that I installed the typings for React by running <code>npm install --save @types/react @types/react-dom</code>.</li>
<li>Finally, I made the important <strong><em>tsconfig.json</em></strong> file. With <code>compilerOptions</code> I can override certain configurations for TypeScript's compiler. For example, I can compile my TypeScript down to an older version of ECMAScript with <code>target</code>. </li>
</ul>
</li>
</ul>
<pre><code>{
    <span class="hljs-attr">"compilerOptions"</span>: {
      <span class="hljs-attr">"module"</span>: <span class="hljs-string">"commonjs"</span>,
      <span class="hljs-attr">"target"</span>: <span class="hljs-string">"es5"</span>,
      <span class="hljs-attr">"jsx"</span>: <span class="hljs-string">"react"</span>,
      <span class="hljs-attr">"lib"</span>: [<span class="hljs-string">"dom"</span>, <span class="hljs-string">"es2015"</span>, <span class="hljs-string">"es2017"</span>],
      <span class="hljs-attr">"strict"</span>: <span class="hljs-literal">true</span>,
      <span class="hljs-attr">"noEmit"</span>: <span class="hljs-literal">true</span>,
    },
  }
</code></pre><p><strong>The Learning Begins:</strong> TypeScript offers all the features of JavaScript with the type system layered on top. This is why they call TypeScript a superset of JavaScript. TypeScript helps prevent weird things in JavaScript like below from happening.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1616003833726/EUK3jDpUr.png" alt="EwYIDI2XMAEtz-B.png" /></p>
<ul>
<li>The various basic types that we can assign to our variables and functions are <code>boolean</code>, <code>number</code>, and <code>string</code>. They have a special type called <code>any</code> too.</li>
<li>I learned that if an object has properties that are optional, we can use the <code>?</code> operator after the property name so that TypeScript won't scream at us if we don't use it down the line.</li>
<li>We can also set our own types as well. There are a few ways to do this:<ul>
<li>With Union Types, we can combine types to tell the compiler that the value can be two or more types. For example, we can denote that an ID can be of both <code>number</code> and <code>string</code> by typing <code>id: number | string</code>.</li>
<li>With Type Aliases, we can define a type and use it more than once. This is very useful, especially for objects. Check out the example below:</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">type</span> Pet = {
    name: <span class="hljs-built_in">string</span>
    species: <span class="hljs-built_in">string</span>
    age: <span class="hljs-built_in">number</span>
    shots: <span class="hljs-built_in">boolean</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkPet</span>(<span class="hljs-params">pet: Pet</span>)</span>{
    ...
    <span class="hljs-built_in">console</span>.log(pet.age) <span class="hljs-comment">// will be a number</span>
}
</code></pre><ul>
<li>More: <ul>
<li>Interfaces are pretty similar, but the main difference is that you can extend them by adding new properties. There are things that interfaces can do that Type Aliases can't such as being always named in error messages.</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">interface</span> Pet {
    name: <span class="hljs-built_in">string</span>
    species: <span class="hljs-built_in">string</span>
    age: <span class="hljs-built_in">number</span>
    shots: <span class="hljs-built_in">boolean</span>
}
<span class="hljs-keyword">interface</span> Dog <span class="hljs-keyword">extends</span> Pet {
    breed: <span class="hljs-built_in">string</span>
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">checkPet</span>(<span class="hljs-params">dog: Dog</span>)</span>{
    ...
    <span class="hljs-built_in">console</span>.log(dog.name) <span class="hljs-comment">// will be a string</span>
    <span class="hljs-built_in">console</span>.log(dog.breed) <span class="hljs-comment">// will be a string</span>
}
</code></pre><p><strong>The Learning Continues:</strong> For my project I am using Twin Macro, which is basically a way to implement TailwindCSS into React. I'm using it with Emotion, the well-known React package to implement styled-components (CSS in JS). </p>
<ul>
<li>The first file that I worked on was <strong><em>seo.tsx</em></strong>. For each page, the best practice is to accept some props for SEO for each webpage, which almost always includes <code>title</code>. Some other props like <code>description</code>, <code>lang</code>, and <code>author</code> won't be used much, so I knew I had to make a type where I could make those props optional.</li>
</ul>
<pre><code><span class="hljs-keyword">interface</span> SEOProps {
    description?: <span class="hljs-built_in">string</span>,
    lang?: <span class="hljs-built_in">string</span>,
    title: <span class="hljs-built_in">string</span>,
    author?: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">const</span> SEO = <span class="hljs-function">(<span class="hljs-params">{description, lang, author, title}: SEOProps</span>) =&gt;</span> {
    ...
}
</code></pre><ul>
<li>It can be tricky getting the syntax right at first. I placed my type outside of the parentheses instead of beside the object which caused the compiler to scream at me.</li>
<li>The next file I worked on was <strong><em>layout.tsx</em></strong>. In this file I simply just said that my child components can be of any type. I'm sure there's a proper way to do this in React or Gatsby. I probably will talk about it when I start doing deeper dives on this.</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">const</span> Layout = <span class="hljs-function">(<span class="hljs-params">{children}: <span class="hljs-built_in">any</span></span>) =&gt;</span> (
    ...
)
</code></pre><p><strong>The Learning Still Continues:</strong></p>
<ul>
<li>The way I usually set up my React projects is to have my components in folders with an <strong><em>index.js</em></strong> file and a <strong><em>styles.js</em></strong> file (the <strong><em>index.js</em></strong> file will take on the name of the component folder). I will then set up my styled-components in <strong><em>styles.js</em></strong> and import it into <strong><em>index.js</em></strong>. The process was familiar in TypeScript until I needed to include props.</li>
<li>In <strong><em>styles.tsx</em></strong>, I utilized the <code>FunctionComponent</code> type from React so that I could give a type to a component. Also, when I defined my type, I used an array as the property because I used the spread operator for my props. I'm struggling to describe this in words, so let me show you:</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React, {FC} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>
<span class="hljs-keyword">import</span> tw <span class="hljs-keyword">from</span> <span class="hljs-string">'twin.macro'</span>

<span class="hljs-keyword">interface</span> ComponentProps {
    [key: <span class="hljs-built_in">string</span>]: <span class="hljs-built_in">any</span>
}

<span class="hljs-keyword">const</span> ThisButton: FC&lt;ComponentProps&gt; = <span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span> &lt;button {...props} /&gt;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> Button = tw(ThisButton)<span class="hljs-string">`
    bg-blue-700 hover:bg-blue-900
`</span>
</code></pre><p><strong>Conclusion:</strong></p>
<ul>
<li>I'm having a lot of fun using TypeScript and I can see the reason for the hype. But I have a whole lot to learn.</li>
<li>In some future posts, I'll make some actual deep-dive tutorials about how to make a proper Gatsby website with styled-components, TailwindCSS, and of course, TypeScript.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Why I no longer feel guilty about taking time off]]></title><description><![CDATA[It was cold and the snow on the sidewalk made it hard for me to walk. Nevertheless, I was determined to walk to my destination of nowhere; I felt like if I stopped walking, I would never be the same again. It's hard to put into words exactly what I w...]]></description><link>https://blog.lloydmiller.dev/why-i-no-longer-feel-guilty-about-taking-time-off</link><guid isPermaLink="true">https://blog.lloydmiller.dev/why-i-no-longer-feel-guilty-about-taking-time-off</guid><category><![CDATA[opinion pieces]]></category><category><![CDATA[Developer]]></category><category><![CDATA[mentalhealth]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Thu, 11 Mar 2021 23:00:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615481027135/mi_sXBp-4_.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It was cold and the snow on the sidewalk made it hard for me to walk. Nevertheless, I was determined to walk to my destination of nowhere; I felt like if I stopped walking, I would never be the same again. It's hard to put into words exactly what I was feeling. It was my third time feeling it, so I knew exactly what it was. But when you feel yourself dissociating, you panic like it's the first time. I immediately wanted to leave the environment that was making me feel this way.</p>
<p>There's something that I've come to accept about myself recently: I'm not built to be a workaholic. But ironically, that's who I am. I'm accepting something that runs contrary to my current being: a freelancer who's always working on a billion things at once for myself and others. My week is 90% work, including weekends.</p>
<p>"I have to finish this."</p>
<p>"Oh man, I have to put this on pause. I have a deadline coming up."</p>
<p>"I should've finished this ages ago."</p>
<p>"This could be so much better."</p>
<p>My mind runs at a million thoughts per hour, thinking about all the stuff that I have to do and what could go right/wrong if they are finished or not. I press on, trying to do a million things at once but sometimes end up not completing anything. I then burnout, resulting in completing less than what I expected. Then I start to worry, and this worry often ends up in something that is way more daunting.</p>
<p><strong>The What:</strong></p>
<ul>
<li>As you may have guessed, I suffer from anxiety. It's the <a target="_blank" href="https://adaa.org/understanding-anxiety/facts-statistics">most common mental illness</a> in the United States.</li>
<li>My brand of anxiety differs from many others, but it usually follows the same theme: feelings of doom and gloom that can have different triggers, but most times it's from constant stress.</li>
<li><p>I'm also addicted to something that has kept me up at night, seven days a week: "Hustle porn". <a target="_blank" href="https://www.businessinsider.com/reddit-alexis-ohanian-hustle-porn-toxic-dangerous-thing-in-tech-2018-11">Alexis Ohanian</a>, Reddit co-founder, said it best:</p>
<blockquote>
<p>"This is one of the most toxic, dangerous things in tech right now. This idea that unless you are suffering, grinding working every hour of every day, you're not working hard enough. It's such bulls---, such utter bulls---." </p>
</blockquote>
</li>
<li><p>It seems that "hustle porn" is dying in the eyes of the public, especially over the past year. With COVID and the trauma of losing loved ones and losing jobs, priorities have been re-calibrated. Several years ago, every other post was about the 24/7 hustle. Now, you'll get "ratio'd" on Twitter if you even suggest it.</p>
</li>
<li>There's been a movement of people, which thankfully includes men, talking about the importance of protecting their mental health. Even some of the biggest "hustle porn" stars (ha!) are starting to admit this. <ul>
<li>Seriously men, we need to talk more about our mental health.</li>
<li>It really connected with me when <a target="_blank" href="https://www.usmagazine.com/celebrity-news/news/prince-harry-being-a-royal-was-destroying-my-mental-health/">Prince Harry</a> spoke about the need to protect his mental health and what he did about it.</li>
</ul>
</li>
</ul>
<p><strong>The Why:</strong> Recent events have made me re-evaluate my priorities. Working 24/7 isn't sustainable on a practical level. It causes burnout for me, and studies have shown time-and-time again that burnout causes loss of productivity. It's illogical to burn myself out in trying to be more productive.</p>
<ul>
<li><a target="_blank" href="https://www2.deloitte.com/us/en/pages/about-deloitte/articles/burnout-survey.html">Deloitte conducted a survey</a> on employee burnout and found there are three major causes: lack of support or recognition from leadership (31%), unrealistic results and expectations (30%), consistently working long hours or on weekends (29%).<ul>
<li>I checked all three, so congratulations to me 🤦🏾‍♂️.</li>
</ul>
</li>
<li>Studies have continuously shown the benefits of a proper work-life balance. These include an increase in productivity, lower levels of illness, heightened happiness, and increased motivation.</li>
<li>Anxiety is terrible and I wouldn't wish it on my biggest enemies. Keeping attacks at bay is paramount.</li>
</ul>
<p><strong>The How:</strong></p>
<ul>
<li>Ever since that cold winter's night, I decided that I will no longer look down on rest. I will not feel guilty about listening to my body's need to rest anymore.</li>
<li>The Deloitte study provides a blueprint for how I can have a proper work-life balance and prevent burnout. I need to believe in myself more, stop setting unrealistic expectations and deadlines for myself, and stop working through the wee hours of the night and weekends.</li>
<li>I've decided that I will no longer work 24/7. Yes, that includes personal projects. I've put off the launch of my projects so many times already 😩. But, my mental health comes first.</li>
<li>I've decided that I must do everything in my power to protect my mental health. That doesn't only include the need to rest, but to do more things that make me happy. That also includes finding new things that I'll probably enjoy.</li>
<li>Yes, we're still in a pandemic. I also work remotely. But I have to find ways to get out more. Restaurants are now open and I'm sure co-working spaces are open for business too. It's time to utilize them.</li>
</ul>
<p><strong>Conclusion:</strong></p>
<ul>
<li>I've detected a marked increase in my well-being ever since I made the decision to no longer feel guilty about taking time off. I'm a lot happier now that I refuse to feel guilty about slacking off. </li>
<li>I know myself, and with this knowledge, I've decided to trust that every decision I make will be the right one for me. Once I put myself first, I trust that everything else will fall naturally in line.</li>
<li>One of the things that have come as a result of this is writing about the stuff I want when I want. It's been so therapeutic, and coincidentally, has opened new doors for me. I like writing coding tutorials, but I enjoyed writing this non-technical lifestyle piece. I'm looking forward to writing more.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Write Your Laravel Validation Logic Like a Senior Dev, Part 2]]></title><description><![CDATA[Sometimes the rules that come pre-built into Laravel are not enough. Luckily, Laravel provides a way for you to create your own rules using a separate Rule class. In this post, I'll show you how we can create a custom rule that makes sure passwords a...]]></description><link>https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-2</link><guid isPermaLink="true">https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-2</guid><category><![CDATA[100DaysOfCode]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Web Development]]></category><category><![CDATA[Laravel]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Wed, 10 Mar 2021 20:50:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615391165198/94FDNtvSA.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Sometimes the rules that come pre-built into Laravel are not enough. Luckily, Laravel provides a way for you to create your own rules using a separate Rule class. In this post, I'll show you how we can create a custom rule that makes sure passwords are at least 8 characters and includes a symbol, a number, and a lower and upper case letter.</p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li>Go back to the <a target="_blank" href="https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-1">previous post</a> to see how you should set up your project.<ul>
<li>This rule will only be used for <strong><em>RegisterRequest</em></strong>, so focus on making sure that's set up properly.</li>
</ul>
</li>
<li>If you haven't already, <a target="_blank" href="https://www.postman.com/downloads/">download and install Postman</a>.</li>
<li>Don't forget to run your server! <code>php artisan serve</code></li>
</ul>
<p><strong>The Work Begins:</strong></p>
<ul>
<li>We'll create our custom rule by running <code>php artisan make:rule StrongPassword</code>. <ul>
<li>You can find your new rule in <strong><em>app/Http/Rules/StrongPassword.php</em></strong>.  </li>
<li>In your custom rule you will see a constructor and two methods: <code>passes()</code> and <code>message()</code>. The <code>passes()</code> method will check to see if the <code>$attribute</code>'s condition in terms of the <code>$value</code> is met and returns either true or false. The <code>message()</code> method returns whatever string you want to return when <code>passes()</code> is false.</li>
</ul>
</li>
<li>The best way to ensure that a string follows a certain pattern is to use regular expressions. In PHP, we use the function <code>preg_match()</code> to help with that.</li>
</ul>
<p><strong>The Work Continues:</strong></p>
<ul>
<li>I'm not a god at regex but luckily I found one that works. Here's how we'll pattern match the <code>$attribute</code>'s <code>$value</code> to return a boolean:</li>
</ul>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">passes</span>(<span class="hljs-params">$attribute, $value</span>)
    </span>{
        <span class="hljs-keyword">return</span> preg_match(<span class="hljs-string">"/^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@()$%^&amp;*=_{}[\]:;\"'|\\&lt;&gt;,.\/~`±§+-]).{8,30}$/"</span>, $value);
    }
</code></pre><ul>
<li>So if it doesn't pass (returns false), our defined error message will be returned. </li>
</ul>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">message</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-string">'Your :attribute must be at least 8 characters and must include a number, a symbol, a lower and an upper case letter'</span>;
    }
</code></pre><ul>
<li><strong>NB:</strong> Since <code>password</code> is the attribute that we're making a rule for, it will replace <code>:attribute</code> in our string.</li>
</ul>
<p><strong>The Work Ends:</strong></p>
<ul>
<li>Go into <strong><em>RegisterRequest</em></strong> and import <code>StrongPassword</code>. Now we need to instantiate it so we can use it. We'll replace the value for <code>password</code> in our <code>rules()</code> method and use an array instead.</li>
</ul>
<pre><code><span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Rules</span>\<span class="hljs-title">StrongPassword</span>;
...

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rules</span>(<span class="hljs-params"></span>)
</span>{
    <span class="hljs-keyword">return</span> [
        <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'required|string|max:255'</span>,
        <span class="hljs-string">'email'</span> =&gt; <span class="hljs-string">'required|string|email|max:255|unique:users'</span>,
        <span class="hljs-string">'password'</span> =&gt; [<span class="hljs-string">'required'</span>, <span class="hljs-string">'string'</span>, <span class="hljs-keyword">new</span> StrongPassword],
    ]
}
</code></pre><ul>
<li>Now when we run it we should see something like this:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615408066839/cyr_gRNUa.jpeg" alt="Screenshot 2021-03-10 152711.jpg" /></p>
<p><strong>Conclusion:</strong></p>
<ul>
<li>Having custom rules gives you a lot more options. You can add a lot more logic for even more robust rules. In a future post, I'll show you how I used a trait to make datetime-based custom rules.</li>
<li>In our next post we'll be discussing After Validation Hooks.</li>
</ul>
<div class="hn-embed-widget" id="buyme"></div>]]></content:encoded></item><item><title><![CDATA[Write Your Laravel Validation Logic Like a Senior Dev, Part 1]]></title><description><![CDATA[Laravel senior devs are constantly looking for ways to ensure their functions, especially their controller methods, are DRY and only do one thing. Laravel provides many easy and intuitive ways to refactor your controllers to make them highly readable...]]></description><link>https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-1</link><guid isPermaLink="true">https://blog.lloydmiller.dev/write-your-laravel-validation-logic-like-a-senior-dev-part-1</guid><category><![CDATA[PHP]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Tue, 09 Mar 2021 23:07:29 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615311996747/4KQL4ANDr.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Laravel senior devs are constantly looking for ways to ensure their functions, especially their controller methods, are DRY and <a target="_blank" href="https://alvinalexander.com/programming/clean-code-quotes-robert-c-martin/#:~:text=%22The%20first%20rule%20of%20functions,something%2C%20but%20not%20both.%22">only do one thing</a>. Laravel provides many easy and intuitive ways to refactor your controllers to make them highly readable and maintainable. Laravel has one nifty feature called Form Requests, which are custom request classes that <a target="_blank" href="https://laravel.com/docs/8.x/validation#form-request-validation">contain validation and authorization logic</a>. This is the first of a three- (or more) part series to show how we can take advantage of Laravel features to make Validation more powerful.</p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li><a target="_blank" href="https://laravel.com/docs/8.x/routing">Set up your API</a> and <a target="_blank" href="https://laravel.com/docs/8.x/controllers">make a controller</a> or two for registration and logging in. Because the actual controller methods won't be triggered, you won't need to install Sanctum or worry too much about the code behind authentication. <ul>
<li>If you're using this tutorial for your project, then carry along with installing what you need.</li>
<li>Also, if you're using Blade or Jetstream, you may want to skip any API-specific instructions ahead. </li>
</ul>
</li>
<li>If you haven't already, <a target="_blank" href="https://www.postman.com/downloads/">download and install Postman</a>.</li>
<li>Don't forget to run your server! <code>php artisan serve</code></li>
</ul>
<p><strong>First Steps:</strong> Because we're working with an API, we'll need to create custom validation Exceptions. As it stands now, Laravel will return HTML every time we get a validation error. </p>
<ul>
<li>Run <code>php artisan make:request ApiFormRequest</code>. This will create a file that can be found at <strong><em>app/Http/Requests/ApiFormRequest.php</em></strong>.</li>
<li>In this file, we'll be overriding the <code>failedValidation()</code> method. However, because we want to reuse it for our other Form Requests, we'll make <code>ApiFormRequest</code> an abstract class. We'll remove the bodies of our <code>authorize()</code> and <code>rules()</code> methods and make them abstract so that we don't end up clashing or overriding them. </li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Contracts</span>\<span class="hljs-title">Validation</span>\<span class="hljs-title">Validator</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Foundation</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">FormRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Exceptions</span>\<span class="hljs-title">HttpResponseException</span>;

<span class="hljs-keyword">abstract</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ApiFormRequest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">FormRequest</span>
</span>{
    <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">authorize</span>(<span class="hljs-params"></span>)</span>;

    <span class="hljs-keyword">protected</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">failedValidation</span>(<span class="hljs-params">Validator $validator</span>)
    </span>{
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> HttpResponseException(response()-&gt;json([
            <span class="hljs-string">'errors'</span> =&gt; $validator-&gt;errors()
        ], <span class="hljs-number">422</span>));
    }

    <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rules</span>(<span class="hljs-params"></span>)</span>;
}
</code></pre><p><strong>The Work Begins:</strong></p>
<ul>
<li>Let's start with validation for our Registration controller/method. We'll create a Form Request for it by running <code>php artisan make:request RegisterRequest</code>.</li>
<li>And if you know your <a target="_blank" href="https://www.php.net/manual/en/language.oop5.abstract.php">PHP OOP principles and Inheritance</a>, you probably predicted that we'll be extending <code>ApiFormRequest</code> instead of <code>FormRequest</code> to use its methods.</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">ApiFormRequest</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RegisterRequest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ApiFormRequest</span>
</span>{
    <span class="hljs-comment">//</span>
}
</code></pre><ul>
<li>Within each Form Request, Laravel automatically sets <code>authorize()</code> to return false. Do not forget to set this to true, or you will get "Unauthorized" errors that may trip you up. <strong>NB:</strong> You can also define custom logic for your <code>authorize()</code> method but that's for another day.</li>
</ul>
<p><strong>The Work Continues:</strong></p>
<ul>
<li>Now we need to set the rules. In the array, we'll place the name of each field to be validated as the key, and for the value, we'll use a <a target="_blank" href="https://laravel.com/docs/8.x/validation#available-validation-rules">variety of rules</a> in a string. The three fields we'll be validating are <code>name</code>, <code>email</code>, and <code>password</code>. <strong>NB:</strong> Each value can be an array instead of a string. This comes in handy when we're making custom rules.</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">ApiFormRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Rules</span>\<span class="hljs-title">StrongPassword</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RegisterRequest</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ApiFormRequest</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">authorize</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rules</span>(<span class="hljs-params"></span>)
    </span>{
        <span class="hljs-keyword">return</span> [
            <span class="hljs-string">'name'</span> =&gt; <span class="hljs-string">'required|string|max:255'</span>,
            <span class="hljs-string">'email'</span> =&gt; <span class="hljs-string">'required|string|email|max:255|unique:users'</span>,
            <span class="hljs-string">'password'</span> =&gt; <span class="hljs-string">'required|string|min:8'</span>,
        ];
    }
</code></pre><ul>
<li>In your controller, import RegisterRequest and use it to type-hint the <code>$request</code> parameter passed into your controller's method. </li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Controllers</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Http</span>\<span class="hljs-title">Requests</span>\<span class="hljs-title">RegisterRequest</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Support</span>\<span class="hljs-title">Facades</span>\<span class="hljs-title">Hash</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Models</span>\<span class="hljs-title">User</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">RegisterController</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Controller</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__invoke</span>(<span class="hljs-params">RegisterRequest $request</span>)</span>{
        $validated = $request-&gt;validated();
        ...
    }
</code></pre><ul>
<li>In the code above, we retrieved the validated data and passed it to the <code>$validated</code> variable. Our validated data is in an array, not an object, so to access the validated <strong>email</strong> data, for instance, we use <code>validated['email']</code>. <strong>NB:</strong> Laravel recommends doing it this way but you'll still be able to use the <code>$request</code> object if your validations pass.</li>
<li>Now follow the previous steps to make another Form Request for your Login controller/method. </li>
</ul>
<p><strong>The Work Ends:</strong></p>
<ul>
<li>Open up Postman and play around in the body of your request. You can omit values or make the <code>password</code> too short. Whatever you do, try to get a negative response from the API. Here's what I did and the response I got:</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1615328668721/Nr-rIblI1.jpeg" alt="Screenshot 2021-03-09 172319.jpg" /></p>
<ul>
<li>You might be asking why your error messages may look different than mine. That's because I made custom error messages for my rules! You can do this too in your Form Request (only if you want to because you really don't).<ul>
<li>In RegisterRequest, under your <code>rules()</code> method, add another method called <code>messages()</code>.</li>
<li>The format follows the <code>rules()</code> method where you'll return an associative array. The difference is that the keys are named a bit differently, and your values will be the messages for each rule.</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">messages</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">return</span> [
        <span class="hljs-string">'name.max:255'</span> =&gt; <span class="hljs-string">'Your name is too long :/'</span>,
        <span class="hljs-string">'email.email'</span> =&gt; <span class="hljs-string">'Please ensure that your email address is in the correct format'</span>,
        <span class="hljs-string">'email.max:255'</span> =&gt; <span class="hljs-string">'Your email address is too long :/'</span>,
        <span class="hljs-string">'email.unique:users'</span> =&gt; <span class="hljs-string">'This user already exists'</span>,
        <span class="hljs-string">'password.required'</span> =&gt; <span class="hljs-string">'Please enter a valid password'</span>
    ];
}
</code></pre><p><strong>Conclusion:</strong></p>
<ul>
<li>Laravel suggests that separating your validation logic should be done when it is complex. I believe that for clean and maintainable code, it's best to do it as often as possible.</li>
<li>There's so much more power we can unlock with Laravel Validation and Form Requests. In the next article, we'll make a custom Rule object to help us with modern password validation.</li>
</ul>
<div class="hn-embed-widget" id="buyme"></div>]]></content:encoded></item><item><title><![CDATA[Zoom OAuth with a NuxtJS App, Part 2]]></title><description><![CDATA[No reason for a long intro paragraph. Here is the first part for Zoom OAuth with NuxtJS, if you haven't seen it yet. Let's get to work!
Preliminaries:

For some strange reason, fetch is not automatically available in our serverMiddleware (don't ask, ...]]></description><link>https://blog.lloydmiller.dev/zoom-oauth-with-a-nuxtjs-app-part-2</link><guid isPermaLink="true">https://blog.lloydmiller.dev/zoom-oauth-with-a-nuxtjs-app-part-2</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Nuxt]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Tue, 09 Mar 2021 07:55:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615259540194/FwhVmOKQL.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>No reason for a long intro paragraph. Here is the first part for <a target="_blank" href="https://blog.lloydmiller.tech/zoom-oauth-with-a-nuxtjs-app-part-1">Zoom OAuth with NuxtJS</a>, if you haven't seen it yet. Let's get to work!</p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li>For some strange reason, <code>fetch</code> is not automatically available in our <strong><em>serverMiddleware</em></strong> (don't ask, I dunno). Hence, we'll need to import the <strong><em>node-fetch</em></strong> library into our project like so: <code>const fetch = require('node-fetch')</code>.</li>
</ul>
<p><strong>First Steps:</strong></p>
<ul>
<li>Let's rewrite the skeleton of the <strong><em>serverMiddleware</em></strong> we made in the previous article to include an arrow function, just for code readability.</li>
</ul>
<pre><code><span class="hljs-keyword">const</span> fetch = require(<span class="hljs-string">'node-fetch'</span>)

<span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">async</span> (req, res) =&gt; {
<span class="hljs-comment">//</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> handler
</code></pre><p><strong>The Work Begins:</strong> Our <strong><em>serverMiddleware</em></strong> will help us get the Access Token which will be used to get user data.</p>
<ul>
<li>In an async function, I prefer to use <code>try...catch</code> blocks because they make error and response handling easier and cleaner. So let's create one inside our handler.</li>
</ul>
<pre><code><span class="hljs-keyword">const</span> handler = <span class="hljs-keyword">async</span> (req,res) =&gt; {
    <span class="hljs-keyword">try</span> {
        <span class="hljs-comment">//</span>
    }
    <span class="hljs-keyword">catch</span>(e){
        console.log(e)
    }
}
</code></pre><ul>
<li>To get the access token, we'll need to make a POST request to Zoom with these three parameters: <code>grant_type</code>, <code>code</code>, and <code>redirect_uri</code>.<ul>
<li>The value for <code>grant_type</code>, according to the docs, is <code>'authorization_code'</code>.</li>
<li>For <code>code</code>, we'll need to catch the value for the <code>code</code> param that we got from the client-side.</li>
<li>The value for <code>redirect_uri</code> is the same base URL that we have in our <strong><em>.env</em></strong> file.</li>
</ul>
</li>
<li>To get <code>code</code>, we'll use the URL interface that is also made available in Node. The URL interface has a property called <code>searchParams</code> that has all query params in an object, and we can use the <code>get</code> method to retrieve its value. To make use of the URL interface, we'll need to make an absolute URL, since the URL we get from the client is only partial.<ul>
<li>Afterwards, we can create the URL to which we'll be making a POST request.</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> url = <span class="hljs-keyword">new</span> URL(req.url, `https:<span class="hljs-comment">//${req.headers.host}`)</span>

    <span class="hljs-keyword">const</span> zoomUrl = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">'https://zoom.us/oauth/token'</span>)
    zoomUrl.searchParams.<span class="hljs-keyword">set</span>(<span class="hljs-string">'grant_type'</span>, <span class="hljs-string">'authorization_code'</span>)
    zoomUrl.searchParams.<span class="hljs-keyword">set</span>(<span class="hljs-string">'code'</span>, url.searchParams.<span class="hljs-keyword">get</span>(<span class="hljs-string">'code'</span>))
    zoomUrl.searchParams.<span class="hljs-keyword">set</span>(<span class="hljs-string">'redirect_uri'</span>, process.env.NUXT_ENV_BASE_URL)
}
</code></pre><p><strong>The Work Continues:</strong> Zoom has a strange demand that we use a special code in our header to make our POST request. This code is special because, in pseudocode, it would look something like this: <code>base64(CLIENT_ID:CLIENT_SECRET)</code>.  </p>
<ul>
<li>We turn that into real code by using Node's Buffer class. This class helps in the handling of binary data. We'll create a new buffer with the concatenated string using our Zoom client ID and client secret, and then we'll convert that into base64.</li>
</ul>
<pre><code><span class="hljs-keyword">const</span> <span class="hljs-keyword">data</span> = process.env.NUXT_ENV_ZOOM_CLIENT_ID + <span class="hljs-string">':'</span> + process.env.NUXT_ENV_ZOOM_CLIENT_SECRET
<span class="hljs-keyword">const</span> newData = Buffer.from(<span class="hljs-keyword">data</span>, <span class="hljs-string">'utf8'</span>)
<span class="hljs-keyword">const</span> b64string = newData.toString(<span class="hljs-string">'base64'</span>)
</code></pre><ul>
<li>Now we can finally make our POST request. I like to define my options object first for <code>fetch</code>. If we await our JSON response, we should get back an object with our access token.</li>
</ul>
<pre><code>const <span class="hljs-keyword">options</span> = {
    <span class="hljs-keyword">method</span>: <span class="hljs-string">'POST'</span>,
    headers: {
        <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
        <span class="hljs-string">'Authorization'</span>: <span class="hljs-string">'Basic '</span> + b64string
    }
}
const response = await <span class="hljs-keyword">fetch</span>(zoomUrl, <span class="hljs-keyword">options</span>)
const <span class="hljs-type">json</span> = await response.json()
</code></pre><p><strong>The Work Ends:</strong></p>
<ul>
<li>Sometimes, for a variety of reasons, the access token is not generated, hence we'll need to move forward only if there is an access token. To get our user, we'll make another <code>fetch</code>, but using the GET method this time. <ul>
<li>Just like with most other token-based auth, we'll use our access token as a bearer token in the header.</li>
</ul>
</li>
<li>When that is done, we will await the User object, and send the data back to the client. <pre><code><span class="hljs-keyword">if</span> (<span class="hljs-type">json</span>.access_token){
  const preUser = await <span class="hljs-keyword">fetch</span>(<span class="hljs-string">'https://api.zoom.us/v2/users'</span>, {
      <span class="hljs-keyword">method</span>: <span class="hljs-string">'GET'</span>,
      headers: {
          <span class="hljs-string">'Authorization'</span>: <span class="hljs-string">'Bearer '</span> + <span class="hljs-type">json</span>.access_token
      }
  })
  const <span class="hljs-keyword">user</span> = await preUser.json()
  res.setHeader(<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'application/json'</span>)
  res.<span class="hljs-keyword">end</span>(<span class="hljs-type">JSON</span>.stringify(<span class="hljs-keyword">user</span>))
}
<span class="hljs-keyword">else</span> {
  throw <span class="hljs-built_in">new</span> Error(<span class="hljs-string">'Something went wrong!'</span>)
}
</code></pre></li>
<li>In our <a target="_blank" href="https://blog.lloydmiller.tech/zoom-oauth-with-a-nuxtjs-app-part-1">last article</a>, the method that we used to fetch user data was <code>getZoomUser</code>. In the last line of that method, just log <code>user</code> to the console, open your browser and you'll see what you get.<ul>
<li>You will see an object with properties that look unrelated at first glance. Open the object and you will see the user that you got permission from to use their data (it'll most likely be your account).</li>
</ul>
</li>
</ul>
<p><strong>Conclusion:</strong></p>
<ul>
<li>In just about every frontend framework that you use, persisting the data you get from the API is hard work. In a future article, we'll discuss state management and persisting state with cookies.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Zoom OAuth with a NuxtJS App, Part 1]]></title><description><![CDATA[Some weeks ago we discussed how to do Zoom OAuth with Next.js. As was said there, we need to authenticate our calls to Zoom's API in order for us to use it, and we can either do this with JWT or OAuth. If you're building a third-party service or appl...]]></description><link>https://blog.lloydmiller.dev/zoom-oauth-with-a-nuxtjs-app-part-1</link><guid isPermaLink="true">https://blog.lloydmiller.dev/zoom-oauth-with-a-nuxtjs-app-part-1</guid><category><![CDATA[Tutorial]]></category><category><![CDATA[Nuxt]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Vue.js]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Tue, 09 Mar 2021 01:13:13 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1615074809890/9chbhzXhx.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Some weeks ago we discussed how to do <a target="_blank" href="https://blog.lloydmiller.tech/zoom-oauth-with-nextjs-app">Zoom OAuth with Next.js</a>. As was said there, we need to authenticate our calls to Zoom's API in order for us to use it, and we can either do this with JWT or OAuth. If you're building a third-party service or application, OAuth is the way to go. We'll not be using an SDK for this tutorial; everything will be done from scratch.</p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li>Make sure you have a Zoom account and register your app in <a target="_blank" href="https://marketplace.zoom.us/">Zoom's Marketplace</a>. More details on how to do so can be found <a target="_blank" href="https://marketplace.zoom.us/docs/guides/build/oauth-app#register-your-app">here</a>.</li>
<li><a target="_blank" href="https://ngrok.com/download">Download and install Ngrok</a>. We'll need it because Zoom doesn't allow redirects to localhost.<ul>
<li>Run Ngrok by using your terminal and running <code>ngrok http 3000</code> wherever your Ngrok executable file is stored. </li>
</ul>
</li>
<li>Presuming that you already have Nuxt <a target="_blank" href="https://nuxtjs.org/docs/2.x/get-started/installation">downloaded and set up</a>, you'll need to set up your environment variables.<ul>
<li>Create a <strong><em>.env</em></strong> file. Here we will house our Zoom Client ID and Client Secret. You should place your Ngrok URL here too.</li>
<li>For this tutorial, we'll name our variables thusly: <code>NUXT_ENV_ZOOM_CLIENT_ID</code>, <code>NUXT_ENV_ZOOM_CLIENT_SECRET</code>, and <code>NUXT_ENV_BASE_URL</code>. <strong>NB:</strong> We use the prefix <code>NUXT_ENV_</code>to inject it straight into the process environment, so we don't have to do anything extra in <strong><em>nuxt.config.js</em></strong>.</li>
</ul>
</li>
<li>Insert the Ngrok URL into 'Redirect URL for OAuth' and 'Whitelist URL' fields on the App Credentials page of your Zoom app.</li>
<li><a target="_blank" href="https://marketplace.zoom.us/docs/guides/auth/oauth/oauth-scopes">Scopes are extremely important</a> so don't forget to add them to your app dashboard!</li>
<li>Run <code>npm run dev</code>!</li>
</ul>
<p><strong>The Work Begins:</strong> Here we're going to make a simple link that when clicked will help kick off the whole process. </p>
<ul>
<li>In your pages folder, create <strong><em>index.vue</em></strong> if it's not already there. We will put <code>link</code> as a property so we can manipulate it and bind it to our <code>a</code> tag via <code>v-bind:href</code>. We need the link to be available when our page is mounted, so we'll use Vue's <code>mounted</code> hook.</li>
<li>We'll also be using the <a target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/API/URL">URL interface</a> which should be available to all modern browsers.</li>
</ul>
<pre><code><span class="hljs-tag">&lt;<span class="hljs-name">template</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">:href</span>=<span class="hljs-string">"link"</span>&gt;</span>Create Zoom link<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">template</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> {
    data(){
        <span class="hljs-keyword">return</span> {
            <span class="hljs-attr">link</span>: <span class="hljs-string">''</span>,
        }
    }, 
    mounted(){
        <span class="hljs-built_in">this</span>.getZoomLink()
    },
    <span class="hljs-attr">methods</span>: {
        getZoomLink(){
            <span class="hljs-keyword">let</span> url = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">'https://zoom.us/oauth/authorize'</span>)
            url.searchParams.set(<span class="hljs-string">'response_type'</span>, <span class="hljs-string">'code'</span>)
            url.searchParams.set(<span class="hljs-string">'redirect_uri'</span>, process.env.url)
            url.searchParams.set(<span class="hljs-string">'client_id'</span>, process.env.zoomclient)
            <span class="hljs-built_in">this</span>.link = url.href
        }, 
    }    
}
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
</code></pre><ul>
<li>When you click the link, a page describing your app and the info that it's requesting will show up (if you're logged in). Once you click on the 'Authorize' button, it will redirect you to the <code>redirect_uri</code> that you set.</li>
</ul>
<p><strong>The Work Continues:</strong> You should now see <code>code</code> in the query string in your browser's URL bar. We'll need to use this string to get our access code. </p>
<ul>
<li>To get our <code>code</code>, we'll use <code>this.$route.query</code>. It returns an object, so we'll first check to see if it is empty with <code>Object.keys()</code> to ensure we only run it when this query string is set. We'll pass it on to a property called <code>code</code> so that we can use its value elsewhere.<ul>
<li>We'll be using the <code>created</code> lifecycle hook instead of <code>mounted</code> for this part. This hook is usually used for fetching data on initialization and fires right <a target="_blank" href="https://www.digitalocean.com/community/tutorials/vuejs-component-lifecycle">before the mounting</a> of our component.</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-keyword">data</span>(){
    <span class="hljs-keyword">return</span> {
        link: <span class="hljs-string">''</span>,
        code: <span class="hljs-string">''</span>
    }
},
created(){
    <span class="hljs-keyword">const</span> obj_length = Object.keys(<span class="hljs-keyword">this</span>.$route.query).length
    <span class="hljs-keyword">if</span> (obj_length &gt; <span class="hljs-number">0</span>){
        <span class="hljs-keyword">this</span>.code = <span class="hljs-keyword">this</span>.$route.query.code
    }
},
mounted(){
    <span class="hljs-keyword">this</span>.getZoomLink()
},
</code></pre><ul>
<li>Now we'll create a method that will send this data to our <strong><em>serverMiddleware</em></strong>. It will run immediately after we get the <code>code</code> string. <pre><code>created(){
  <span class="hljs-keyword">const</span> obj_length = <span class="hljs-built_in">Object</span>.keys(<span class="hljs-built_in">this</span>.$route.query).length
  <span class="hljs-keyword">if</span> (obj_length &gt; <span class="hljs-number">0</span>){
      <span class="hljs-built_in">this</span>.code = <span class="hljs-built_in">this</span>.$route.query.code
      <span class="hljs-built_in">this</span>.getZoomUser()
  }
},
...
methods: {
  <span class="hljs-keyword">async</span> getZoomUser(){
      <span class="hljs-keyword">let</span> options = {
          <span class="hljs-attr">method</span>: <span class="hljs-string">'GET'</span>,
          <span class="hljs-attr">headers</span>: {
              <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>
          }
      }  
      <span class="hljs-keyword">let</span> response = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">`/api/access-token?code=<span class="hljs-subst">${<span class="hljs-built_in">this</span>.code}</span>`</span>, options)
      <span class="hljs-keyword">let</span> user = <span class="hljs-keyword">await</span> response.json()
  },
}
</code></pre></li>
</ul>
<p><strong>The Work Ends:</strong> All Zoom API endpoints will need to be accessed from the server-side, hence, we'll need to use Nuxt <strong><em>serverMiddleware</em></strong>. Seriously, don't even try using the client. You'll get a lot of CORS issues. Our <strong><em>serverMiddleware</em></strong> will act as an API that will communicate with Zoom's API.</p>
<ul>
<li>The first thing we'll need to do is create an <strong><em>api/</em></strong> folder in the root, then create a file that will house our <strong><em>serverMiddleware</em></strong>. I've named it <strong><em>zoom-token.js</em></strong>.</li>
<li>Now we'll register our <strong><em>serverMiddleware</em></strong> in our <strong><em>nuxt.config.js</em></strong> file. We'll set a proxy for our API, and tell Nuxt where exactly the API's handler function is.</li>
</ul>
<pre><code><span class="hljs-attribute">serverMiddleware</span>: [
    {<span class="hljs-attribute">path</span>: <span class="hljs-string">'/api/access-token'</span>, <span class="hljs-attribute">handler</span>: <span class="hljs-string">'~/api/zoom-token.js'</span>},
]
</code></pre><ul>
<li>Now we can begin work on our <strong><em>serverMiddleware</em></strong>. If we log our request to the console (in our terminal, not the browser), we'll see all the related properties, methods, and data, including our query string.</li>
</ul>
<pre><code><span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">req, res</span>)</span>{
    <span class="hljs-built_in">console</span>.log(req)
}
</code></pre><p><strong>Conclusion:</strong> </p>
<ul>
<li>In our next post, we'll go deeper into <strong><em>serverMiddleware</em></strong> and show how we can return our data to the client-side.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Forget REST. Here's How to Make a GraphQL API with Laravel]]></title><description><![CDATA[GraphQL, in my opinion, has been one of the best technologies to be released in the last ten years. GraphQL is a specification that describes how we can use just one query to get data from an API instead of hitting multiple endpoints with different H...]]></description><link>https://blog.lloydmiller.dev/make-a-graphql-api-with-laravel</link><guid isPermaLink="true">https://blog.lloydmiller.dev/make-a-graphql-api-with-laravel</guid><category><![CDATA[Laravel]]></category><category><![CDATA[GraphQL]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Tutorial]]></category><category><![CDATA[Web Development]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Sun, 28 Feb 2021 23:13:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1614288740727/Y7WgpNdNY.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>GraphQL, in my opinion, has been one of the best technologies to be released in the last ten years. GraphQL is a specification that describes how we can use just one query to get data from an API instead of hitting multiple endpoints with different HTTP request methods as seen with REST APIs. Because it's just a specification, GraphQL can simply be implemented in <a target="_blank" href="https://graphql.org/code/">any programming language or framework</a>. In my opinion, one of the best libraries for Laravel based on a PHP GraphQL implementation is Lighthouse. </p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li>In your terminal, navigate to your Laravel project and install Lighthouse by typing <code>composer require nuwave/lighthouse</code>.</li>
<li>Next, run <code>php artisan vendor:publish --tag=lighthouse-schema</code>. This will create the schema in our new <strong><em>graphql</em></strong> folder.</li>
<li>If you want type-checking and autocompletion in your IDE, run <code>php artisan lighthouse:ide-helper</code> and it will create three other files in your root: <strong><em>_lighthouse_ide_helper.php</em></strong>, <strong><em>programmatic-types.graphql</em></strong>, and <strong><em>schema-directives.graphql</em></strong>.<ul>
<li><strong>NB:</strong> If you're using git, please add these files to your <strong><em>.gitignore</em></strong> file.</li>
</ul>
</li>
<li>Finally, no GraphQL project is complete without a GUI. Lighthouse recommends that we use the GraphQL Playground library for Laravel: <code>composer require mll-lab/laravel-graphql-playground</code>.<ul>
<li>We'll be able to access it in our browser with<code>/graphql-playground</code>.</li>
<li>Personally, I would've preferred if there was a GraphiQL library for Laravel that was recently updated. GraphiQL is what I use for my Gatsby projects.</li>
</ul>
</li>
</ul>
<p><strong>First Steps:</strong> Before we get to work, we'll need to set up our database and do some seeding. Let's create a tiny and simple e-commerce platform where a seller can only create one item at a time. We'll also make each item have multiple sales. </p>
<ul>
<li>Each user will have one <code>Item</code> at a time. We'll also be type-hinting our model so we can have an easier time creating our GraphQL schema.</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span> 
...
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateItemTable</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::create(<span class="hljs-string">'item'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;id();
            $table-&gt;string(<span class="hljs-string">'name'</span>)-&gt;nullable(<span class="hljs-literal">false</span>);
            $table-&gt;dateTimeTz(<span class="hljs-string">'expiry_time'</span>)-&gt;nullable(<span class="hljs-literal">false</span>);
            $table-&gt;string(<span class="hljs-string">'description'</span>, <span class="hljs-number">140</span>)-&gt;nullable(<span class="hljs-literal">false</span>);
            $table-&gt;float(<span class="hljs-string">'price'</span>)-&gt;nullable(<span class="hljs-literal">false</span>);
            $table-&gt;bigInteger(<span class="hljs-string">'seller_id'</span>)-&gt;nullable(<span class="hljs-literal">false</span>)-&gt;unsigned();
            $table-&gt;timestamps();
        });
    }
    ...
}
</code></pre><pre><code><span class="hljs-meta">&lt;?php</span>

...
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Relations</span>\<span class="hljs-title">HasMany</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Relations</span>\<span class="hljs-title">BelongsTo</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Item</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasFactory</span>;

    <span class="hljs-keyword">protected</span> $table = <span class="hljs-string">'item'</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sales</span>(<span class="hljs-params"></span>): <span class="hljs-title">HasMany</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(<span class="hljs-string">'App\Models\Sale'</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">seller</span>(<span class="hljs-params"></span>): <span class="hljs-title">BelongsTo</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(<span class="hljs-string">'App\Models\User'</span>, <span class="hljs-string">'seller_id'</span>);
    }
}
</code></pre><ul>
<li>Next, we'll create the Sales migration and model with similar type-hinting.</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>
...
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CreateSalesTable</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Migration</span>
</span>{
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">up</span>(<span class="hljs-params"></span>)
    </span>{
        Schema::create(<span class="hljs-string">'sales'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">Blueprint $table</span>) </span>{
            $table-&gt;id();
            $table-&gt;integer(<span class="hljs-string">'quantity'</span>)-&gt;nullable(<span class="hljs-literal">false</span>);
            $table-&gt;float(<span class="hljs-string">'amount'</span>)-&gt;nullable(<span class="hljs-literal">false</span>);
            $table-&gt;bigInteger(<span class="hljs-string">'item_id'</span>)-&gt;nullable(<span class="hljs-literal">false</span>)-&gt;unsigned();
            $table-&gt;bigInteger(<span class="hljs-string">'seller_id'</span>)-&gt;nullable(<span class="hljs-literal">false</span>)-&gt;unsigned();
            $table-&gt;timestamps();
        });
    }
    ...
}
</code></pre><pre><code><span class="hljs-meta">&lt;?php</span>
...
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Relations</span>\<span class="hljs-title">BelongsTo</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Sale</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Model</span>
</span>{
    <span class="hljs-keyword">use</span> <span class="hljs-title">HasFactory</span>;

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">item</span>(<span class="hljs-params"></span>): <span class="hljs-title">BelongsTo</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(<span class="hljs-string">'App\Models\Item'</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">seller</span>(<span class="hljs-params"></span>): <span class="hljs-title">BelongsTo</span> 
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;belongsTo(<span class="hljs-string">'App\Models\User'</span>, <span class="hljs-string">'seller_id'</span>);
    }
}
</code></pre><ul>
<li>Finally, we can define our User model. </li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

...
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Relations</span>\<span class="hljs-title">HasOne</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">Illuminate</span>\<span class="hljs-title">Database</span>\<span class="hljs-title">Eloquent</span>\<span class="hljs-title">Relations</span>\<span class="hljs-title">HasMany</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">User</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Authenticatable</span>
</span>{
    ...
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">item</span>(<span class="hljs-params"></span>): <span class="hljs-title">HasOne</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasOne(<span class="hljs-string">'App\Models\Item'</span>, <span class="hljs-string">'seller_id'</span>);
    }

    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sales</span>(<span class="hljs-params"></span>): <span class="hljs-title">HasMany</span>
    </span>{
        <span class="hljs-keyword">return</span> <span class="hljs-keyword">$this</span>-&gt;hasMany(<span class="hljs-string">'App\Models\Sale'</span>, <span class="hljs-string">'seller_id'</span>);
    }
}
</code></pre><ul>
<li>Now we'll need to seed our tables (if you don't want to enter data manually). I don't have time to go through <a target="_blank" href="https://laravel.com/docs/8.x/database-testing">Seeders and Factories</a> here so please check out the docs.</li>
<li>We can now test our GraphQL configuration. Open GraphQL Playground and let's retrieve the first user in our database: <pre><code>{
  <span class="hljs-keyword">user</span>(id: <span class="hljs-number">1</span>){
      <span class="hljs-type">name</span>
      email
  }
}
</code></pre></li>
</ul>
<p><strong>The Work Begins:</strong> Here we will be defining our schema. It's important that you have some grasp of the types used in a GraphQL schema. We'll only brush up on this, so for a deeper understanding, I'd advise you to check out <a target="_blank" href="https://graphql.org/learn/schema/">GraphQL's docs on the subject</a>. </p>
<ul>
<li>Open the <strong><em>graphql/schema.graphql</em></strong> file and you will see the definitions for two custom scalar types, <code>Date</code> and <code>DateTime</code>. GraphQL's default scalar types are <code>Int</code>, <code>Float</code>, <code>String</code>, <code>Boolean</code>, and <code>ID</code>.</li>
<li>Near the top, we define the root <code>Query</code> type. In it, we'll add the query for finding the user, which both <code>Item</code> and <code>Sale</code> models are attached to.<ul>
<li><strong>NB:</strong> Our Laravel models are 'Object Types' in GraphQL. Again, please remember to <a target="_blank" href="https://graphql.org/learn/schema/">visit the GraphQL docs</a> for a deeper understanding.</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-selector-tag">type</span> <span class="hljs-selector-tag">Query</span> {
    <span class="hljs-selector-tag">user</span>(<span class="hljs-attribute">id</span>: ID <span class="hljs-variable">@eq</span>): <span class="hljs-selector-tag">User</span> @<span class="hljs-selector-tag">find</span> 
}
</code></pre><p><strong>The Work Continues:</strong></p>
<ul>
<li>Now we'll define our object types by giving them fields and defining each field with types. Let's start with the User object type.</li>
</ul>
<pre><code><span class="hljs-selector-tag">type</span> <span class="hljs-selector-tag">User</span> {
    <span class="hljs-attribute">id</span>: ID!
    <span class="hljs-attribute">name</span>: String!
    <span class="hljs-attribute">email</span>: String!
    <span class="hljs-attribute">item</span>: Item <span class="hljs-variable">@hasOne</span>
    <span class="hljs-attribute">sales</span>: [Sale] <span class="hljs-variable">@hasMany</span>
    <span class="hljs-attribute">created_at</span>: DateTime!
    <span class="hljs-attribute">updated_at</span>: DateTime!
}
</code></pre><ul>
<li>A few notes about the code above:<ul>
<li>An exclamation point simply means that the field can't be null. An error will be returned if the query returns a null value.</li>
<li>When a type is between square brackets, that means we can expect a List.</li>
</ul>
</li>
<li>Now let's define the Item object type:</li>
</ul>
<pre><code><span class="hljs-selector-tag">type</span> <span class="hljs-selector-tag">Item</span> {
    <span class="hljs-attribute">id</span>: ID!
    <span class="hljs-attribute">name</span>: String!
    <span class="hljs-attribute">expiry_time</span>: DateTime!
    <span class="hljs-attribute">description</span>: String!
    <span class="hljs-attribute">price</span>: Float!
    <span class="hljs-attribute">sales</span>: [Sale] <span class="hljs-variable">@hasMany</span>
    <span class="hljs-attribute">seller</span>: User! <span class="hljs-variable">@belongsTo</span>
    <span class="hljs-attribute">created_at</span>: DateTime!
    <span class="hljs-attribute">updated_at</span>: DateTime!
}
</code></pre><ul>
<li>See how easy it is? Now you should be able to do the <code>Sale</code> object type on your own.</li>
</ul>
<p><strong>The Work Ends:</strong></p>
<ul>
<li>Go to GraphQL Playground and run a query. Here's one that should work wonderfully if the database is seeded correctly and relationships are properly defined.<pre><code>{
  <span class="hljs-keyword">user</span>(id: <span class="hljs-number">1</span>){
      <span class="hljs-type">name</span>
      email
      item {
          <span class="hljs-type">name</span>
          price
          description
          expiry_time
          sales {
              quantity
              amount
          }
      }
  }
}
</code></pre></li>
<li>For any troubleshooting, read the error message on the right of the screen. Most times it will tell you exactly what you need.</li>
</ul>
<p><strong>Conclusion:</strong></p>
<ul>
<li>There are so many more things you can do with GraphQL queries. One of the most important things we can do is authentication, which we'll discuss in another post. We will also discuss mutations in the future. </li>
<li>If you want to get ahead on your own with Lighthouse, please refer to the <a target="_blank" href="https://lighthouse-php.com/tutorial/#what-is-graphql">Lighthouse docs</a>.</li>
<li>I prefer Lighthouse because of its ease-of-use and the ability to edit the schema directly. However, if you want to make a GraphQL API the PHP/Laravel way, then I recommend you check out <a target="_blank" href="https://github.com/rebing/graphql-laravel">Rebing's library</a>.</li>
</ul>
<div class="hn-embed-widget" id="buyme"></div>]]></content:encoded></item><item><title><![CDATA[Lloyd's Thoughts: Why Spotify Could Acquire Clubhouse]]></title><description><![CDATA[After Spotify's purchase of fast-growing startups Gimlet and Anchor way back in February 2019, CEO Daniel Ek said Spotify's goal was to become the "world’s number one audio platform". I highly doubt that when he made this statement, he envisioned tha...]]></description><link>https://blog.lloydmiller.dev/lloyds-thoughts-why-spotify-could-acquire-clubhouse</link><guid isPermaLink="true">https://blog.lloydmiller.dev/lloyds-thoughts-why-spotify-could-acquire-clubhouse</guid><category><![CDATA[opinion pieces]]></category><category><![CDATA[tech ]]></category><category><![CDATA[technology]]></category><category><![CDATA[social media]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Thu, 25 Feb 2021 03:49:47 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1614106357818/36RHnUeAK.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>After Spotify's purchase of fast-growing startups Gimlet and Anchor way back in February 2019, CEO Daniel Ek said Spotify's goal was to become the <a target="_blank" href="https://newsroom.spotify.com/2019-02-06/audio-first/">"world’s number one audio platform"</a>. I highly doubt that when he made this statement, he envisioned that there would've been a fast-growing audio startup where people could have podcast-style talks live with their audience. And to be fair, this business may not have succeeded in 2019. The calculus has changed due to COVID, and with an ever-present competitor in Apple, Spotify may be looking to differentiate itself even more. I personally believe that Daniel Ek and company are looking seriously into Clubhouse.</p>
<p><strong>Background:</strong></p>
<ul>
<li>Clubhouse was founded on March 15, 2020, by Paul Davison and Rohan Seth. </li>
<li>Their latest funding round, a Series B, was led by Andreessen Horowitz and raised $100 million. They are now <a target="_blank" href="https://pitchbook.com/newsletter/clubhouse-picks-up-new-funding-at-1b-valuation">valued at $1 billion</a>.</li>
<li>They now have 2 million WAUs, <a target="_blank" href="https://www.statista.com/statistics/1199871/number-of-clubhouse-users/">according to Statista</a> and have surpassed <a target="_blank" href="https://venturebeat.com/2021/02/18/clubhouse-grows-to-8-million-downloads-for-people-starved-for-audio-conversations/">8 million downloads</a>.</li>
<li>Spotify is a global behemoth in audio. They went public in 2018 after raising a total of <a target="_blank" href="https://www.crunchbase.com/organization/spotify/company_financials">$2.6 billion in funding</a>. They are now currently <a target="_blank" href="https://www.billboard.com/articles/business/9529823/spotify-stock-new-high-stream-on-valuation/">valued at $72 billion</a>.</li>
</ul>
<p><strong>The What:</strong></p>
<ul>
<li>Funnily enough, Daniel Ek was asked just recently about Clubhouse. On Monday, January 22, 2021, Spotify <a target="_blank" href="https://newsroom.spotify.com/2021-02-22/todays-spotify-stream-on-announcements/">held a 90-minute event</a> where they showed off new audio products and podcast ad technology.</li>
<li>About Clubhouse, <a target="_blank" href="https://www.theverge.com/platform/amp/2021/2/23/22295315/spotify-ceo-interview-podcast-daniel-ek-music-stream-on">Ek told The Verge</a> that he's not surprised at Clubhouse's success as it follows the same format that has worked very well for podcasts.<ul>
<li>He went on to say that it's an interesting space that his team is "keeping an eye on." </li>
<li>But he pointed out a weakness of Clubhouse that Spotify is strong in, and that is people want media-on-demand, which they can't get on Clubhouse. </li>
</ul>
</li>
</ul>
<p><strong>The Why:</strong></p>
<ul>
<li>Spotify loves social.<ul>
<li>Spotify has dabbled in social over the years, enabling users to discover, follow, and interact with other users, artists, and podcasts. Clubhouse's discovery features are also great; they help users know who to follow and what rooms may interest them.</li>
<li>They have many features that are social in nature and that drive their success, such as seeing <a target="_blank" href="https://support.spotify.com/us/article/friend-feed/">what friends are listening to</a>, user profiles, and a new <a target="_blank" href="https://newsroom.spotify.com/2020-07-28/your-squad-can-now-stream-simultaneously-using-spotifys-group-session-beta/">party mode</a>.</li>
<li>Spotify has shown that it's not afraid to try social features that seem a bit "out there". Remember the news about them <a target="_blank" href="https://techcrunch.com/2020/01/21/spotify-test-lets-influencers-post-stories-to-introduce-their-own-playlists/">testing Stories</a>?</li>
</ul>
</li>
<li>Clubhouse may want to move past (some of its) ephemerality in the future.<ul>
<li>Currently, after a talk is finished on Clubhouse, it is gone forever. 'Live' can be monetized, but just like other platforms with live features such as Instagram, YouTube, and Twitch, users are able to save these live shows for further viewing.</li>
<li>When Facebook and Twitter follow through on their plans to release their Clubhouse competitors, they will most likely integrate them into their Stories features. Stories (in Twitter's case, Fleets) are ephemeral, yes, but they last for 24 hours. That brings more eyes and ears for ad dollars.</li>
</ul>
</li>
<li>There is increased competition.<ul>
<li><a target="_blank" href="https://techcrunch.com/2020/12/17/twitter-launches-its-voice-based-spaces-social-networking-feature-into-beta-testing/">Twitter's</a> and <a target="_blank" href="https://www.theverge.com/2021/2/10/22276645/facebook-clubhouse-social-audio-app-develop">Facebook's</a> moves to compete with Clubhouse could eventually enable them to disrupt the podcasting space.</li>
<li>Can you imagine a new kind of podcast that lasts only 24 hours? Spotify can't afford huge competitors other than Apple in the podcasting space.</li>
<li>Also, Clubhouse surely can't afford to compete against giants like Facebook and Twitter right now. Sure, Clubhouse has money to burn, but they're not going to win against companies with the infrastructure, talent, money, influence, and established networks like Facebook and Twitter have. </li>
<li>Oh, and if they acquire Clubhouse, they could stop them from being a likely and formidable competitor.</li>
</ul>
</li>
<li>Their marriage would be beautiful.<ul>
<li>Spotify's podcasts don't have the magic of a live discussion. I think podcasts and live audio should be merged, and it would be a beautiful thing. </li>
</ul>
</li>
</ul>
<p><strong>The How:</strong></p>
<ul>
<li>Clubhouse is "cheap" now, and it's my hunch that their valuation will be quadrupled by this summer.</li>
<li>Spotify spent nearly $600 million in 2020 on acquisitions and other investments, but they still have over <a target="_blank" href="https://s22.q4cdn.com/540910603/files/doc_financials/2020/q4/4e770a8c-ee99-49a8-9f9e-dcc191807b56.pdf">$1.3 billion in the bank</a>. A cash plus stock deal doesn't look farfetched. <ul>
<li><strong>AND AMAZINGLY</strong>, there was news released today that said Spotify took on <a target="_blank" href="https://www.billboard.com/articles/business/streaming/9531101/spotify-debt-exchangeable-notes/">$1.3 billion in debt</a> for acquisitions. Hmm 🤔</li>
</ul>
</li>
<li>I made some imperfectly fleshed-out tweets a while back on how I think an acquisition could go. One of them went like this: 
<blockquote><p>Prediction: Spotify will buy Clubhouse amid competition from Twitter and make it run independently. It will scale with the help of Spotify's infrastructure.</p>— The Lloydinator (@lloydmiller) <a href="https://twitter.com/lloydmiller/status/1355303412276060166?ref_src=twsrc%5Etfw">January 29, 2021</a></blockquote> <ul>
<li>The alternate thing that could happen is Spotify immediately brings them in-house and renames the app Spotify Clubhouse. They may let it operate as a separate app but Clubhouse's features will get deeply entwined with Spotify's. Spotify would have more powerful ways to get ads into more ears.</li>
</ul>
</li>
</ul>
<p><strong>Conclusion:</strong></p>
<ul>
<li>Why aren't more people begging for this to happen? To be fair, I'm just a layman, so it could be a very bad idea, or impossible. However, I am also a very logical person, and I highly doubt it would be a terrible or impossible move.</li>
<li>I do see arguments against a quick move, though. One could say that Spotify would be throwing its weight around too quickly and lose sight of <a target="_blank" href="https://newsroom.spotify.com/2021-02-22/todays-spotify-stream-on-announcements/">its podcasting goals</a>. </li>
<li>But wouldn't it be prudent to buy Clubhouse while they're cheap, and give them a year or more of leeway to grow with Spotify's resources? They could do this while Spotify consolidates its position in podcasting and only fully integrates Clubhouse when it's ready. Wasn't that Facebook's playbook with Instagram?</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Coding Fail of the Week (February 14-20, 2021)]]></title><description><![CDATA[This is my first of many Coding Fails of the Week. In this edition, I won't even have to discuss the code because even a 5-year-old kid could spot the error.
How it happened: In constructing my Laravel API, I needed to test out the ability to add a p...]]></description><link>https://blog.lloydmiller.dev/coding-fail-of-the-week-february-14-20-2021</link><guid isPermaLink="true">https://blog.lloydmiller.dev/coding-fail-of-the-week-february-14-20-2021</guid><category><![CDATA[coding]]></category><category><![CDATA[Laravel]]></category><category><![CDATA[APIs]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Mon, 22 Feb 2021 04:53:15 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1613969343397/J5LcQsNTu.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is my first of many Coding Fails of the Week. In this edition, I won't even have to discuss the code because even a 5-year-old kid could spot the error.</p>
<p><strong>How it happened:</strong> In constructing my Laravel API, I needed to test out the ability to add a post by a certain time. So I whipped out Postman, put in my date, and hit Enter. I swore it was error-proof; my API was working well earlier and the minor changes I made shouldn't have broken it. But alas, I got an error saying the date was in the wrong format. :/</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613968077865/7kaYW-QHv.jpeg" alt="Screenshot 2021-02-05 174117.jpg" /></p>
<p><strong>What really happened:</strong> Biggest duh moment of my life. If you haven't seen the error yet, look carefully. Ignore the error message because it will throw you off. If you haven't seen it yet, I'll give you a clue: 2021 isn't a leap year. I spent about 90 minutes trying to find what was causing this issue when the answer was literally right there.</p>
<p><strong>What I learned:</strong> Don't just look at the obvious. The real solution to our problems is often hidden in plain sight.</p>
]]></content:encoded></item><item><title><![CDATA[Integrate Twilio Into Laravel Like a Senior Dev]]></title><description><![CDATA[In many tutorials around the web, authors use and define services like Twilio in the controller, which flies in the face of the principles of MVC frameworks like Laravel. It also doesn't heed DRY principles as defined in the book, The Pragmatic Progr...]]></description><link>https://blog.lloydmiller.dev/integrate-twilio-into-laravel-like-a-senior-dev</link><guid isPermaLink="true">https://blog.lloydmiller.dev/integrate-twilio-into-laravel-like-a-senior-dev</guid><category><![CDATA[Laravel]]></category><category><![CDATA[twilio]]></category><category><![CDATA[APIs]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Mon, 22 Feb 2021 01:11:43 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1613943589677/kQ8H5-qNi.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In many tutorials around the web, authors use and define services like Twilio in the controller, which flies in the face of the principles of MVC frameworks like Laravel. It also doesn't heed DRY principles as defined in the book, <a target="_blank" href="https://thevaluable.dev/dry-principle-cost-benefit-example/">The Pragmatic Programmer</a>. In this tutorial, we'll discuss how you can implement third-party services like Twilio into your Laravel application, the right way.</p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li>Download the Twilio SDK by opening your terminal and typing <code>composer require twilio/sdk</code> in your project.</li>
<li>Ensure that you're all set up with Twilio and that you have your Twilio number, <a target="_blank" href="https://www.twilio.com/console">Account SID and Auth Token</a> ready.</li>
<li>Place your SID, token, and number as separate values in your .env file. For this tutorial, I'll just name these variables <code>TWILIO_SID</code>, <code>TWILIO_TOKEN</code>, <code>TWILIO_NUMBER</code>.</li>
</ul>
<p><strong>First Steps:</strong></p>
<ul>
<li>In your project's config folder, go to services.php. This is where we will define our new third-party Twilio service.</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">return</span> [
    ...
    <span class="hljs-string">'twilio'</span> =&gt; [
        <span class="hljs-string">'sid'</span> =&gt; env(<span class="hljs-string">'TWILIO_SID'</span>),
        <span class="hljs-string">'token'</span> =&gt; env(<span class="hljs-string">'TWILIO_TOKEN'</span>),
        <span class="hljs-string">'phone'</span> =&gt; env(<span class="hljs-string">'TWILIO_NUMBER'</span>)
    ]
]
</code></pre><p><strong>The Work Begins:</strong></p>
<ul>
<li>In your app folder, create a Services folder. It is there that you'll create a file called Twilio.php.</li>
<li>Our service will only need to authenticate the client and send text messages, so we'll only be using <code>Twilio\Rest\Client</code>. We'll also need to set the namespace for our file so that we can access it elsewhere. </li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Services</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Twilio</span>\<span class="hljs-title">Rest</span>\<span class="hljs-title">Client</span>;
</code></pre><ul>
<li>The first thing that we'll do is define some properties so that we can plug in the values that are in our .env file.<ul>
<li>After that, we'll use the constructor to pass the values to our properties.</li>
</ul>
</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Services</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">Twilio</span>\<span class="hljs-title">Rest</span>\<span class="hljs-title">Client</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Twilio</span> </span>{
    <span class="hljs-keyword">protected</span> $sid;
    <span class="hljs-keyword">protected</span> $token;
    <span class="hljs-keyword">protected</span> $phone;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{
        <span class="hljs-keyword">$this</span>-&gt;sid = config(<span class="hljs-string">'services.twilio.sid'</span>);
        <span class="hljs-keyword">$this</span>-&gt;token = config(<span class="hljs-string">'services.twilio.token'</span>);
        <span class="hljs-keyword">$this</span>-&gt;phone = config(<span class="hljs-string">'services.twilio.phone'</span>);
    }
}
</code></pre><p><strong>The Work Continues:</strong></p>
<ul>
<li>Twilio demands that every request be authenticated with the use of our SID and Auth Token. So instead of calling a separate method to authenticate for every request that we make, we'll just house our client authentication code in our constructor. We'll instantiate the client, and pass it to a property so that we can use it later.</li>
</ul>
<pre><code>...
<span class="hljs-keyword">protected</span> $client;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">$this</span>-&gt;sid = config(<span class="hljs-string">'services.twilio.sid'</span>);
    <span class="hljs-keyword">$this</span>-&gt;token = config(<span class="hljs-string">'services.twilio.token'</span>);
    <span class="hljs-keyword">$this</span>-&gt;phone = config(<span class="hljs-string">'services.twilio.phone'</span>);

    <span class="hljs-keyword">$this</span>-&gt;client = <span class="hljs-keyword">new</span> Client(<span class="hljs-keyword">$this</span>-&gt;sid, <span class="hljs-keyword">$this</span>-&gt;token);
}
</code></pre><ul>
<li>Finally, we'll just define one method to send our text messages. </li>
</ul>
<pre><code>...
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">__construct</span>(<span class="hljs-params"></span>)</span>{
    <span class="hljs-keyword">$this</span>-&gt;sid = config(<span class="hljs-string">'services.twilio.sid'</span>);
    <span class="hljs-keyword">$this</span>-&gt;token = config(<span class="hljs-string">'services.twilio.token'</span>);
    <span class="hljs-keyword">$this</span>-&gt;phone = config(<span class="hljs-string">'services.twilio.phone'</span>);

    <span class="hljs-keyword">$this</span>-&gt;client = <span class="hljs-keyword">new</span> Client(<span class="hljs-keyword">$this</span>-&gt;sid, <span class="hljs-keyword">$this</span>-&gt;token);
}

<span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">send</span>(<span class="hljs-params">$recipient_phone, $message</span>)</span>{
    $sms = <span class="hljs-keyword">$this</span>-&gt;client-&gt;messages-&gt;create($recipient_phone, [
        <span class="hljs-string">'from'</span> =&gt; <span class="hljs-keyword">$this</span>-&gt;phone,
        <span class="hljs-string">'body'</span> =&gt; $message
    ]);

    <span class="hljs-keyword">return</span> $sms;
}
</code></pre><p><strong>The End:</strong></p>
<ul>
<li>Now, when you want to use this service, like in an <a target="_blank" href="https://laravel.com/docs/8.x/events">Event Listener</a>, all you'll need to do is import the service, instantiate the service, and use the method!</li>
</ul>
<pre><code><span class="hljs-meta">&lt;?php</span>

<span class="hljs-keyword">namespace</span> <span class="hljs-title">App</span>\<span class="hljs-title">Listeners</span>;

<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Events</span>\<span class="hljs-title">SomeEvent</span>;
<span class="hljs-keyword">use</span> <span class="hljs-title">App</span>\<span class="hljs-title">Services</span>\<span class="hljs-title">Twilio</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DoThisAfterSomeEvent</span>
</span>{
    ...
    <span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params">SomeEvent $event</span>)</span>{
        ...
        $twilio = <span class="hljs-keyword">new</span> Twilio();
        $twilio-&gt;send($recipient_phone, $message);
    }
}
</code></pre><div class="hn-embed-widget" id="buyme"></div>]]></content:encoded></item><item><title><![CDATA[Log Your Laravel API Routes With Middleware]]></title><description><![CDATA[From time-to-time, you will happen upon a problem between your frontend and API that you can't quite see. A dd() in your controller or a console.log() in your frontend just doesn't seem to cut it and the problem seems out of reach. Luckily, Laravel p...]]></description><link>https://blog.lloydmiller.dev/log-your-laravel-api-routes-with-middleware</link><guid isPermaLink="true">https://blog.lloydmiller.dev/log-your-laravel-api-routes-with-middleware</guid><category><![CDATA[Laravel]]></category><category><![CDATA[APIs]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Sun, 21 Feb 2021 20:04:40 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1613929798807/M0c7chYTR.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>From time-to-time, you will happen upon a problem between your frontend and API that you can't quite see. A <code>dd()</code> in your controller or a <code>console.log()</code> in your frontend just doesn't seem to cut it and the problem seems out of reach. Luckily, Laravel provides you with a way to see the request as it hits your API, and that is through middleware. </p>
<p><strong>First Steps:</strong></p>
<ul>
<li>In your terminal, go to where your project's API is located, and type <code>php artisan make:middleware LogRoute</code>. This will create a file in app/Http/Middleware.</li>
<li>If you haven't already, <a target="_blank" href="https://www.postman.com/downloads/">download Postman</a>. It will help you to test your API without having to go through your frontend.</li>
</ul>
<p><strong>The Meat:</strong></p>
<ul>
<li>In your LogRoute.php, import the Log Facade by typing 
<code>use Illuminate\Support\Facades\Log</code> at the top of your file.</li>
<li>Next, in our <code>handle()</code> function, we'll need to first ensure that our request can pass unimpeded between handlers. <pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params">Request $request, <span class="hljs-built_in">Closure</span> $next</span>)</span>{
  $response = $next($request)
  ...
}
</code></pre></li>
<li>Next, we'll need to tell our Middleware to stop running if our app environment isn't local. We'll do that with an if statement: 
<code>if (app()-&gt;environment('local'))</code>.</li>
<li><p>Now we can finish up our middleware by making an array of our request and response and logging that data. Our response is what's most important, as it will have a stack trace to help us debug.</p>
<pre><code><span class="hljs-keyword">public</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">handle</span>(<span class="hljs-params">Request $request, <span class="hljs-built_in">Closure</span> $next</span>)</span>{
  $response = $next($request);

  <span class="hljs-keyword">if</span> (app()-&gt;environment(<span class="hljs-string">'local'</span>)){
      $data = [
          <span class="hljs-string">'request'</span> =&gt; $request-&gt;all(),
          <span class="hljs-string">'response'</span> =&gt; $response-&gt;getContent()
      ];
      Log::info(json_encode($data));
  }

  <span class="hljs-keyword">return</span> $response;
}
</code></pre></li>
</ul>
<p><strong>Finishing Up:</strong></p>
<ul>
<li>To use our middleware, we first have to register it. Go to Kernel.php in your app/Http folder and scroll all the way down to <code>$routeMiddleware</code>. When you're there, add this line: <code>'log.route' =&gt; \App\Http\Middleware\LogRoute::class,</code>. </li>
<li>Now go to your API routes and place <code>-&gt;middleware('log.route')</code> at the end of the route that you want to test.</li>
</ul>
<p><strong>The End:</strong></p>
<ul>
<li>Test your API by making a request with Postman. </li>
<li>Go to storage/logs/laravel.log to see what's happening. </li>
</ul>
<p><strong>One More Thing:</strong></p>
<ul>
<li>You can add more methods in your middleware to test requests, such as <code>$request-&gt;getMethod()</code> which will tell you the type of request method that hit the API (POST, GET, etc.) and <code>$request-&gt;getUri()</code> which will tell you the source of the request.</li>
</ul>
<div class="hn-embed-widget" id="buyme"></div>]]></content:encoded></item><item><title><![CDATA[Zoom OAuth with NEXT.js App]]></title><description><![CDATA[To make apps using Zoom's API, authentication is a must. You can either do this with JWT or OAuth. If you're building a third-party service or application, OAuth is the way to go. We'll not be using an SDK for this tutorial; everything will be done f...]]></description><link>https://blog.lloydmiller.dev/zoom-oauth-with-nextjs-app</link><guid isPermaLink="true">https://blog.lloydmiller.dev/zoom-oauth-with-nextjs-app</guid><category><![CDATA[Next.js]]></category><category><![CDATA[APIs]]></category><category><![CDATA[REST API]]></category><category><![CDATA[Node.js]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Lloyd Miller]]></dc:creator><pubDate>Sun, 21 Feb 2021 05:33:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1613850143601/jOotH7Cnj.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>To make apps using Zoom's API, authentication is a must. You can either do this with JWT or OAuth. If you're building a third-party service or application, OAuth is the way to go. We'll not be using an SDK for this tutorial; everything will be done from scratch. </p>
<p><strong>Preliminaries:</strong></p>
<ul>
<li>Register your app in <a target="_blank" href="https://marketplace.zoom.us/">Zoom's Marketplace</a>. More details on how to do so can be found <a target="_blank" href="https://marketplace.zoom.us/docs/guides/build/oauth-app#register-your-app">here</a>.</li>
<li>Open your terminal, go to the folder where your code is stored, and install your Next.js app by running: <code>npm init next-app my-next-app &amp;&amp; cd my-next-app</code>. Of course, you can rename 'my-next-app' into anything you choose.</li>
<li><a target="_blank" href="https://ngrok.com/download">Download and install Ngrok</a>. We'll need it because Zoom doesn't allow redirects to localhost. You can choose to <a target="_blank" href="https://phoenixnap.com/kb/how-to-edit-hosts-file-in-windows-mac-or-linux">edit your Host file instead</a>, but this is much easier.</li>
</ul>
<p><strong>Further setup:</strong></p>
<ul>
<li>Create a .env.development or .env.local file in your project's root. </li>
<li>Using your terminal, go to the folder where you installed Ngrok, and run <code>ngrok http 3000</code>. Change '3000' to whatever port your Next app will be listening on.</li>
<li>Copy the secure Forwarding address to your project's .env file.<ul>
<li><strong>NB:</strong> If you're using a .env.local file, ensure that you have 'NEXT_PUBLIC' <a target="_blank" href="https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser">appended to every variable</a> for this guide. </li>
</ul>
</li>
<li><p>Finally, insert the Forwarding address into 'Redirect URL for OAuth' and 'Whitelist URL' fields on the App Credentials page of your Zoom app. </p>
<ul>
<li><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613858694888/8S5aYF-J1.jpeg" alt="Screenshot 2021-02-20 170103.jpg" /></li>
<li><a target="_blank" href="https://marketplace.zoom.us/docs/guides/auth/oauth/oauth-scopes">Scopes</a> are just as important as the 'Redirect URL for OAuth' and 'Whitelist URL' fields so don't forget to add them to your app dashboard.</li>
</ul>
</li>
<li>You may also want to place your Client ID and Client Secret in your .env file as a more secure way of using them in your app.</li>
</ul>
<p><strong>The Job Begins:</strong></p>
<ul>
<li>We need to direct the user to the address <code>https://zoom.us/oauth/authorize</code> with a few params. </li>
<li>On your index.js page (or the page you plan to start the Zoom auth process from), we'll use the React Hook <code>useEffect</code> to mount this address when the page loads. We'll also be using the hook <code>useState</code>.</li>
</ul>
<pre><code><span class="hljs-keyword">import</span> React, {useEffect, useState} <span class="hljs-keyword">from</span> <span class="hljs-string">'react'</span>

<span class="hljs-keyword">const</span> IndexPage = <span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> [url, setUrl] = useState(<span class="hljs-literal">null</span>)

    <span class="hljs-keyword">const</span> makeLink = <span class="hljs-function">() =&gt;</span> {
        <span class="hljs-keyword">const</span> thisUrl = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">'https://zoom.us/oauth/authorize'</span>)
        thisUrl.searchParams.set(<span class="hljs-string">'response_type'</span>, <span class="hljs-string">'code'</span>)
        thisUrl.searchParams.set(<span class="hljs-string">'redirect_uri'</span>, process.env.NEXT_PUBLIC_NGROK_URL)
        thisUrl.searchParams.set(<span class="hljs-string">'client_id'</span>, process.env.NEXT_PUBLIC_ZOOM_CLIENT)
        setUrl(thisUrl.href)
    }

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

    <span class="hljs-keyword">return</span> (
        <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">{url}</span>&gt;</span>Start Zoom OAuth Process<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></span>
    )
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> IndexPage
</code></pre><ul>
<li>If you look at the code snippet you'll see we've used JavaScript's powerful URL Browser API. We'll be using it throughout this guide to help us manipulate URLs.</li>
<li>Now when we click the link, we should see something like this: </li>
<li><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1613876101221/j5kjJSmtq.jpeg" alt="Screenshot 2021-02-20 191002.jpg" /><ul>
<li><strong>NB:</strong> Please remember to put the same redirect URL into both 'Redirect URL for OAuth' and 'Whitelist URL' fields. And <strong>don't forget your Scopes!</strong> That's how the customer will know what your app will do with their permission.</li>
</ul>
</li>
</ul>
<p><strong>The Job Continues:</strong></p>
<ul>
<li>When it's redirected back to your site, you will see a <code>code</code> param followed by a random string in the URL. We'll capture that using a data fetching feature called 'getServerSideProps'. This feature is used for pre-rendering with Next SSR. If you're familiar with writing Node, you won't have much of an issue here.</li>
<li>First, we will generate a full URL from our request: </li>
</ul>
<pre><code>...
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> getServerSideProps = <span class="hljs-keyword">async</span> ({req, res}) =&gt; {
    <span class="hljs-keyword">const</span> thisUrl = <span class="hljs-keyword">new</span> URL(req.url, `http:<span class="hljs-comment">//${req.headers.host}`)</span>
    ...
}
<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> IndexPage
</code></pre><ul>
<li>Next, Zoom demands that in order for us to get the access token to the user's account, we'll be making a POST request with a special code in our header to Zoom's servers. We'll generate our code like this: <code>base64(CLIENT_ID:CLIENTSECRET)</code>. That's pseudocode of course, so let's turn that into real code. We'll be using <a target="_blank" href="https://www.w3schools.com/nodejs/ref_buffer.asp">Node's Buffer class</a>, which helps in the handling of binary data.</li>
</ul>
<pre><code><span class="hljs-keyword">if</span> (thisUrl.searchParams.<span class="hljs-keyword">get</span>(<span class="hljs-string">'code'</span>)){
        <span class="hljs-keyword">const</span> urlParam = thisUrl.searchParams.<span class="hljs-keyword">get</span>(<span class="hljs-string">'code'</span>)

        <span class="hljs-keyword">const</span> <span class="hljs-keyword">data</span> = process.env.NEXT_PUBLIC_ZOOM_CLIENT_ID + <span class="hljs-string">':'</span> + 
            process.env.NEXT_PUBLIC_ZOOM_CLIENT_SECRET
        <span class="hljs-keyword">const</span> newData = Buffer.from(<span class="hljs-keyword">data</span>, <span class="hljs-string">'utf8'</span>)
        <span class="hljs-keyword">const</span> b64string = newData.toString(<span class="hljs-string">'base64'</span>)
...
</code></pre><ul>
<li>We're almost there. In order to get our access token, we should generate the URL for our POST request using the same URL API that is <a target="_blank" href="https://nodejs.org/api/url.html">also a part of Node</a>. </li>
</ul>
<pre><code><span class="hljs-keyword">const</span> zoomUrl = <span class="hljs-keyword">new</span> URL(<span class="hljs-string">'https://zoom.us/oauth/token'</span>)
zoomUrl.searchParams.<span class="hljs-keyword">set</span>(<span class="hljs-string">'grant_type'</span>, <span class="hljs-string">'authorization_code'</span>)
zoomUrl.searchParams.<span class="hljs-keyword">set</span>(<span class="hljs-string">'code'</span>, urlParam)
zoomUrl.searchParams.<span class="hljs-keyword">set</span>(<span class="hljs-string">'redirect_uri'</span>, process.env.NEXT_PUBLIC_NGROK_URL)
</code></pre><ul>
<li>Above you can see that the string we captured from the <code>code</code> param is passed into the new URL in its <code>code</code> param.</li>
<li>Now, let's make the POST request to the URL we've made above. I like using try-catch blocks, as I can handle errors a lot better and the code is more organized using async-await.</li>
</ul>
<pre><code><span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> options = {
        <span class="hljs-attr">method</span>: <span class="hljs-string">'POST'</span>,
        <span class="hljs-attr">headers</span>: {
            <span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'application/json'</span>,
            <span class="hljs-string">'Authorization'</span>: <span class="hljs-string">'Basic '</span> + b64string
        }
    }
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(zoomUrl.href, options)  
    <span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> response.json()
    ...
}
<span class="hljs-keyword">catch</span>(e){
    <span class="hljs-built_in">console</span>.log(e)
}
</code></pre><ul>
<li>If we click our link again (which by the way is totally fine), it will skip the permission page and you will see a new <code>code</code> generated. We'll pass that code into the 'code' param in our new URL, and use the base-64 string we generated in our header. We wait for a response using the Fetch API (not naturally a part of Node but included with Next) and convert the response to JSON.</li>
<li>If all goes well, we can then retrieve the user using the access token retrieved from the JSON object that we received from the last request. 
So the complete try-catch block should look like this: </li>
</ul>
<pre><code><span class="hljs-keyword">try</span> {
    <span class="hljs-keyword">const</span> options = {
        method: <span class="hljs-symbol">'POST</span>',
        headers: {
            <span class="hljs-symbol">'Content</span>-Type': <span class="hljs-symbol">'application</span>/json',
            <span class="hljs-symbol">'Authorization</span>': <span class="hljs-symbol">'Basic</span> ' + b64string
        }
    }
    <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(zoomUrl.href, options)  
    <span class="hljs-keyword">const</span> json = <span class="hljs-keyword">await</span> response.json()

    <span class="hljs-keyword">if</span> (json.access_token){
        <span class="hljs-keyword">const</span> newOptions = {
            method: <span class="hljs-symbol">'GET</span>,
            headers: {
                <span class="hljs-symbol">'Authorization</span>': <span class="hljs-symbol">'Bearer</span> ' + json.access_token
            }
        }
        <span class="hljs-keyword">const</span> preUser = <span class="hljs-keyword">await</span> fetch(<span class="hljs-symbol">'https</span>:<span class="hljs-comment">//api.zoom.us/v2/users', newOptions)</span>
        <span class="hljs-keyword">const</span> zoomUser = <span class="hljs-keyword">await</span> preUser.json()

        <span class="hljs-keyword">return</span> {
            props: {zoomUser}
        }
    }
}
catch(e){
    console.log(e)
}
</code></pre><ul>
<li>Since this is basically Node, you should look for errors in your console and not in the browser. However, if you've been following along perfectly, you shouldn't have any issues. Now we need to get this data to the client-side.</li>
</ul>
<p><strong>The Job Ends:</strong></p>
<ul>
<li>If you look at the snippet above, you'll realize that we're returning our Zoom user object in props. So just like with any React app, we can pass our data into the client like so: </li>
</ul>
<pre><code>const IndexPage = <span class="hljs-function"><span class="hljs-params">({zoomUser})</span> =&gt;</span> {
    <span class="hljs-built_in">console</span>.log(zoomUser)
    ...
}
</code></pre><ul>
<li>Now what happens next is all up to you. It is advised that you try to persist your data so that it doesn't disappear if the user refreshes the page or navigates to another page. Access tokens last for an hour, so keep that in mind.</li>
</ul>
<p><strong>Conclusion:</strong></p>
<ul>
<li>If you want to do more with Zoom OAuth, such as refreshing the token, you can learn more in the <a target="_blank" href="https://marketplace.zoom.us/docs/guides/auth/oauth#refreshing">Zoom docs</a>.</li>
<li>If you're wondering how you can persist your data, one good and secure way to do so is through the use of cookies. In another blog post I will discuss how I persist Zoom user data. Ciao for now.</li>
</ul>
]]></content:encoded></item></channel></rss>