Skip to content

Commit

Permalink
Merge pull request #156 from huv1k/feat/pothos
Browse files Browse the repository at this point in the history
👋 Hey strangers,

this PR updates this template with a couple of changes. 

## Schema builder
I decided to switch to [Pothos GraphQL](https://github.com/hayes/pothos) instead of Nexus, because there is [no future](graphql-nexus/nexus-plugin-prisma#1039 (comment)) for `nexus-prisma`, so it doesn't make sense to keep it inside this template. @hayes is doing a great job with Pothos and everything about it is good 🙌

## Move to JWT
I moved the session strategy to `JWT` so it's prepared for edge computing and Next.js middleware.

## New database
I decided to move to [PlanetScale](https://planetscale.com), they provide really generous free tier. They are a new cool kid in the block with database branching which is a perfect feature. If this doesn't suit, you can still you another database that Prisma supports. 

## Fixes
I fixed some problems with types and now it should work as expected. When `JWT` token is not presented, there is a fallback to `userId` and `userRole`. Fixes #29
  • Loading branch information
huv1k committed Jul 31, 2022
2 parents b4fdcb7 + 2e24341 commit 92cccf6
Show file tree
Hide file tree
Showing 34 changed files with 2,924 additions and 4,706 deletions.
10 changes: 5 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
DATABASE_URL="postgresql://"
GITHUB_ID=""
GITHUB_SECRET=""
NEXTAUTH_URL="http://localhost:3000/api/auth"
JWT_SECRET=""
DATABASE_URL=postgresql://
GITHUB_ID=
GITHUB_SECRET=
NEXTAUTH_URL=http://localhost:3000/api/auth
NEXTAUTH_SECRET=
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.graphql
1 change: 0 additions & 1 deletion .graphqlrc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ generates:
src/lib/graphql/generated/:
preset: near-operation-file
presetConfig:
folder: generated
baseTypesPath: types.ts
plugins:
- typescript-operations
Expand Down
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx pretty-quick --staged
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
save-exact=true
6 changes: 3 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
"silent": false
},
{
"match": "./src/lib/nexus/*",
"cmd": "yarn generate:nexus"
"match": "./src/lib/pothos/*",
"cmd": "yarn schema:generate"
},
{
"match": "./src/lib/graphql/*",
"cmd": "yarn generate:hooks"
"cmd": "yarn hooks:generate"
}
]
}
Expand Down
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Nextjs-auth-prisma boilerplate
# Next.js-auth-prisma boilerplate

Build bleeding-edge full-stack applications using **Next.js**, **GraphQL**, **TypeScript** and **Prisma**.

Expand Down Expand Up @@ -27,28 +27,29 @@ I have created this boilerplate because there was a missing one with all feature
- 🛡 [NextAuth.js](https://github.com/nextauthjs/next-auth) - Authentication for Next.js
- 🦅 [urql](https://github.com/FormidableLabs/urql) - Highly customisable GraphQL client with sensitive defaults
- ⚙️ [GraphQL Code Generator](https://github.com/dotansimha/graphql-code-generator) - Generates code out of GraphQL schema
- 🧬 [GraphQL Helix](https://github.com/contrawork/graphql-helix) - Flexible utility functions for building GraphQL servers
- 💄 [Prettier](https://github.com/prettier/prettier) - Formating your code
- 🧘‍♀️ [GraphQL Yoga](https://github.com/dotansimha/graphql-yoga/) - Fully-featured GraphQL Server
- 💄 [Prettier](https://github.com/prettier/prettier) - Formatting your code
- 🤖 [Dependabot](https://github.com/marketplace/dependabot-preview) - Keeping your dependencies up to date

### Run Prisma migrations on save

This boilerplate works out of the box with automatic migrations for rapid prototyping. I described this in my article [Improve prototyping speed of Prisma](https://huvik.dev/blog/improve-prototyping-speed-of-prisma), you can check how it works under the hood.

![](https://i.imgur.com/clz6RjW.gif)
![](https://i.imgur.com/kF73swy.gif)

### Automatic GraphQL hooks generation

Hooks for GraphQL are automatically generated inside `src/lib/grahql/*` from your GraphQL files. You can customize hooks generation inside `.graphqlrc.yaml`.
![](https://i.imgur.com/gFGF2fB.gif)

![](https://i.imgur.com/xNwz7AA.gif)

### Authentication using NextAuth.js

This boilerplate is configured to use [GitHub](https://next-auth.js.org/providers/github) authentication provider. [NextAuth.js](https://github.com/nextauthjs/next-auth) comes with a lot of different [providers](https://next-auth.js.org/configuration/providers). You can choose, which providers suit your needs most.

### Defining custom authorization rules

You can define authorization rules for your resolvers. For example [isAdmin](https://github.com/huv1k/nextjs-auth-prisma/blob/master/src/lib/nexus/rules.ts) rule for listing [all users](https://github.com/huv1k/nextjs-auth-prisma/blob/master/src/lib/nexus/types/user.ts#L24).
You can define authorization rules for your resolvers. You can follow [Pothos's auth plugin](https://pothos-graphql.dev/docs/plugins/scope-auth) documentation or checkout [example](https://github.com/huv1k/nextjs-auth-prisma/blob/master/src/lib/pothos/builder.ts) in this repository.

### Deployment

Expand All @@ -58,7 +59,7 @@ For deployment, you can use [Vercel](https://vercel.com/), this boilerplate work

### Connect your database

You can follow [Prisma getting started](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch-typescript-postgres#connect-your-database), which requires to have running PostgreSQL database running. If you don't want to use a local docker setup, I suggest using [Railway.app](https://railway.app/), which has a nice generous free plan for PostgreSQL databases.
This starter could be used with all databases supported by Prisma. I would suggest using [PlanetScale](https://planetscale.com/), which has a nice generous free plan. You can follow [Prisma getting started](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/relational-databases/connect-your-database-typescript-planetscale) to get your database up and running.

### NextAuth GitHub provider

Expand Down
9 changes: 9 additions & 0 deletions next-auth.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { UserRole, User } from '@prisma/client'
import { JWT, DefaultJWT } from 'next-auth/jwt'

declare module 'next-auth/jwt' {
interface JWT extends DefaultJWT {
role: UserRole
id: User['id']
}
}
5 changes: 4 additions & 1 deletion next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
11 changes: 11 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// @ts-check

/**
* @type {import('next').NextConfig}
**/
module.exports = {
reactStrictMode: true,
images: {
domains: ['avatars.githubusercontent.com'],
},
}
81 changes: 39 additions & 42 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,58 +1,55 @@
{
"name": "next-auth-prisma-template",
"version": "0.0.1",
"name": "nextjs-auth-prisma-template",
"version": "1.0.0",
"license": "MIT",
"author": {
"name": "Lukáš Huvar",
"email": "lukas@huvar.cz",
"url": "https://huvik.dev/"
},
"scripts": {
"build": "yarn generate:nexus && next build",
"build": "yarn schema:generate && yarn hooks:generate && next build",
"dev": "next",
"start": "next start",
"hooks:generate": "graphql-codegen --config .graphqlrc.yaml",
"lint": "next lint",
"postinstall": "yarn prisma generate && yarn generate:nexus",
"generate:hooks": "graphql-codegen --config .graphqlrc.yaml",
"generate:nexus": "ts-node --skip-project --transpile-only src/lib/nexus/schema --nexus-exit"
},
"husky": {
"hooks": {
"pre-commit": "pretty-quick --staged"
}
"postinstall": "yarn prisma generate",
"schema:generate": "tsx src/lib/utils/build-schema.ts",
"start": "next start",
"prepare": "husky install"
},
"dependencies": {
"@next-auth/prisma-adapter": "^0.4.4-canary.64",
"@prisma/client": "2.24",
"graphql": "^15.5.0",
"graphql-helix": "1.6.1",
"next": "^11.0.0",
"next-auth": "^3.27.0",
"next-urql": "3.0.0",
"nexus": "1.0.0",
"nexus-prisma": "^0.28.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-is": "17.0.2",
"urql": "2.0.2"
"@graphql-yoga/node": "2.13.3",
"@next-auth/prisma-adapter": "1.0.3",
"@pothos/core": "3.13.0",
"@pothos/plugin-prisma": "3.14.0",
"@pothos/plugin-scope-auth": "3.11.0",
"@prisma/client": "4.0.0",
"graphql": "16.5.0",
"next": "12.2.3",
"next-auth": "4.10.0",
"next-urql": "3.3.3",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-is": "18.2.0",
"urql": "2.2.2"
},
"devDependencies": {
"@graphql-codegen/cli": "1.21.4",
"@graphql-codegen/near-operation-file-preset": "^1.18.0",
"@graphql-codegen/typed-document-node": "1.18.8",
"@graphql-codegen/typescript": "1.21.0",
"@graphql-codegen/typescript-operations": "1.17.15",
"@graphql-codegen/typescript-urql": "2.0.3",
"@graphql-typed-document-node/core": "3.1.0",
"@types/next-auth": "3.1.26",
"eslint": "^7.28.0",
"eslint-config-next": "^11.0.0",
"graphql-playground-html": "^1.6.29",
"husky": "4.3.8",
"prettier": "2.2.1",
"pretty-quick": "3.1.0",
"prisma": "2.24",
"ts-node": "9.1.1",
"typescript": "^4.3.4"
"@graphql-codegen/cli": "^2.11.3",
"@graphql-codegen/near-operation-file-preset": "2.4.0",
"@graphql-codegen/typed-document-node": "2.3.2",
"@graphql-codegen/typescript": "2.7.2",
"@graphql-codegen/typescript-operations": "2.5.2",
"@graphql-codegen/typescript-urql": "3.6.3",
"@graphql-typed-document-node/core": "3.1.1",
"@types/node": "18.6.3",
"@types/react": "18.0.15",
"eslint": "8.20.0",
"eslint-config-next": "12.2.2",
"husky": "^8.0.0",
"prettier": "2.7.1",
"pretty-quick": "3.1.3",
"prisma": "4.0.0",
"tsx": "3.8.0",
"typescript": "4.7.4"
}
}
59 changes: 29 additions & 30 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
// learn more about it in the docs: https://pris.ly/d/prisma-schema

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
provider = "mysql"
url = env("DATABASE_URL")
referentialIntegrity = "prisma"
}

generator client {
provider = "prisma-client-js"
provider = "prisma-client-js"
previewFeatures = ["referentialIntegrity"]
}

generator nexusPrisma {
provider = "nexus-prisma"
generator pothos {
provider = "prisma-pothos-types"
}

enum Role {
Expand All @@ -21,50 +23,47 @@ enum Role {
}

model Account {
id String @id @default(uuid())
userId String
providerType String
providerId String
providerAccountId String
refreshToken String?
accessToken String?
accessTokenExpires DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
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?
@@unique([providerId, providerAccountId])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
}

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

model User {
id String @id @default(uuid())
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accounts Account[]
sessions Session[]
role Role @default(USER)
}

model VerificationRequest {
id String @id @default(uuid())
model VerificationToken {
identifier String
token String @unique
expires DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([identifier, token])
}
13 changes: 10 additions & 3 deletions src/components/viewer.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import Image from 'next/image'
import { signOut } from 'next-auth/client'
import { UserDetailFragment } from '../lib/graphql/generated/viewer.generated'
import { signOut } from 'next-auth/react'
import { UserDetailFragment } from '../lib/graphql/viewer.generated'

interface Props {
viewer: UserDetailFragment
}

export const Viewer = ({ viewer }: Props) => (
<div>
<Image width="150px" height="150px" src={viewer.image} alt={viewer.name} />
{viewer.image && (
<Image
width="150px"
height="150px"
src={viewer.image}
alt={viewer.name ?? 'User without a name'}
/>
)}
<h2>{viewer.name}</h2>
<button onClick={() => signOut()}>Sign out</button>
</div>
Expand Down
24 changes: 1 addition & 23 deletions src/lib/graphql/generated/schema.graphql
Original file line number Diff line number Diff line change
@@ -1,30 +1,8 @@
### This file was generated by Nexus Schema
### Do not make changes to this file directly

"""
A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.
"""
scalar DateTime

"""
The `JSONObject` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
scalar Json
@specifiedBy(
url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf"
)

type Query {
users: [User]
users: [User!]!
viewer: User
}

enum Role {
ADMIN
SUPERADMIN
USER
}

type User {
id: ID!
image: String
Expand Down
Loading

1 comment on commit 92cccf6

@vercel
Copy link

@vercel vercel bot commented on 92cccf6 Jul 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.