이번 시간에는 Next.js를 기반으로 해서 Prisma, PlanetScale, NextAuth를 사용해서 간단하게 인증 처리를 구현해보도록 하겠습니다. 처음이 어렵지 이것만 공부(정리)를 잘 해놓는다면 여러 사이드 프로젝트를 만드는데 큰 도움이 될 것입니다. 그만큼 저는 인증이 제일 어렵습니다... 😂😢
DB를 직접 구축해서 사용해도 되긴 하지만 좀 더 빠르고 편하게 사용하고 싶었기 때문에 저는 PlanetScale과 Vercel Storage 중에 어떤걸 사용해볼까 고민하다가 PlanetScale이 더 무료로 제공되는게 좋아보이기도 하고 Vercel Storage는 아직 나온지 얼마 안되서 PlanetScale보다 덜 유명한거 같아서 PlanetScale을 선택했습니다.
PlanetScale is the world’s most advanced serverless MySQL platform. - 홈페이지
메인 소개부분에서는 PlanetScale이 세계에서 가장 발전된 서버리스 MySQL 플랫폼이라고 하네요.
서버리스라는 뜻은 진짜 서버가 없다는 뜻이 아니라 사용자(고객) 입장에서 서버가 없는 것처럼 사용하면 된다는 뜻인거 아시죠? 네. 아는척 해봤습니다. 어쨌거나 직접 개발자가 DB 서버를 생성하고 세팅하고 관리하기는 좀 복잡하고 어렵잖아요? 그래서 나온게 PlanetScale 같은 서비스가 아닐까 싶습니다. 최근에는 Vercel Storage라는 서비스도 있고, Neon이라고 해서 Serverless Postgres 서비스도 있습니다. (이것도 유행인가요?? 🤔)
자, 좀 더 메인 페이지를 읽어보도록 하겠습니다. 비슷한 내용인걸 알 수 있습니다.
데이터베이스는 어렵습니다. 우리는 차라리 PlanetScale이 그들을 관리하기를 원합니다. 우리 팀은 고객이 데이터베이스 서버가 아닌 health and fitness 목표를 달성할 수 있도록 지원하는 데 주력하기를 원합니다.
PlanetScale은 엔지니어링 리더로서 제게 중요한 세 가지를 모두 보여줍니다: 신뢰할 수 있는 시스템, 함께 확장할 수 있는 비용 구조, 그리고 팀의 전반적인 효율성에 큰 도움이 됩니다.
역시 중요한건 가격이겠죠? 가격 정책을 살펴보면 다음과 같습니다. 4가지 Plan 으로 되어있습니다.
Hobby 및 Scaler는 사용량 기반 요금제이며 데이터베이스에서 read나 write한 행을 기준으로 청구됩니다. Scaler Pro는 리소스 기반이며 특정 워크로드를 지원하도록 프로비저닝된 인프라를 기반으로 가격이 책정됩니다.
Hobby는 무료 버전인데 특징은 다음과 같습니다. 이정도면 개인이 간단히 사이드 프로젝트 만들때 충분할 거 같습니다.
참고 : https://planetscale.com/docs/concepts/what-is-planetscale
PlanetScale에서 강조하고 있는 핵심 기능을 살펴보겠습니다.
첫번째는 Non-blocking schema changes 입니다. 일반적으로 스키마 변경 할 때 다운타임이 존재합니다. 다운타임은 고객 경험과 신뢰를 해치는 것은 물론이고, 적은 다운타임으로도 수천에서 수백만 달러의 손실이 발생할 수 있습니다. 여기서는 Non-blocking schema changes 워크플로우를 사용해서 테이블 잠금이나 다운타임이 발생하지 않는다고 합니다. - 상세 참고
두번째는 Branching workflow 입니다. 안전한 마이그레이션과 함께 제공되는 Branching 워크플로우를 통해 프로덕션 데이터베이스에서 스키마 변경을 차단하지 않습니다. 스키마 변경사항을 프로덕션 데이터베이스에 직접 적용하는 대신 데이터베이스의 복사본인 브랜치를 만들 수 있습니다.
개발 브랜치에서 프로덕션으로 스키마 변경사항을 배포할 준비가 되면 Deploy Request를 오픈합니다. 배포 요청을 사용하면 프로덕션에 변경 사항을 배포하기 전에 팀에서 스키마 변경 사항을 보고 의견을 제시하고 승인할 수 있습니다.
이건 진짜 괜찮아보입니다👏. 깃허브 브랜치 나누는 것처럼 개발, 운영 브랜치로 나눠놓고, 개발 및 테스트는 개발 브랜치에서 진행하고 실제 운영 배포는 PR처럼 Deploy Request를 통해 하는 방법이네요.
이외에도 다음과 같은 기능을 제공하고 있습니다.
저는 무료 버전인 Hobby 버전으로 생성해보도록 하겠습니다. 간단하게 계정 생성하고 DB 만들기 하면 끝입니다.
처음에 보면 단일 프로덕션 분기가 생성됩니다. main이 기본 브랜치 역할을 합니다.
추가 브랜치를 만들면 소스 분기의 스키마가 새 분기로 복사되어 개발할 격리된 MySQL 인스턴스가 제공됩니다. 저는 dev라는 브랜치를 만들었습니다. 잘 생성이 되었군요.
생성까지 했으니 이제 Prisma를 사용해서 연동해보는 작업을 진행해보겠습니다. Let's go~
Next-generation Node.js and TypeScript ORM. - 홈페이지
Prisma는 직관적인 데이터 모델, 자동 마이그레이션, type-safety 및 자동 완성 덕분에 데이터베이스 작업 시 새로운 수준의 개발자 경험을 제공합니다. 개인적으로 제가 생각하는 가장 큰 장점은 역시 Type-safe와 자동 완성 기능이 아닐까 싶습니다. 그만큼 TypeScript 지원이 잘 되어있습니다.
Prisma에서 Client와 Migrate, 이 두 가지는 핵심 도구(기능)입니다.
플레이그라운드 참고 : https://playground.prisma.io/
(간단하게 Prisma에서 Client, Migrate를 경험해볼 수 있는 플레이그라운드입니다. 아주 잘 되어있네요 🙌)
참고 : https://www.prisma.io/docs/concepts/components/prisma-client
Prisma Client는 데이터에 맞게 자동으로 생성되고 type-safe query builder 입니다.
Prisma Client를 설정하려면 데이터베이스 연결, Prisma Client Generator 및 하나 이상의 모델이 포함된 Prisma 스키마 파일이 필요합니다:
datasource db {
url = env("DATABASE_URL")
provider = "postgresql"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
email String @unique
name String?
}
필요한 Prisma CLI, Prisma Client 라이브러리를 설치해줍니다.
npm install prisma --save-dev
npm install @prisma/client
그리고 나서 prisma generate
명령을 실행해주면 prisma client를 node_modules/.prisma/client 디렉토리에 생성하게 됩니다. 이렇게 되면 PrismaClient를 인스턴스화한 후 코드에서 쿼리 전송을 시작할 수 있습니다:
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// use `prisma` in your application to read and write data in your DB
// run inside `async` function
const newUser = await prisma.user.create({
data: {
name: 'Alice',
email: 'alice@prisma.io',
},
})
const users = await prisma.user.findMany()
❗️ 여기서 중요한 핵심은 Generating Prisma Client 라고 볼 수 있습니다. 위에 prisma.user.create
에서 prisma에 user가 있다는 것을 타입스크립트는 어떻게 알 수 있었을까요? 이는 스키마 파일을 통해 prisma generate를 했기 때문에 그렇습니다.
Prisma Client는 데이터베이스 스키마에 맞게 자동으로 생성되는 데이터베이스 클라이언트입니다. 기본적으로 Prisma Client는 node_modules/.prisma/client 폴더에 생성되지만 사용자 지정 위치를 지정할 수 있습니다.
앞서 Prisma 스키마 파일에 generator client를 명시해둔 것도 Prisma Client를 사용하기 위한 설정인 셈이죠.
generator client {
provider = "prisma-client-js"
}
당연하겠지만 생성된 Prisma Client 코드를 업데이트하려면 Prisma 스키마가 변경될 때마다 Prisma generate 명령을 다시 실행해야 합니다.
Prisma Client generation 을 위한 일반적인 워크플로우 그림은 다음과 같습니다. 굳~
참고 : https://www.prisma.io/docs/concepts/components/prisma-migrate
Prisma Migrate는 다음을 가능하게 하는 필수 데이터베이스 스키마 마이그레이션 도구입니다:
Prisma Migrate는 .sql
마이그레이션 파일의 기록을 생성하며 개발 및 배포 모두에서 역할을 수행합니다. 프로토타이핑하는 경우 혹은 MongoDB를 사용하는 경우 db push 명령어 사용을 고려하십시오.
데이터베이스 마이그레이션은 데이터베이스 스키마의 구조를 수정하고 발전시키는 제어된 변경사항 집합입니다. 마이그레이션은 데이터베이스 스키마를 한 상태에서 다른 상태로 전환하는 데 도움이 됩니다. 예를 들어, 마이그레이션 내에서 테이블과 열을 작성하거나 제거하거나, 테이블에서 필드를 분할하거나, 데이터베이스에 유형과 제약 조건을 추가할 수 있습니다.
Prisma schema를 생성하고 아래 migrate 명령을 실행해봅니다.
prisma migrate dev --name init
그러면 Prisma 스키마가 데이터베이스 스키마와 동기화되고 마이그레이션 기록이 생성되는 것을 알 수 있을 겁니다.
migrations/
└─ 20230713140442_init/
└─ migration.sql
그 상태에서 Prisma schema를 수정해서 테이블에 행 하나를 추가한 다음에, 다시 실행해봅니다. 대신 이름은 다르게 합니다.
prisma migrate dev --name added_job_title
그려면 migration sql 파일이 추가적으로 생성되는 것을 알 수 있습니다.
migrations/
└─ 20230713140442_init/
└─ migration.sql
└─ 20230713140442_added_job_title/
└─ migration.sql
해당 파일을 열어보면 실제 SQL DDL 명령(create, alter ...)이 있는 것을 보실 수 있을 겁니다.
나중에 운영 배포 시에는 migrations 파일이 기록되어있으니까 그대로 실행만 시켜주면 되겠죠? npx prisma migrate deploy
라는 명령으로 실행시켜주면 됩니다.
막상 쭉 보니까 migrate 명령 대신에 db push라는 명령이 있더군요. 어떨때는 db push를 사용하는 거 같고, 어떨때는 migrate를 사용하는거 같아서 좀 헷갈렸습니다. 스택오버플로우에도 그런 질문이 있더군요.
✍️ PlanetScale로 작업할 때는 마이그레이션 대신 db push를 사용하는 것이 좋습니다. 아마 PlanetScale가 기본적으로 Deploy Request를 제공하고 있기 때문에 그런거 아닐까 싶습니다.
참고 : https://github.com/planetscale/nextjs-starter
planetscale에서 nextjs 12 + prisma + planetscale 예시 깃허브 프로젝트가 있더군요. 나름 간단하게 테스트 해보기 좋았습니다.
1단계. DB에 접속할 수 있는 username과 password를 만들어줍니다. planetscale 대시보드를 보면 'Get connection strings'가 있습니다.
Name은 기본으로 해주고, Branch와 Role을 선택해주면 되는데 Admin으로 해줘야지 DDL 명령어가 실행될 수 있습니다.
그렇게 생성이 되면, username과 password가 자동으로 만들어지며 Connect with를 Prisma로 선택하면 .env
파일과 schema.prisma
파일을 어떻게 작성하라고 잘 나와있습니다.
// .env
mysql://<USERNAME>:<PLAIN_TEXT_PASSWORD>@<ACCESS_HOST_URL>/<DATABASE_NAME>?sslaccept=strict
// schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
relationMode = "prisma"
}
이제 기본적으로 프로젝트에 작성되어있는 schema를 prisma db push를 통해 PlanetScale에 생성해줍니다.
npx prisma db push
확인해보면 dev 브랜치에 테이블 2개가 정상적으로 만들어진 것을 알 수 있습니다.
이제 미리 작성되어있는 seed.js를 node로 실행시켜보면 데이터가 기본적으로 초기화되어 저장되는 것을 볼 수 있습니다.
// seed.js
const { PrismaClient } = require('@prisma/client');
const { categories, products } = require('./data.js');
const prisma = new PrismaClient();
const load = async () => {
try {
await prisma.category.deleteMany();
console.log('Deleted records in category table');
await prisma.product.deleteMany();
console.log('Deleted records in product table');
await prisma.$queryRaw`ALTER TABLE Product AUTO_INCREMENT = 1`;
console.log('reset product auto increment to 1');
await prisma.$queryRaw`ALTER TABLE Category AUTO_INCREMENT = 1`;
console.log('reset category auto increment to 1');
await prisma.category.createMany({
data: categories,
});
console.log('Added category data');
await prisma.product.createMany({
data: products,
});
console.log('Added product data');
} catch (e) {
console.error(e);
process.exit(1);
} finally {
await prisma.$disconnect();
}
};
load();
npx prisma studio
명령을 통해 prisma 스튜디오를 띄어서 확인해볼 수 있습니다.
PlanetScale Insights를 통해 쿼리 수행시간이 얼마나 걸렸고, 쿼리 각각 마다 수행시간을 자세히 알 수 있어서 정말 좋은거 같습니다.
이번 시간에는 PlanetScale와 Prisma에 대해 좀 친해지는 시간을 가져봤습니다. 사용하기 전에 인사와 자기소개 한 정도? 라고 보면 되겠네요. PlanetScale 를 알게 된건 사실 얼마 되지 않았는데 나름 신생 회사인가 봅니다. 해외 개발 유튜브에서도 종종 보이고 있는걸 보면 많이 성장한 거 같고요. 아무튼 좋습니다. 저처럼 혼자 끄적거리면서 뭔가 하려는 분들에게 추천합니다. ㅎㅎ
다음 시간에는 NextAuth 까지 살펴본다음에 이를 종합해서 간단한 구글 로그인 처리를 해보는 것까지 마무리하도록 하겠습니다.