SeaORM with seekdb
This topic describes how to build an application with SeaORM and seekdb to perform basic operations such as creating tables, inserting data, and querying data.
Environment preparation
Before you start, make sure that you have installed the following tools:
- Rust 1.60 or later
- Cargo (Rust's package manager)
- seekdb
Create a new project
cargo new sea-orm-seekdb-demo
cd sea-orm-seekdb-demo
Add dependencies
Edit the Cargo.toml file and add the following dependencies:
[package]
name = "sea-orm-seekdb-demo"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.0", features = ["full"] }
sea-orm = { version = "0.12", features = [
"sqlx-mysql",
"runtime-tokio-rustls",
"macros",
"with-json"
] }
sea-orm-migration = "0.12"
serde = { version = "1.0", features = ["derive"] }
dotenv = "0.15"
async-std = { version = "1.12", features = ["attributes"] }
chrono = "0.4"
Configure the database connection
Create a .env file in the project root directory:
DATABASE_URL=mysql://username:password@localhost:2881/database_name
Define entities
Create a src/entities directory and add mod.rs and user.rs files:
// src/entities/mod.rs
pub mod user;
// src/entities/user.rs
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "users")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
pub email: String,
pub created_at: DateTimeUtc,
pub updated_at: DateTimeUtc,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}
Database connection
Create a src/db.rs file:
use sea_orm::*;
use std::env;
use dotenv::dotenv;
pub async fn establish_connection() -> Result<DatabaseConnection, DbErr> {
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
Database::connect(&database_url).await
}
Implement CRUD operations
Create a src/user_service.rs file:
use crate::entities::user;
use sea_orm::*;
use chrono::Utc;
pub struct UserService;
impl UserService {
// Create a user
pub async fn create_user(
db: &DatabaseConnection,
name: String,
email: String,
) -> Result<user::Model, DbErr> {
let new_user = user::ActiveModel {
name: Set(name),
email: Set(email),
created_at: Set(Utc::now()),
updated_at: Set(Utc::now()),
..Default::default()
};
new_user.insert(db).await
}
// Get a single user
pub async fn get_user(
db: &DatabaseConnection,
id: i32,
) -> Result<Option<user::Model>, DbErr> {
user::Entity::find_by_id(id).one(db).await
}
// Update a user
pub async fn update_user(
db: &DatabaseConnection,
id: i32,
name: Option<String>,
email: Option<String>,
) -> Result<user::Model, DbErr> {
let mut user: user::ActiveModel = user::Entity::find_by_id(id)
.one(db)
.await?
.ok_or_else(|| DbErr::Custom("User not found".to_owned()))?
.into();
if let Some(name) = name {
user.name = Set(name);
}
if let Some(email) = email {
user.email = Set(email);
}
user.updated_at = Set(Utc::now());
user.update(db).await
}
// Delete a user
pub async fn delete_user(
db: &DatabaseConnection,
id: i32,
) -> Result<DeleteResult, DbErr> {
let user: user::ActiveModel = user::Entity::find_by_id(id)
.one(db)
.await?
.ok_or_else(|| DbErr::Custom("User not found".to_owned()))?
.into();
user.delete(db).await
}
// Get all users
pub async fn get_all_users(
db: &DatabaseConnection,
) -> Result<Vec<user::Model>, DbErr> {
user::Entity::find().all(db).await
}
}
Main program
Modify src/main.rs:
mod entities;
mod db;
mod user_service;
use sea_orm::{DatabaseConnection, DbErr, Statement, ConnectionTrait};
use user_service::UserService;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Establish a database connection
println!("Connecting to database...");
let db = db::establish_connection().await?;
println!("Database connected successfully!");
// Create tables if they do not exist
println!("Creating tables if not exist...");
create_tables(&db).await?;
println!("Tables created successfully!");
// Create users
println!("Inserting sample data...");
let users_to_create = vec![
("Alice", "alice@example.com"),
("Bob", "bob@example.com"),
("Charlie", "charlie@example.com"),
];
for (name, email) in users_to_create {
println!("Creating user: {} <{}>", name, email);
match UserService::create_user(
&db,
name.to_string(),
email.to_string()
).await {
Ok(user) => println!("Created user: {} <{}>", user.name, user.email),
Err(e) => eprintln!("Error creating user: {}", e),
}
}
// Get all users and print them
println!("\nCurrent users in database:");
match UserService::get_all_users(&db).await {
Ok(users) => {
for user in users {
println!("ID: {}, Name: {}, Email: {}, Created: {}, Updated: {}",
user.id, user.name, user.email, user.created_at, user.updated_at);
}
},
Err(e) => eprintln!("Error fetching users: {}", e),
}
Ok(())
}
async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> {
let stmt = r#"
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
"#;
db.execute(Statement::from_string(
db.get_database_backend(),
stmt.to_owned(),
))
.await?;
Ok(())
}
Run the program
cargo run
The expected output is as follows:
Connecting to database...
Database connected successfully!
Creating tables if not exist...
Tables created successfully!
Inserting sample data...
Creating user: Alice <alice@example.com>
Creating user: Bob <bob@example.com>
Creating user: Charlie <charlie@example.com>
Current users in database:
ID: 1, Name: Alice, Email: alice@example.com, Created: 2025-05-27 09:47:01 UTC, Updated: 2025-05-27 09:47:01 UTC
ID: 2, Name: Bob, Email: bob@example.com, Created: 2025-05-27 09:47:01 UTC, Updated: 2025-05-27 09:47:01 UTC
ID: 3, Name: Charlie, Email: charlie@example.com, Created: 2025-05-27 09:47:01 UTC, Updated: 2025-05-27 09:47:01 UTC