ข้ามไปยังเนื้อหา

Database ด้วย 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/TypeORMSQLx
รูปแบบ QueryORM API / query builderRaw SQL
Type safetyGenerated typesCompile-time macro verification
MigrationsBuilt-in CLIsqlx migrate CLI
Connection poolจัดการภายในPgPool (explicit)
Async runtimeNode event loopTokio
Cargo.toml
[dependencies]
sqlx = { version = "0.7", features = ["postgres", "runtime-tokio-rustls", "uuid", "chrono"] }
TypeScript
// Node — Prisma client setup
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;
Rust
// Rust — SQLx PgPool setup
use 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")
}

sqlx::query_as! map result row ของ SQL ไปยัง Rust struct โดยตรง ชื่อ column ต้องตรงกับชื่อ field ถ้า schema เปลี่ยนและ column หายไป code จะ ไม่สามารถ compile ได้

TypeScript
// Prisma
const users = await prisma.user.findMany({
where: { active: true },
orderBy: { createdAt: 'desc' },
});
const user = await prisma.user.findUnique({
where: { id },
});
Rust
use sqlx::PgPool;
use uuid::Uuid;
#[derive(sqlx::FromRow, serde::Serialize)]
struct User {
id: Uuid,
name: String,
email: String,
active: bool,
}
// List query
async 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 one
async 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
}
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)
}
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

SQLx ตรวจสอบว่า SQL queries ถูกต้องเมื่อใด?
SQLx method ใดดึง rows ทั้งหมดเป็น `Vec<T>`?
`RETURNING id, name, email` ใน `INSERT` statement เปิดใช้อะไรใน SQLx?