<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Barry Melton</title>
    <description>The latest articles on Forem by Barry Melton (@sureisfun).</description>
    <link>https://forem.com/sureisfun</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F181240%2F5e0a1ad7-c481-4aa7-a6e1-9f342603634e.jpeg</url>
      <title>Forem: Barry Melton</title>
      <link>https://forem.com/sureisfun</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sureisfun"/>
    <language>en</language>
    <item>
      <title>Three Weeks with Planetscale and Prisma: A Hands-On Review</title>
      <dc:creator>Barry Melton</dc:creator>
      <pubDate>Tue, 07 Feb 2023 18:13:00 +0000</pubDate>
      <link>https://forem.com/sureisfun/three-weeks-with-planetscale-and-prisma-a-hands-on-review-2mbg</link>
      <guid>https://forem.com/sureisfun/three-weeks-with-planetscale-and-prisma-a-hands-on-review-2mbg</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Hi. I'm a long-time lurker, first time poster. After a recent tech layoff, decided that I have time to test out some of my own projects and contribute more. I hope this post is well received. &lt;/p&gt;

&lt;h2&gt;
  
  
  Planetscale, What is It?
&lt;/h2&gt;

&lt;p&gt;To understand &lt;a href="https://planetscale.com" rel="noopener noreferrer"&gt;Planetscale&lt;/a&gt;, which is a hosted Vitess-as-a-Service platform, you first need to understand &lt;a href="https://vitess.io/" rel="noopener noreferrer"&gt;Vitess&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Vitess is a fork of MySQL that (in simple terms) eliminates the parts of MySQL that make it hard to scale, like foreign keys, and adds tools like query rewriting and shard management that enable truly large scaling. You'll see it in use at places like Pinterest, Facebook, and Slack. Planetscale. &lt;/p&gt;

&lt;p&gt;I'm building out a new service (&lt;a href="https://fancasting.com" rel="noopener noreferrer"&gt;Fancasting.com&lt;/a&gt; - don't judge it harshly, it isn't finished) between job hunting and thought I'd give it a try. Approximately two weeks in, I'm ready to share some thoughts. &lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;p&gt;So the obvious question I had when starting out was "Why would I use MySQL pretending to be Mongo when I could just use Mongo?" Well, there are a lot of good reasons for that. If you've got a ton of experience with MySQL, then there's practically no learning curve. In 26 years of development, approximately 20 of those years relied on MySQL as the backend data store, so this was an easy choice to me. &lt;/p&gt;

&lt;p&gt;Another consideration is scaling -- both systems can massively scale, but in my experience with Mongo, scaling beyond toy app sizes introduces a lot of complexity, requiring skills I don't have. Vitess has similar complexity, but that's where Planetscale comes in. Their promise is to smooth out the scaling difficulties and make things "just work." I can't speak to the veracity of these claims, but considering their pricing model gets more expensive the more you use it, by the time you get to a meaningful stage of scaling, you're probably paying them enough that managed services are incentivized to be useful. I'll put a pin in that until I can say for sure, but I have reasons for confidence that it &lt;em&gt;might&lt;/em&gt; be truthful. Time will tell. &lt;/p&gt;

&lt;p&gt;There are some other nifty features, like automatic daily backups, painless migrations, local emulators, and things that I'll probably touch on later, but it is sufficient to say that working with Planetscale has been downright enjoyable, especially with Prisma. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prisma
&lt;/h2&gt;

&lt;p&gt;You're more likely to be acquainted with &lt;a href="https://prisma.io" rel="noopener noreferrer"&gt;Prisma&lt;/a&gt; than Planetscale, but it is a rather nice [Type|Java]script ORM. If you're familiar with ORMs, you're mostly familiar with Prisma, and while I wouldn't put it on par with Django's ORM or SQLAlchemy, it has most of what you need in an ORM, and where it doesn't have feature completeness, allows you to shell out to raw SQL queries. &lt;/p&gt;

&lt;p&gt;But the important bit is -- you remember those relationships that I mentioned before? The ones that Vitess (and by extension, Planetscale) eliminate? Well, Prisma gives them back via its &lt;code&gt;@relation&lt;/code&gt; capabilities.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  expires      DateTime
  user         User?    @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId       String?
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you eliminate relationships in the data layer, that means you either need to go the painful route of reimplementing them in code. I've done this in the past, and it isn't fun. Prisma allows you to create relational entities at the ORM level though, and for the features that it does support, that means not having to double-query two tables and manually reduce, or leverage the typical Mongo pattern of duplicating and nesting related data -- which is great, right up until a &lt;code&gt;User&lt;/code&gt; that &lt;code&gt;belongs_to&lt;/code&gt; a &lt;code&gt;Comment&lt;/code&gt; updates their profile, which now requires updating every &lt;code&gt;Comment&lt;/code&gt; object they've ever posted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project
&lt;/h2&gt;

&lt;p&gt;Altogether, my Project uses React via NextJS (both are latest versions, but I'm not yet using Next's App Pages), handles authentication via &lt;a href="https://next-auth.js.org/" rel="noopener noreferrer"&gt;NextAuth&lt;/a&gt; and are hosted on &lt;a href="https://netlify.app" rel="noopener noreferrer"&gt;Netlify&lt;/a&gt; which run Next's backend functions. The database is obviously Planetscale. NextAuth has a 'Prisma' adapter which will allow you to sync logins to the backend database using a backend function. I followed &lt;a href="https://planetscale.com/blog/nextjs-netlify-planetscale-starter-app" rel="noopener noreferrer"&gt;this article&lt;/a&gt; to get started, and extended into &lt;a href="https://planetscale.com/blog/how-to-setup-next-js-with-prisma-and-planetscale" rel="noopener noreferrer"&gt;this article&lt;/a&gt; for some more of the finer points, but I'll distill the basics of what I've done. &lt;/p&gt;

&lt;p&gt;Go sign up for Planetscale (I'm on the free plan for the moment, but you do you), then we'll assume you're adding this to an existing NextJS app: &lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up Planetscale
&lt;/h2&gt;

&lt;p&gt;I've done this on Debian via Scoop and Mac OSX, but installing the Planetscale CLI tool is pretty painless either way. For OSX it's&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install planetscale/tap/pscale
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you should have a &lt;code&gt;pscale&lt;/code&gt; command installed. If you want to look at your database, it's &lt;code&gt;pscale shell &amp;lt;DATABASE_NAME&amp;gt; &amp;lt;BRANCH&amp;gt;&lt;/code&gt; -- the default branch is &lt;code&gt;main&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Now let's install some dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;yarn add -D prisma 
yarn add nanoid next-auth @next-auth/prisma-adapter @prisma/client

npx prisma init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file and add an environment variable for your database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DATABASE_URL="mysql://root@127.0.0.1:3309/YOUR-DB-NAME-HERE"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;schema.prisma&lt;/code&gt; file and add the database connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;generator client {
  provider = "prisma-client-js"
  relationMode = "prisma"
}

datasource db {
  provider = "mysql"
  url = env("DATABASE_URL")
  relationMode = "prisma"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note that the &lt;code&gt;referentialIntegrity&lt;/code&gt; mode listed in the articles I linked earlier is deprecated. This is the bit that tells Prisma how to relate models to each other so that it can &lt;code&gt;relate&lt;/code&gt; models that do not have table-level relationships. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For the sake of brevity, I'm going to assume you already have oAuth credentials for the providers you want to use, but this isn't an auth tutorial, so you if you don't, you can feel free to use NextAuth's &lt;a href="https://next-auth.js.org/configuration/providers/credentials" rel="noopener noreferrer"&gt;&lt;code&gt;Credentials&lt;/code&gt;&lt;/a&gt; provider to follow along. &lt;/p&gt;

&lt;p&gt;Next, we'll set up our user account tables. Edit your &lt;code&gt;schema.prisma&lt;/code&gt; file to add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model Account {
  id                 String  @id @default(cuid())
  userId             String
  type               String
  provider           String
  providerAccountId  String
  refresh_token      String?  @db.Text
  access_token       String?  @db.Text
  expires_at         Int?
  token_type         String?
  scope              String?
  id_token           String?  @db.Text
  session_state      String?

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  id            String    @id @default(cuid())
  name          String?
  email         String?   @unique
  emailVerified DateTime?
  image         String?
  accounts      Account[]
  sessions      Session[]
}

model VerificationToken {
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Feel free to add values that you'd like to use here. If you're intending to have a 'nickname' or &lt;code&gt;location&lt;/code&gt; or whatever other attribute on your User object, go ahead and add it now. Or add it later. Migration is super easy. &lt;/p&gt;

&lt;p&gt;You'll see those &lt;code&gt;relation&lt;/code&gt; fields -- that tells Prisma that (for example) the &lt;code&gt;user&lt;/code&gt; on the &lt;code&gt;Session&lt;/code&gt; object relates to a &lt;code&gt;User&lt;/code&gt; object, and the &lt;code&gt;fields&lt;/code&gt; and &lt;code&gt;references&lt;/code&gt; attributes tell it how to map those relations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Now that you've created some models and have some code that could work, let's run our first DB migration: &lt;code&gt;npx prisma db push&lt;/code&gt; will run the SQL commands to create the database tables from your &lt;code&gt;schema.prisma&lt;/code&gt; file. &lt;/p&gt;

&lt;h2&gt;
  
  
  Finish Setting up NextAuth
&lt;/h2&gt;

&lt;p&gt;Create a new backend route at &lt;code&gt;pages/api/auth/[...nextauth].js&lt;/code&gt; and add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
import { PrismaAdapter } from "@next-auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"

export const authOptions = {
  // Tell NextAuth to use the Prisma adapter
  adapter: PrismaAdapter(prisma),

  // Configure one or more authentication providers
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    // ...add more providers here
  ],
  // This is optional, but I like my session object ID to match the UserID
  // for slightly more efficient ID checking. 
  callbacks: [
    session: async (session) =&amp;gt; {
      session.id = session.user.id;
      session.user = session.user;
      return Promise.resolve(session);
    },
  ],
}

export default NextAuth(authOptions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: Follow the &lt;a href="https://next-auth.js.org/configuration/providers/credentials" rel="noopener noreferrer"&gt;&lt;code&gt;Credentials&lt;/code&gt;&lt;/a&gt; instructions if you don't have or don't want to bother with oAuth. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's make the Session Provider object available to the entire application: &lt;/p&gt;

&lt;p&gt;Edit your &lt;code&gt;pages/_app.jsx&lt;/code&gt; to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { SessionProvider } from "next-auth/react"
export default function App({
  Component,
  pageProps: { session, ...pageProps },
}) {
  return (
    &amp;lt;SessionProvider session={session}&amp;gt;
      &amp;lt;Component {...pageProps} /&amp;gt;
    &amp;lt;/SessionProvider&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, let's add a sign in button somewhere to the app. I placed mine in the Header. &lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;components/Header&lt;/code&gt; folder, then in &lt;code&gt;components/Header/Header.jsx&lt;/code&gt;, let's add the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { Suspense } from "react";
import Image from "next/image";
import Logo from "public/logo.svg";
import Link from "next/link";
import { useSession } from "next-auth/react";
import AvatarLoader from "components/Header/AvatarLoader";

export default function Header({ children, className = "" }) {
  const { data: session } = useSession();

  return (
    &amp;lt;div className={`${className}`}&amp;gt;
      &amp;lt;Bootstrap /&amp;gt;
      &amp;lt;nav
        className={`container w-full py-4 mx-auto relative z-10 h-24 ${className}`}
      &amp;gt;
        &amp;lt;div className="relative w-full flex justify-center mb-4"&amp;gt;
          &amp;lt;Link href="/"&amp;gt;
            &amp;lt;Image
              id="logo"
              src={Logo}
              alt="Fancasting"
              height={32}
              width={32}
            /&amp;gt;
          &amp;lt;/Link&amp;gt;
          &amp;lt;Suspense fallback={&amp;lt;div /&amp;gt;}&amp;gt;
            &amp;lt;AvatarLoader session={session} /&amp;gt;
          &amp;lt;/Suspense&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/nav&amp;gt;
      {children}
    &amp;lt;/div&amp;gt;
  );
}

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

&lt;/div&gt;



&lt;p&gt;Now let's create the &lt;code&gt;AvatarLoader&lt;/code&gt; component this expects to exist:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { Suspense } from "react";
import { useSession, signIn } from "next-auth/react";
import Avatar from "components/User/AvatarMenu/Avatar";

function LoginButton() {
  return (
    &amp;lt;button
      onClick={() =&amp;gt; signIn()}
      className="bg-brandOrange text-white px-8 py-2 rounded-md -top-1 right-1 absolute"
    &amp;gt;
      Sign in!
    &amp;lt;/button&amp;gt;
  );
}

export default function AvatarLoader() {
  let session = useSession();

  if (session.status === "loading") return null;
  if (session.status === "unauthenticated") {
    return &amp;lt;LoginButton /&amp;gt;;
  }

  if (session.status === "authenticated" &amp;amp;&amp;amp; session.data.user) {
    return (
      &amp;lt;Suspense&amp;gt;
        &amp;lt;Avatar user={session.data.user} /&amp;gt;
      &amp;lt;/Suspense&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;I won't get into the Avatar -- it's a simple tailwind circle that shows the user's profile picture, and has a menu in a floating div relative to it. This article is way long enough already.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AvatarLoader does something I don't like, which is to manage its own state instead of receiving it via prop. I think this is an anti-pattern, but when playing around with prop drilling, I wasn't able to consistently get the session state from a parent component, and suffered either flashes of unstyled content, or component-swapping.  I'd probably view this as a teaching opportunity of a junior dev had done this, and I should almost certainly revisit it later, but it works now, and gracefully waits until the auth session state is known and stable before deciding whether to render the &lt;code&gt;LoginButton&lt;/code&gt; or the &lt;code&gt;Avatar&lt;/code&gt; component, and doesn't suffer the usual problem with SPAs of showing one then switching to the other, which is a pet peeve of mine. &lt;/p&gt;

&lt;p&gt;At this point, just add the &lt;code&gt;Header&lt;/code&gt; to a page, and then you should have a functional app with working auth. If you click the "Login" button, you should see the NextAuth login screen, and logging in should result in a few new entries to your database. You can verify that success with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pscale shell &amp;lt;DATABASE_NAME&amp;gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SELECT id, name, image FROM User;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Okay, this went a lot longer than I expected it to, and turned into the auth tutorial I said that it wasn't. Some of the things I expected to cover in this article didn't quite make it past the editing room, so if there's interest, I intended to cover some of the more complicated relationships. On the Planetscale side, I intended to better cover migrations, and their super unique branching model. On the programming side, handling &lt;code&gt;Likes&lt;/code&gt; on posts, relating &lt;code&gt;Posts&lt;/code&gt; and &lt;code&gt;Comments&lt;/code&gt; back to a user. How to &lt;code&gt;aggregate&lt;/code&gt; and &lt;code&gt;orderBy&lt;/code&gt; relations -- which were slightly more complex than I was used to. If you want to see those as a followup, put a like on this article! &lt;/p&gt;

&lt;p&gt;On the whole, Prisma and Planetscale have been absolutely delightful to work with. And while Prisma has thrown a couple of wrenches at me (nested relations are syntactically ... not terse), I've been able to make a ton of progress in a short amount of time, despite using a new-to-me toolkit, which I usually advise against. &lt;/p&gt;

&lt;p&gt;As far as use cases, I think Planetscale will do well at the tasks I've given it (storing and retrieving data in normal CRUD-type retrieval patterns is what it was built to do) -- but while I am sure that it will more than happily handle parsing data for purposes of analytics (aggregating submitted posts by popularity, or querying for things that have high interaction this day/week/month) I have some slight concerns that the pricing model will be cost prohibitive for a service I do not ever expect to turn a profit.&lt;/p&gt;

&lt;p&gt;If there were a more direct profit motive to my app, then I'd argue that the increase in productivity almost certainly makes Planetscale a good choice. As it stands, if the service ever becomes "too popular to afford" on Planetscale, I can down-migrate to Vitess directly and host it for a more fixed-cost operation, but will likely miss a lot of its developer-friendly features. &lt;/p&gt;

&lt;p&gt;Until next time, thanks for reading! &lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
  </channel>
</rss>
