Astro กับ Prisma: Database ORM สำหรับ Astro SSR
#astro13 เม.ย. 2569
Astro กับ Prisma: Database ORM สำหรับ Astro SSR
Prisma เป็น next-generation ORM สำหรับ Node.js และ TypeScript ที่ช่วยให้การทำงานกับ database เป็นเรื่องง่ายและ type-safe เมื่อใช้ร่วมกับ Astro SSR mode คุณสามารถสร้าง full-stack web application ได้อย่างมีประสิทธิภาพ
ทำไมต้องใช้ Prisma?
- Type Safety: Auto-generated TypeScript types จาก schema
- Migrations: จัดการ database schema changes อย่างเป็นระบบ
- Prisma Studio: GUI สำหรับดูและแก้ไขข้อมูล
- Multi-database: รองรับ PostgreSQL, MySQL, SQLite, MongoDB และอื่นๆ
- Query Builder: API ที่ intuitive และ readable
การตั้งค่า Astro SSR
ก่อนอื่น เปิดใช้งาน SSR ใน Astro:
npx astro add node
แก้ไข astro.config.mjs:
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone',
}),
});
การติดตั้ง Prisma
npm install prisma @prisma/client
npx prisma init
คำสั่งนี้จะสร้าง:
prisma/schema.prisma- Prisma schema file.env- Environment variables
กำหนด Schema
แก้ไข prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
tags Tag[]
viewCount Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Tag {
id String @id @default(cuid())
name String @unique
posts Post[]
}
enum Role {
USER
ADMIN
EDITOR
}
สร้าง Prisma Client Singleton
// src/lib/prisma.ts
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development'
? ['query', 'error', 'warn']
: ['error'],
});
if (process.env.NODE_ENV !== 'production') {
globalForPrisma.prisma = prisma;
}
API Routes ด้วย Prisma
สร้าง API endpoint สำหรับ posts:
// src/pages/api/posts/index.ts
import type { APIRoute } from 'astro';
import { prisma } from '../../../lib/prisma';
export const GET: APIRoute = async ({ url }) => {
const page = parseInt(url.searchParams.get('page') ?? '1');
const limit = parseInt(url.searchParams.get('limit') ?? '10');
const skip = (page - 1) * limit;
const [posts, total] = await Promise.all([
prisma.post.findMany({
where: { published: true },
include: {
author: { select: { id: true, name: true } },
tags: true,
},
orderBy: { createdAt: 'desc' },
skip,
take: limit,
}),
prisma.post.count({ where: { published: true } }),
]);
return new Response(
JSON.stringify({
posts,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
},
}),
{ headers: { 'Content-Type': 'application/json' } }
);
};
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
const { title, content, authorId, tags } = body;
const post = await prisma.post.create({
data: {
title,
content,
authorId,
tags: {
connectOrCreate: tags.map((tag: string) => ({
where: { name: tag },
create: { name: tag },
})),
},
},
include: { author: true, tags: true },
});
return new Response(JSON.stringify(post), {
status: 201,
headers: { 'Content-Type': 'application/json' },
});
};
Dynamic Page ด้วย Prisma
---
// src/pages/posts/[id].astro
import { prisma } from '../../lib/prisma';
import BaseLayout from '../../layouts/BaseLayout.astro';
const { id } = Astro.params;
if (!id) {
return Astro.redirect('/404');
}
const post = await prisma.post.findUnique({
where: { id },
include: {
author: true,
tags: true,
},
});
if (!post || !post.published) {
return Astro.redirect('/404');
}
// เพิ่ม view count
await prisma.post.update({
where: { id },
data: { viewCount: { increment: 1 } },
});
---
<BaseLayout title={post.title}>
<article>
<h1>{post.title}</h1>
<p>โดย {post.author.name}</p>
<div class="tags">
{post.tags.map(tag => <span class="tag">{tag.name}</span>)}
</div>
<div set:html={post.content} />
<p>ยอดเข้าชม: {post.viewCount}</p>
</article>
</BaseLayout>
Prisma Migrations
# สร้าง migration
npx prisma migrate dev --name add_post_table
# Apply migrations ใน production
npx prisma migrate deploy
# Reset database (development only)
npx prisma migrate reset
# เปิด Prisma Studio
npx prisma studio
Seed Data
สร้าง prisma/seed.ts:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
role: 'ADMIN',
},
});
await prisma.post.createMany({
data: [
{
title: 'บทความแรก',
content: 'เนื้อหาบทความแรก',
published: true,
authorId: admin.id,
},
{
title: 'บทความที่สอง',
content: 'เนื้อหาบทความที่สอง',
published: true,
authorId: admin.id,
},
],
});
console.log('Seed completed!');
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());
เพิ่มใน package.json:
{
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
สรุป
Prisma กับ Astro SSR เป็นคู่ที่ทรงพลังสำหรับการสร้าง full-stack applications ด้วย type-safe database queries, automatic migrations, และ Prisma Studio ทำให้การพัฒนาและ maintain database เป็นเรื่องง่ายและมีประสิทธิภาพ