LoginSignup
45
25

More than 1 year has passed since last update.

Next.js × Prisma を CRUD アプリケーションでざっくり理解する。

Last updated at Posted at 2021-01-10

何について書いた記事か?

  • Next.js × Prisma を利用して CRUD アプリケーションを作成した ので、そのまとめ記事です.
    • :beginner: Next.js は React でアプリケーションを作成するためのフレームワークです.
    • :beginner: Prisma は Node.js のための ORM です.
  • 各レイヤーの役割とソースコードを対応付けながら説明することで、Next.js × Prisma で作るとどんな実装になるのか?』 をざっくりと理解することを目指します.
  • 開発を始める前にこの記事で概要を掴む 的な使い方をしてくれると嬉しいです :smile:

:bulb: こんな人にオススメ

  • Next.js / Prisma に興味を持っていて、まずはこれらで実現できることを知りたい.
  • React / Vue.js を書いていて、バックエンドも JS (TS) で書けたらいいなと思っている.

:warning: 注意点

  1. サンプルアプリケーション開発のハンズオン的な説明は行っていません.
  1. 自分は React, TypeScript の経験が浅いので、慣例 / ベストプラクティスに沿っていないソースがある と思います :rolling_eyes:
  • 逆に言えば、慣れていなくても楽しく、ストレスなく開発できました!
  • 『ここはこう書いた方がいいよ!』といったご指摘があれば、コメントで教えていただけると嬉しいです :bow:

何を作ったか?

シンプルに CRUD できるだけのアプリケーションです.
:warning: 2021/3/11 追記: 公開からしばらく経ったので DB 停止しました.

next-prisma-crud-demo.gif

  • 題材は何でもよかったので、スターバックスのカスタマイズドリンクを CRUD する前提にしています.

chanmio_special.jpg

Next.js × Prisma で作ったアプリケーションの構成

新しい技術を学ぶときの難しさの一つに 『色んな登場人物が出てきて関係性がわからない :innocent: ということがあると思っているので、最初に アプリケーションの主要な構成 を説明しておきます.

ただ、この説明だけでそれぞれについて深く理解することは難しいと思うので、まずは 『なるほど、こういうものがあるのね :thinking: と把握してもらえれば OK です.

:star: WEB ページにアクセスされてから DB 操作までのフロー

next-prisma-app-structure.png

  • 図の :arrow_up: が開発環境 / :arrow_down: が本番環境です.
    • 本番での差分にはオレンジの枠線を付けています.
  • フロントエンドは React による実装で、UI コンポーネントとして chakra-ui を利用しました.
  • バックエンドを Next.js の API Routes で実装しています.
    • これにより、フロントエンド / バックエンドがどちらも Next.js 上で実行される ことになります.
  • フロントエンド ⇄ バックエンドの通信は axios で行います.
  • バックエンド ⇄ DB の通信は Prisma で行います.

CRUD ごとにソースコードを理解する

前置きが長くなってしまいましたが、ようやく Next.js / Prisma のコードに関する説明です.
まずは CRAETE を例に、 React コンポーネント ⇄ API Routes ⇄ Prisma の関連図とソースコード を見ていきます.
READ / UPDATE / DELETE については、Prisma のコード例だけ紹介しておきます.

:warning: Prisma の基本的な書き方を集中するため、エラーハンドリングは省略しています.

:white_check_mark: CREATE

:star: CREATE するまでのフロー

next-prisma-create.png

:star: CRAETE を行う React コンポーネント

pages/create.tsx
export default function Create() {
  ...

  const createBeverage = async (values: BeverageFormData, actions) => {
    // Formik からフォームに入力された値を取得
    const body = {
      name: values.name,
      description: values.description,
      price: Number(values.price),
      isRecommend: values.isRecommend,
    };

    // axios で API Routes として定義された URL に通信を飛ばす
    await axios.post("/api/beverages", body);

    // 登録後のロジック
    ...
  };

:star: CRAETE を行う API Routes ( Prisma Client を利用 )

pages/api/beverages.ts
const handleCreate = async (
  req: NextApiRequest,
  res: NextApiResponse<Beverage>
) => {
  // request からフォームの値を取得
  const { name, description, price, isRecommend } = req.body;

  // 登録前のチェック
  ...

  // prisma - CREATE
  // Prisma Client を呼び出して、DB にデータを登録する.
  const beverage = await prisma.beverage.create({
    data: { name, description, price, isRecommend },
  });
  // 登録結果を JSON で返却する
  res.json(beverage);
};

:white_check_mark: READ

:star: READ を行う API Routes ( Prisma Client を利用 )

pages/api/beverages.ts
const handleRead = async (
  req: NextApiRequest,
  res: NextApiResponse<Beverage[]>
) => {
  // prisma - READ
  // テーブル内の全データを id 昇順に取得
  const beverages = await prisma.beverage.findMany({
    orderBy: {
      id: "asc",
    },
    // 取得データを絞り込む場合はココに `where` などを追加していく
    // https://www.prisma.io/docs/concepts/components/prisma-client/crud
  });
  res.json(beverages);
};

:white_check_mark: UPDATE

:warning: UPDATE / DELETE では、どのデータに対して操作するのかを特定するために、ファイル名を [id].ts として URL から情報を受け取っています.
ここでは、Next.js - Dynamic API Routes という仕組みを利用しています.

:star: UPDATE を行う API Routes ( Prisma Client を利用 )

pages/api/beverage/[id].ts
const handleUpdate = async (
  req: NextApiRequest,
  res: NextApiResponse<Beverage>
) => {
  // URL から id を取得
  const url = req.url;
  const updateID = parseInt(url.split(/\//, 10).pop());
  // request からフォームの値を取得
  const { name, description, price, isRecommend } = req.body;

  // 更新前のチェック

  // prisma - UPDATE
  // id が一致したデータに対して、フォームに入力された値で更新
  const beverage = await prisma.beverage.update({
    where: { id: updateID },
    data: { name, description, price, isRecommend },
  });
  res.json(beverage);
};

:white_check_mark: DELETE

:star: DELETE を行う API Routes ( Prisma Client を利用 )

pages/api/beverage/[id].ts
const handleDelete = async (
  req: NextApiRequest,
  res: NextApiResponse<Beverage>
) => {
  // URL から id を取得
  const url = req.url;
  const deleteID = parseInt(url.split(/\//, 10).pop());

  // prisma - DELETE
  // id が一致したデータを削除
  const beverage = await prisma.beverage.delete({
    where: {
      id: deleteID,
    },
  });
  res.json(beverage);
};

見ていただいた通り、Prisma を利用すると DB 操作が簡単に書けていい ですね!
また、スキーマ定義を元に型定義が生成されるので、タイプセーフに書ける というのも大きなメリットです.


Next.js × Prisma で開発するメリットは何か?

実際に開発して感じた Next.js × Prisma のメリットについてまとめておきます.

メリット :one: : JS (TS) だけで完結するため、キャッチアップコストを抑えられる

  • 当然ですが、フロントエンド / バックエンドが同一言語なので開発のキャッチアップコストを抑えることができます.
  • また、今回開発した構成だと 従来の REST API と構成が近いため、そういった意味でも理解しやすい のではと思っています.
  • React / Vue.js などのモダンフロントエンドに足を突っ込むとそれだけで結構なキャッチアップコストを支払うことになるので、『そこで学んだ技術でバックエンドも書きたい』というのは自然な発想で、それが実現できるという意味で 開発体験は非常によかった です.

メリット :two: : 同一言語 / 同一ディレクトリのため共通化がしやすい

  • 全てが JS で書かれることで、これまでフロントエンド / バックエンドで別々に管理されていた仕組みを簡単に共通化できるようになります.
  • 例えば、今回のアプリケーションではバリデーション定義を共通化したので、以下に実装例を載せておきます.

:white_check_mark: バリデーション定義の共通化

  • BeverageFormSchema.ts という Yup スキーマを定義して、それをフロントエンド / バックエンドそれぞれから import することで簡単にバリデーション定義を共通化することができました :smiley:
validators/BeverageFormSchema.ts
// Yup で入力チェックのためのスキーマを定義する
export const beverageFormSchema = Yup.object({
  name: Yup.string().required(),
  description: Yup.string(),
  price: Yup.number().min(0).max(3000),
  isRecomment: Yup.boolean(),
});

:star: Frontend

components/BeverageForm.tsx
// 1. スキーマをインポートして...
import { beverageFormSchema } from "../validators/BeverageFormSchema";

...

export default function BeverageForm({ ... }) {
  return (
    <Formik
      initialValues={initialValues}
      onSubmit={onSubmit}
      // 2. Formik に props として渡す
      validationSchema={beverageFormSchema}
    >

:star: Backend

pages/api/beverages.ts
// 1. スキーマをインポートして...
import { beverageFormSchema } from "../../validators/BeverageFormSchema";

...

const handleCreate = async ( ... ) => {
  const { name, description, price, isRecommend } = req.body;

  // 2. Yup の API を利用してバリデーションチェック
  const isValid = await beverageFormSchema.isValid({
    name,
    description,
    price,
    isRecommend,
  });
  if (!isValid) {
    res.status(400).end("sent param is invalid.");
    return;
  }

メリット :three: : サーバー管理が楽になる

  • yarn dev を実行して Next.js の開発用サーバーを立てれば SSR / ISR や API Routes もそこで実行されるため、フロントエンド / バックエンドで別々の開発用サーバーを管理しなくてよくなります.
  • 個人開発や小規模なアプリにとって、このコンパクトさは大きな魅力 だと思いました.

おわりに

いかがだったでしょうか.
Next.js × Prisma で開発をしてみて感じた良さが少しでも伝わっていれば嬉しいです.

この記事で『興味を持ったよ』という方は、是非以下のチュートリアルなどを参考に実際に開発をしてみることをオススメします.
結局、手を動かしてみるのが一番理解できると思うので!

以上、長い記事を読んでいただいてありがとうございました :bow:

開発に役立つチュートリアル集

  • Next.js - Create a Next.js App
    • Next.js の公式チュートリアルです.
    • Next.js の各機能についてハンズオン形式で解説されていて、図も豊富で非常に分かりやすかった です.
  • Prisma - Quickstart
    • Prisma を TS と SQLite で試すチュートリアルです.
    • Next.js のようなフレームワークと分離して Prisma 単体から理解を始めたい という方にオススメです.
  • Prisma - How to Build a Fullstack App with Next.js, Prisma, and PostgreSQL
    • つい最近 Prisma の公式ページに追加されていた記事なので自分はこれには沿っていないのですが、上で説明したものと同じ技術要素でのチュートリアルです.
    • プロジェクトの作成からデプロイまで丁寧に解説されていた ので、これに沿っていけば一通りの開発は順調に進められそうでした.
45
25
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
25