💨

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

2024/03/24に公開

目的

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