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 เป็นเรื่องง่ายและมีประสิทธิภาพ