Next.js (AppRouter,TypeScript) の環境で、OR マッパーPrismaを試してみました。
環境) Next.js v14.1.4, PostgreSQL v16, Prisma v5.11 / Mac (arm)
参考)
https://qiita.com/tomohiko_ohhashi/items/da804ed1f5870c9ce52d
https://www.sddgrp.co.jp/blog/technology/use-next-jsprisma/
コードは上記のものを使用させていただきました。(シンプルなToDoリストWEBアプリです。)
AppRouterで、 PostgreSQLを使用したかったこと、また実行されているのが、サーバサイドなのかクライアント(ブラウザ)なのか確認するため、これらの部分を変更しています。
npx create-next-app prisma-samp
cd prisma-samp
npm i prisma
npm i antd
npm i @prisma/client
npx prisma init
(schema.prisma編集)
npx prisma db push
DBマネージメントツール
npx prisma studio
.env
DATABASE_URL=”postgresql://postgres:postgres@localhost:5432/prisma?schema=public”
prisma/schema.prisma
generator client {
provider = “prisma-client-js”
}datasource db {
provider = “postgresql”
url = env(“DATABASE_URL”)
}model notes {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
}
page.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { PrismaClient } from '@prisma/client'; //import App from './app'; import dynamic from 'next/dynamic' const App = dynamic(()=>import('./app'),{ ssr: false, }) const prisma = new PrismaClient(); export default async function Home() { const data = await prisma.notes.findMany() console.log('data', data) return <div> <div>{data[0].content}</div> <App /> </div> } |
import Appの部分をコメントアウトしているのは、下記console.logでサーバサイドのコンソールに表示していたため。
use clientにしているにもかかわらず、サーバが実行したため、dynamic を使用した。
schemaファイルの記述とprismaコマンドの操作だけでPostgresの管理ができるのはとても便利です。
DBがすでに作成されている場合は、’prisma db pull’コマンドでschemeファイルを作成できます。
下記、その他コードの引用です。
app.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
'use client'; import { useState, useEffect, ChangeEvent } from 'react'; import { Card, Row, Col, Input, Button, Table } from 'antd'; import { ColumnsType } from 'antd/es/table'; interface DataType { key: string; id: number; content: string; createdAt: string; } export default function App() { const [content, setContent] = useState(''); const [dataSource, setDataSource] = useState<DataType[]>([]); console.log('win', typeof window !== "undefined") // 本当にクライアントで実行しているか? useEffect(() => { const fetchNotes = async () => { const response = await fetch('/api/notes'); const notes = await response.json(); setDataSource(notes); }; fetchNotes(); }, []); const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => { setContent(event.target.value); }; const handleSaveClick = async () => { const response = await fetch('/api/notes', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ content }), }); const notes = await response.json(); setDataSource(notes); setContent(''); }; const handleDeleteClick = async (id: number) => { const response = await fetch(`/api/notes?id=${id}`, { method: 'DELETE', }); const notes = await response.json(); setDataSource(notes); }; const columns: ColumnsType<DataType> = [ { title: 'created_at', dataIndex: 'createdAt', width: '20%', render: (date: Date) => new Date(date).toLocaleDateString(), sorter: (a, b) => Date.parse(a.createdAt) - Date.parse(b.createdAt), }, { title: 'content', dataIndex: 'content', width: '75%', }, { width: '5%', render: (record: DataType) => ( <Button danger onClick={() => handleDeleteClick(record.id)}> Delete </Button> ), }, ]; const centeredStyle: React.CSSProperties = { display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100vh', }; return ( <div style={centeredStyle}> <Card title='Note' style={{ width: 800 }}> <Row> <Col span={16}> <Input placeholder='content' value={content} onChange={handleInputChange} /> </Col> <Col span={7} offset={1}> <Button type='primary' onClick={handleSaveClick}> Save </Button> </Col> </Row> <Table dataSource={dataSource} columns={columns} rowKey={(record) => record.id} pagination={{ pageSize: 5, }} style={{ marginTop: 20 }} /> </Card> </div> ); } |
api/notes/route.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
import { NextRequest, NextResponse } from 'next/server'; import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); export async function GET() { const notes = await getAllNotes(); return NextResponse.json(notes); } export async function POST(request: NextRequest) { const { content } = await request.json(); await prisma.notes.create({ data: { content: content, }, }); const notes = await getAllNotes(); return NextResponse.json(notes); } export async function DELETE(request: NextRequest) { const id = parseInt(request.nextUrl.searchParams.get('id')!); await prisma.notes.delete({ where: { id: id, }, }); const notes = await getAllNotes(); return NextResponse.json(notes); } async function getAllNotes() { const notes = await prisma.notes.findMany(); return notes; } |