🦔

[Part1] TypeScript, PostgreSQL, Next.js, Prisma & GraphQLでApp作成

2022/04/17に公開1

この記事は、Next.js, GraphQL, TypeScript, Prisma, PostgreSQLを使ってフルスタックアプリを構築する講座のPart1です。この記事では、データモデルを作成し、Prismaのさまざまなコンポーネントを探ります。

はじめに

このコースでは、ユーザーがキュレーションされたリンクのリストを閲覧し、お気に入りのリンクをブックマークできるフルスタックアプリ「awesome-links」を構築する方法を学びます。

https://www.youtube.com/watch?v=hZgFAO4_t4Q

アプリは以下の技術で構築されています。

  • Next.js:Reactフレームワーク
  • Apollo Server:GraphQLサーバー
  • Nexus:GraphQLスキーマを構築
  • Apollo Client:GraphQLクライアント
  • Prisma :マイグレーションとデータベースアクセスのためのORMである
  • PostgreSQL:データベース
  • AWS S3:画像アップロード用
  • Auth0:認証
  • TypeScript:プログラミング言語
  • TailwindCSS:ユーティリティ優先のCSSフレームワーク
  • Vercel:デプロイ

コースで扱う内容

  • Prismaを用いたデータモデリング
  • Apollo ServerとNexusを使ったNext.js APIルートのGraphQL APIレイヤーの構築
  • Apollo Clientを使用したGraphQLのページネーション
  • Auth0を用いた認証
  • 認証
  • AWS S3による画像アップロード
  • Vercelへのデプロイメント

今日学べること

本講座のPart1では、まずアプリの要件定義と、Prismaを使ったデータベース層の設定を行います。

前提条件

https://www.youtube.com/watch?v=cwg2Vv0rJCo

想定される知識

このコースは、以下を前提としています。

  • Node.jsの基本的な知識

  • リレーショナルデータベースの基本的な理解 このコースに参加する前にデータベースについてもっと学びたい方は、Prismaのデータガイドでデータベースの仕組み、正しいデータベースの選び方、アプリケーションでデータベースを最大限に活用する方法について詳しく学んでください。

  • Next.jsを使用するため、Reactの知識があることを強く推奨します。

このコースでは以下は、必要ありません。

  • TypeScriptの知識。(ただし、すでにJavaScriptの経験があることを前提とします)
  • Prismaの知識は、本講座で説明します。

最後に、このコースではデータベースとしてPostgreSQLを使用しますが、ほとんどのコンセプトはMySQLなど他のリレーショナルデータベースにも適用できます。

この記事の終わりには、データベースに接続されたNext.jsアプリが完成していることでしょう。

開発環境

このコースに従うには、あなたのマシンにNode.jsがインストールされている必要があります。また、PostgreSQLのインスタンスが動作している必要があります。

https://www.youtube.com/watch?v=7ihvEtBAjRY

リポジトリのクローン

このコースの完全なソースコードは、GitHubで見ることができます。

git clone -b part-1 https://github.com/m-abdelwahab/awesome-links.git

クローンしたディレクトリに移動して、依存関係をインストールし、開発サーバーを起動することができます。

cd awesome-links
npm install
npm run dev

スタータープロジェクトはこんな感じです。

プロジェクトの構成と依存関係を見る

awesome-links/
┣ components/
┃ ┗ Layout/
┣ data/
┃ ┗ links.ts
┣ pages/
┃ ┣ _app.tsx
┃ ┣ about.tsx
┃ ┗ index.tsx
┣ public/
┣ styles/
┃ ┗ tailwind.css
┣ .gitignore
┣ README.md
┣ next-env.d.ts
┣ next.config.js
┣ package-lock.json
┣ package.json
┣ postcss.config.js
┣ tailwind.config.js
┗ tsconfig.json

https://www.youtube.com/watch?v=4cpqSOQKSo8

このスタータープロジェクトは、TypeScriptとTailwindCSSをインストールしたNext.jsのアプリです。

Next.jsはフルスタックのReactフレームワークで、さまざまなデータ取得ストラテジーをサポートしています。ひとつはサーバーサイドレンダリングで、リクエストごとにデータをフェッチします。また、ビルド時にデータをフェッチして、CDNで提供できる静的なウェブサイトを用意することもできます。このアプリでは、サーバーサイドでデータを取得することになります。

Next.jsではファイルベースのルーティングを使用しており、pagesディレクトリ内の各ファイルがルートとなります。現在、http://localhost:3000 にインデックスページがあり、http://localhost:3000/about にアバウトページがあります。

_app.tsxファイルは、デフォルトのAppの動作をオーバーライドするために使用されます。このファイルでは、ページ変更時のレイアウトを保持したり、グローバルCSSを追加したりすることができます。

// pages/_app.tsx

import '../styles/tailwind.css' // import Tailwind globally
import { Layout } from '../components/Layout' // header layout persists between page changes
function MyApp({ Component, pageProps }) {
  return (
    <Layout>
      <Component {...pageProps} />
    </Layout>
  )
}
export default MyApp

http://localhost:3000 にナビゲートするときに見るデータは、/data/links.ts ファイルにハードコードされています。今後のパートでは、データはGraphQL APIを使用してデータベースから動的に取得される予定です。

アプリのデータモデルを作成する

データベースは以下のエンティティを持ち、各エンティティはデータベースのテーブルにマッピングされます。

  • User: アカウントを持つ人。お気に入りのリンクをブックマークすることができ、管理者にも一般ユーザーにもなることができます。

  • Link: タイトル、説明、URLなど、リンクのさまざまな属性を表現する。

  • UserとLinkのエンティティの間には、多対多(m-n)の関係があります。このようにして、ユーザーは多くのリンクを持つことができ、リンクは多くのユーザーを持つことができます。

プロジェクトにPrismaを追加する

データベースのテーブルを作成するためにPrismaを使用することにします。これは、データベースと対話するために使用できるORMです。

https://www.youtube.com/watch?v=dxnFH0qvNkU

はじめに、PrismaのCLIを開発依存でインストールします。

npm install prisma -D

これで、Prisma CLIを使用して、Prismaの基本的な設定を実行することで作成できます。

npx prisma init

新しい/prismaディレクトリが作成され、その中にschema.prismaファイルが作成されます。これはメインのPrisma設定ファイルで、データベーススキーマを格納します。.env (dotenv) ファイルもプロジェクトのルートに追加されます。ここで、データベース接続URLやアクセストークンなどの環境変数を定義します。

.envファイルを開き、ダミーの接続URLをPostgreSQLデータベースの接続URLで置き換えます。例えば、データベースがHerokuでホストされている場合、URLは以下のようになります。

// .env
DATABASE_URL="postgresql://giwuzwpdnrgtzv:d003c6a604bb400ea955c3abd8c16cc98f2d909283c322ebd8e9164b33ccdb75@ec2-54-170-123-247.eu-west-1.compute.amazonaws.com:5432/d6ajekcigbuca9"

先ほど追加したデータベースURLは、以下のような構造になっています。

NAME PLACEHOLDER DESCRIPTION
Host HOST IP address/domain of your database server, e.g. localhost
Port PORT Port on which your database server is running, e.g. 5432
User USER Name of your database user, e.g. janedoe
Password PASSWORD Password for your database user
Database DATABASE Name of the database you want to use, e.g. mydb

Prismaでデータベーススキーマを作成する

/prisma/schema.prisma ファイルを開くと、次のようなスキーマが表示されます。

// prisma/schema.prisma

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

generator client {
  provider = "prisma-client-js"
}

datasourceフィールドでは、PostgreSQLを使用していることと、.envファイルからデータベースのURLを読み込むことを指定しました。

次に、generatorブロックでは、データモデルに基づいてPrisma Clientを生成することを指定しています。

Prisma Clientは自動生成されるタイプセーフのクエリビルダで、データベースの操作をどのように簡略化するか見ていきます。

モデルの定義

Userモデルを作成しましょう。

// code above unchanged
model User {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  email     String?  @unique
  image     String?
  role      Role     @default(USER)
}

enum Role {
  USER
  ADMIN
}

ここでは、いくつかのフィールドを持つUserモデルを定義しています。各フィールドは、名前の後に型とオプションのフィールド属性があります。

例えば、id フィールドは String 型で、@id フィールド属性を持ち、これがテーブルのプライマリーキーであることを指定します。default(uuid())属性はUUIDのデフォルト値を設定します。

デフォルトでは、すべてのフィールドが必須です。フィールドをオプションにするには、フィールドタイプの後に「?」をつけます

Role enum は、ユーザーが管理者であるか否かを示すために使用され、その後、User モデルで参照されます。

次に、Linkモデルを作成します。

// prisma/schema.prisma
// code above unchanged

model User {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  email     String?  @unique
  image     String?
  role      Role     @default(USER)
}

enum Role {
  USER
  ADMIN
}

model Link {
  id          String   @id @default(uuid())
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  title       String
  description String
  url         String
  imageUrl    String
  category    String
}

リレーションの定義

最後に、UserモデルとLinkモデルの間に多対多の関係を作り、ユーザーが多くのリンクを持つことができ、リンクが多くのユーザーを持つことができるようにする必要があります。これは、リレーションの両側で、リレーションフィールドをリストとして定義することによって行います。

Userモデルにbookmarksフィールドを追加し、タイプはLink[]とします。次に、Linkモデルにusersフィールドを追加し、型はUser[]とします。

// prisma/schema.prisma
// code above unchanged

model User {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  email     String?  @unique
  image     String?
  role      Role     @default(USER)
+ bookmarks Link[]
}

enum Role {
  USER
  ADMIN
}

model Link {
  id           String   @id @default(uuid())
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
  title        String
  description  String
  url          String
  imageUrl     String
  category     String
+ users        User[]
}

これは暗黙の多対多の関係であり、基礎となるデータベースに関係テーブルがある。このリレーションテーブルはPrismaによって管理される。

最終的なスキーマはこんな感じです。

// prisma/schema.prisma

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

generator client {
  provider = "prisma-client-js"
}

model User {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  email     String?  @unique
  image     String?
  role      Role     @default(USER)
  bookmarks Link[]
}

enum Role {
  USER
  ADMIN
}

model Link {
  id           String   @id @default(uuid())
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
  title        String
  description  String
  url          String
  imageUrl     String
  category     String
  users        User[]
}

マイグレーションと変更をデータベースにプッシュ

https://www.youtube.com/watch?v=PU12wLEUU2k

これらのテーブルをデータベースに作成するには、次のコマンドを実行します。

npx prisma db push

ターミナルに次のような出力が表示されるはずです。

Environment variables loaded from /Users/mahmoud/Desktop/awesome-links/.env
Prisma schema loaded from prisma/schema.prisma

🚀  Your database is now in sync with your schema. Done in 2.10s

データベースのシード

https://www.youtube.com/watch?v=tE2qSy4tNs4

現在、データベースは空っぽなので、データを入れたいと思います。まず最初に、データベースと対話するための型安全なクエリビルダーであるPrisma Clientをインストールする必要があります。

Prisma Clientをインストールするには、次のコマンドを実行します。

npm install @prisma/client

次に、/prisma/seed.tsというファイルを新規に作成します。このファイルによって、Prismaの統合されたシード機能を使用することができます。このファイルの中で、Prisma Clientをインポートし、インスタンス化し、いくつかのレコードを作成します。

// prisma/seed.ts

import { PrismaClient } from '@prisma/client'
import { data } from '../data/links'
const prisma = new PrismaClient()

async function main() {
  await prisma.user.create({
    data: {
      email: `testemail@gmail.com`,
      role: 'ADMIN',
    },
  })

  await prisma.link.createMany({
    data: data,
  })
}

main()
  .catch(e => {
    console.error(e)
    process.exit(1)
  })
  .finally(async () => {
    await prisma.$disconnect()
  })

まず、create()関数を使用してユーザーを作成し、新しいデータベースレコードを作成します。

次に、createMany() 関数を使用して複数のレコードを作成しています。パラメータとして、ハードコードされたデータを渡しています。

デフォルトでは、Next.jsはESNextモジュールを強制的に使用します。この動作をオーバーライドしないと、シードスクリプトを実行することができません。これを行うには、まずts-nodeを開発依存としてインストールします。

npm install ts-node -D

これで、以下のコマンドを実行して、データベースをシードすることができます。

npx prisma db seed 

すべてが正しく動作していれば、次のような出力が表示されるはずです。

Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Running seed: ts-node --compiler-options '{"module":"CommonJS"}' "prisma/seed.ts" ...

🌱  Your database has been seeded.

Prisma Studioを使用してデータベースを調査する

https://www.youtube.com/watch?v=KbZKAP7nweM

Prismaには、データを探索・操作するためのGUIであるPrisma Studioが付属しています。Prisma Studioを使用すると、データベースのデータを表示、作成、更新、削除できます。

Prisma Studioを起動するには、次のコマンドを実行します。

npx prisma studio

すべての手順が正しく行われたなら、データベース内にLinkモデルとUserモデルができているはずです。Linkモデルには4つのレコードがあり、Userモデルには1つのレコードがあります。

まとめと次のステップ

この記事では、問題領域の説明と、Prismaを使用したデータのモデリングを行いました。また、データベースのシードを作成し、Prisma Studioを使用してそれを調査しました。これで、PostgreSQLデータベースに接続されたNext.jsアプリができました。

次のパートでは、次のことを学びます。

  • GraphQLと、APIを構築する際にRESTよりも優れている点
  • ApolloサーバーとNexusを使用したアプリのためのGraphQL APIの構築。
  • Apollo Clientを使用したクライアントでのAPIの使用。
  • GraphQLのページネーションにより、すべてのリンクを一度に読み込まず、より良いパフォーマンスを実現します。
GitHubで編集を提案

Discussion

blueplanetblueplanet

翻訳ありがとうございます。

多分元記事が更新されたと思いますが、下記2つの設定が追加されました。
これがないと、seed のコマンド実行がエラーになっていました。

# package.json

  "prisma": {
    "seed": "ts-node --transpile-only ./prisma/seed.ts"
  }

# tsconfig.json
  "ts-node": {
    "compilerOptions": {
      "module": "CommonJS"
    }
  }