Database ด้วย SQLx
Prisma/TypeORM vs SQLx
หัวข้อที่มีชื่อว่า “Prisma/TypeORM vs SQLx”ใน Node.js คุณอาจใช้ Prisma หรือ TypeORM — ORM ที่ generate query จาก schema หรือ model decorator ส่วน SQLx ใช้แนวทางที่ต่างออกไป: คุณเขียน raw SQL เอง แต่ compiler จะตรวจ query กับ database จริงตั้งแต่ compile time ผ่าน macro ตระกูล sqlx::query! ไม่มี ORM ไม่มี query builder — มีแค่ SQL ที่ type-checked
| ฟีเจอร์ | Prisma/TypeORM | SQLx |
|---|---|---|
| รูปแบบ Query | ORM API / query builder | Raw SQL |
| Type safety | Generated types | Compile-time macro verification |
| Migrations | Built-in CLI | sqlx migrate CLI |
| Connection pool | จัดการภายใน | PgPool (explicit) |
| Async runtime | Node event loop | Tokio |
ตั้งค่า pool
หัวข้อที่มีชื่อว่า “ตั้งค่า pool”[dependencies]sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-rustls", "uuid", "chrono"] }// Node — Prisma client setupimport { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();export default prisma;// Rust — SQLx PgPool setupuse sqlx::PgPool;use std::env;
pub async fn create_pool() -> PgPool { let url = env::var("DATABASE_URL") .expect("DATABASE_URL must be set"); PgPool::connect(&url) .await .expect("Failed to connect to database")}Queries ด้วย query_as!
หัวข้อที่มีชื่อว่า “Queries ด้วย query_as!”sqlx::query_as! map result row ของ SQL ไปยัง Rust struct โดยตรง ชื่อ column ต้องตรงกับชื่อ field ถ้า schema เปลี่ยนและ column หายไป code จะ ไม่สามารถ compile ได้
// Prismaconst users = await prisma.user.findMany({ where: { active: true }, orderBy: { createdAt: 'desc' },});
const user = await prisma.user.findUnique({ where: { id },});use sqlx::PgPool;use uuid::Uuid;
#[derive(sqlx::FromRow, serde::Serialize)]struct User { id: Uuid, name: String, email: String, active: bool,}
// List queryasync fn list_users(pool: &PgPool) -> sqlx::Result<Vec<User>> { sqlx::query_as!( User, "SELECT id, name, email, active FROM users WHERE active = true ORDER BY id" ) .fetch_all(pool) .await}
// Get oneasync fn find_user(pool: &PgPool, id: Uuid) -> sqlx::Result<Option<User>> { sqlx::query_as!( User, "SELECT id, name, email, active FROM users WHERE id = $1", id ) .fetch_optional(pool) .await}Insert และ delete
หัวข้อที่มีชื่อว่า “Insert และ delete”use sqlx::PgPool;use uuid::Uuid;
async fn create_user( pool: &PgPool, name: &str, email: &str,) -> sqlx::Result<User> { sqlx::query_as!( User, "INSERT INTO users (id, name, email, active) VALUES ($1, $2, $3, true) RETURNING id, name, email, active", Uuid::new_v4(), name, email ) .fetch_one(pool) .await}
async fn delete_user(pool: &PgPool, id: Uuid) -> sqlx::Result<bool> { let result = sqlx::query!( "DELETE FROM users WHERE id = $1", id ) .execute(pool) .await?;
Ok(result.rows_affected() > 0)}เชื่อม pool เข้ากับ Axum State
หัวข้อที่มีชื่อว่า “เชื่อม pool เข้ากับ Axum State”use axum::{extract::State, routing::get, Json, Router};use sqlx::PgPool;use std::sync::Arc;
#[derive(Clone)]struct AppState { db: PgPool }
async fn list_users_handler( State(state): State<Arc<AppState>>,) -> Result<Json<Vec<User>>, AppError> { let users = list_users(&state.db).await.map_err(AppError::Database)?; Ok(Json(users))}
#[tokio::main]async fn main() { let pool = create_pool().await; let state = Arc::new(AppState { db: pool });
let app = Router::new() .route("/users", get(list_users_handler)) .with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); axum::serve(listener, app).await.unwrap();}รันบนเครื่องของคุณ — SQLx ต้องการ PostgreSQL database จริงและ environment variable
DATABASE_URLรันsqlx migrate runเพื่อ apply migrations จากนั้นcargo run