💨

Remix + Prisma でデータ表示・登録・削除機能を実装する

に公開

目的

Remix を使用したフルスタックアプリケーションを開発します。
この記事では、DB から取得したデータの一覧表示、登録、削除機能を実装します。

環境

環境 バージョン
Node.js 20.11.1
Yarn 4.1.1
Prisma CLI 5.11.0
Prisma Client 5.11.0

データ取得用関数

概要
DB からデータ取得・登録・削除機能を実装にあたり、 DB アクセス用の Class を作成します。

作業
ファイルを作成します。

mkdir ./app/models/
touch ./app/models/post.server.ts

post.server.ts に以下の通り上書きします。

import { Post } from '.prisma/client';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

class PostRepository {
  async create(params: Pick<Post, 'title' | 'content'>) {
    const { title, content } = params;
    if (!title || !content) throw new Error('Title and content are required');

    await prisma.post.create({
      data: {
        title: title,
        content: content,
      },
    });
    return;
  }

  async find(params: { id: Post['id'] }) {
    return await prisma.post.findUnique({ where: { id: params.id } });
  }

  async findAll(): Promise<Post[]> {
    return await prisma.post.findMany();
  }

  async delete(params: { id: Post['id'] }) {
    await prisma.post.delete({
      where: { id: params.id },
    });
    return;
  }
}

const postRepository = new PostRepository();

export { postRepository };

上記コードは、以下の処理を担当します。

メソッド 処理
create title と content をパラメータを使用して、ポストを DB に登録する
find 既に登録済みポストの id をパラメータに使用して、DB からデータを取得する
findAll 既に登録済みポストを DB から全て取得する
delete 既に登録済みポストの id をパラメータに使用して、DB からデータを削除する

ポスト一覧画面

app/routes/_index.tsx ファイルを以下の通り作成します。

import type { MetaFunction } from '@remix-run/node';
import { Link } from '@remix-run/react';

export const meta: MetaFunction = () => {
  return [
    { title: 'Remix fullstack blog' },
    { name: 'description', content: 'Remix fullstack blog' },
  ];
};

export default function Index() {
  return (
    <div>
      <Link to='/posts'>ポスト一覧</Link>
    </div>
  );
}

app/routes/posts._index.tsx ファイルを以下の通り作成します。
作成するリンクとタイトルのリンクは、後述のセクションで使用します。

import { Post } from '@prisma/client';
import { json } from '@remix-run/node';
import { Link, useLoaderData } from '@remix-run/react';

import { postRepository } from '~/models/post.server';

export const loader = async () => {
  const posts = await postRepository.findAll();

  return json(posts);
};

export default function PostIndex() {
  const posts: Post[] = useLoaderData();
  return (
    <div>
      <h1>ポスト</h1>
      <Link to='new'>ポストを作成する</Link>
      <ul>
        {posts.map((post: Post) => (
          <li key={post.id}>
            <Link to={post.id}>{post.title}</Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Remix を起動して以下の通り表示されると作業完了です。

ポスト登録画面

app/routes/posts.new.tsx ファイルを以下の通り作成します。

import { redirect } from '@remix-run/node';
import type { ActionFunctionArgs } from '@remix-run/node';
import { postRepository } from '~/models/post.server';

export const action = async ({ request }: ActionFunctionArgs) => {
  const form = await request.formData();

  const title = form.get('title');
  const content = form.get('content');
  if (typeof title !== 'string' || typeof content !== 'string') {
    throw new Error('Title and content are required');
  }

  await postRepository.create({ title: title, content: content });

  return redirect('/posts');
};

export default function PostNew() {
  return (
    <div>
      <h1>ポスト新規作成</h1>
      <form method='post'>
        <input
          style={{ display: 'block' }}
          name='title'
          placeholder='title'
          type='text'
        />
        <input
          style={{ display: 'block' }}
          name='content'
          placeholder='content'
          type='text'
        />
        <button style={{ display: 'block' }} type='submit'>
          作成する
        </button>
      </form>
    </div>
  );
}

Remix を起動すると、以下の通り表示されます。

title と content を入力して保存すると、一覧画面に遷移してポストが追加されると作業完了です。

ポスト詳細画面

app/routes/posts.$postId.tsx ファイルを以下の通り作成します。
削除ボタンの機能は後述のセクションで実装します。

import { Post } from '@prisma/client';
import { LoaderFunctionArgs, json } from '@remix-run/node';
import { Form, Link, useLoaderData } from '@remix-run/react';

import { postRepository } from '~/models/post.server';

export const loader = async ({ params }: LoaderFunctionArgs) => {
  if (!params.postId)
    throw new Response(null, {
      status: 404,
      statusText: 'Not Found',
    });

  const post = await postRepository.find({ id: params.postId });

  return json(post);
};

export default function PostShow() {
  const post: Post = useLoaderData();
  return (
    <div>
      <Link to='/posts'>一覧に戻る</Link>
      <h1>ポスト</h1>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
      <Form action='delete' method='post'>
        <button>削除</button>
      </Form>
    </div>
  );
}

Remix を起動すると、以下の通り表示されると作業完了です。

ポスト削除機能

app/routes/posts.$postId.delete.tsx ファイルを以下の通り作成します。

import { redirect, type ActionFunctionArgs } from '@remix-run/node';

import { postRepository } from '~/models/post.server';

export const action = async ({ params }: ActionFunctionArgs) => {
  if (!params.postId) throw new Error('Parameter is missing');

  await postRepository.delete({ id: params.postId });

  return redirect('/posts');
};

削除ボタンを押下すると、一覧画面に遷移し、対象のポストが削除されると作業完了です。

まとめ

実装コードは以下 PR の通りです。
https://github.com/masayuki-0319/remix_fullstack_blog/pull/4

参考 URL

Route Configuration | Remix

Discussion