Guide to getting started with SeaORM: an ORM for Rust
Anshul Chauhan
Posted on July 16, 2022
SeaORM is a relational ORM to help you build web services in Rust with the
familiarity of dynamic languages.
The official docs of SeaORM are on their official website.
This guide assumes that you're using SQLite. Before we start, make sure you
have libsqlite3 installed for your system.
The entire code for this guide is in this repo.
In this guide, we're going to build simple examples of each piece of CRUD operations.
I am using Rust v1.62 and sea-orm
v0.9
Initialize a new project
cargo new seaorm_demo --lib
cd seaorm_demo
First, let's add SeaORM and tokio to our dependencies.
File: Cargo.toml
[dependencies]
tokio = { version = "1.20", features = ["macros", "rt-multi-thread"] }
[dependencies.sea-orm]
version = "0.9"
features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros" ]
default-features = false
Installing sea-orm-cli and Migration
cargo install sea-orm-cli
We'll write a migration file to setup our database and table schema.
sea-orm-cli migrate init
It will generate a module called migration
. Now our project structure should look like this.
.
├── Cargo.lock
├── Cargo.toml
├── migration
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── lib.rs
│ ├── m20220101_000001_create_table.rs
│ └── main.rs
└── src
└── lib.rs
Open the file migration/Cargo.toml
and uncomment the two dependencies for sea-orm-migration
.
File: migration/Cargo.toml
[dependencies.sea-orm-migration]
version = "^0.9.0"
features = [
# Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
# View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
# e.g.
"runtime-tokio-rustls", # `ASYNC_RUNTIME` featrure
"sqlx-sqlite", # `DATABASE_DRIVER` feature
]
Edit the file migration/src/m20220101_000001_create_table.rs
in your
favourite editor and remove the two mentions of todo!()
and save the file.
Set the URL for your database as an environment varibale.
export DATABASE_URL='sqlite://posts.sqlite?mode=rwc'
Next, we'll run the migration.
sea-orm-cli migrate up
It will compile the migration
module and run your migrations. After this, you
should see a file named posts.sqlite in your directory.
To confirm if we have created the table, run the following command.
$ sqlite3 posts.sqlite ".schema post" ".exit"
CREATE TABLE IF NOT EXISTS "post" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "title" text NOT NULL, "text" text NOT NULL );
Generate entities
Create a new entity
module.
cargo new entity --lib
Next, generate the entities.
sea-orm-cli generate entity -o entity/src
It will use the database we created earlier by reading the DATABASE_URL
environment variable.
Add the sea-orm
dependency to the entity
module.
File: entity/Cargo.toml
[dependencies]
sea-orm = { version = "0.9" }
The generated entity should look like the following.
File: entity/src/post.rs
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.0
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "post")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub title: String,
pub text: String,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}
impl ActiveModelBehavior for ActiveModel {}
Since the module entity
is at the root of our project, let’s convert it into
a library so that we can consume it.
Rename the file entity/src/mod.rs
to entity/src/lib.rs
.
mv entity/src/mod.rs entity/src/lib.rs
Next, we’ll add the entity
and migration
library to the dependencies of the
root project.
File: Cargo.toml
[workspace]
members = [".", "entity", "migration"]
[dependencies]
entity = { path = "entity" }
migration = { path = "migration" }
And now your project structure should look like
.
├── Cargo.lock
├── Cargo.toml
├── entity
│ ├── Cargo.toml
│ └── src
│ ├── lib.rs
│ ├── post.rs
│ └── prelude.rs
├── migration
│ ├── Cargo.lock
│ ├── Cargo.toml
│ ├── README.md
│ └── src
│ ├── lib.rs
│ ├── m20220101_000001_create_table.rs
│ └── main.rs
├── src
│ └── lib.rs
└── tasks.sqlite
And your entire Cargo.toml
should look like the following.
File: Cargo.tmol
[package]
name = "seaorm_demo"
version = "0.1.0"
edition = "2021"
[workspace]
members = [".", "entity", "migration"]
[dependencies]
entity = { path = "entity" }
migration = { path = "migration" }
tokio = { version = "1.20", features = ["macros", "rt-multi-thread"] }
[dependencies.sea-orm]
version = "0.9"
features = [ "sqlx-sqlite", "runtime-tokio-rustls", "macros" ]
default-features = false
Connect
Now we write code for establishing a connection to our database.
File: src/lib.rs
use migration::{DbErr, Migrator, MigratorTrait};
use sea_orm::{Database, DbConn};
pub async fn establish_connection() -> Result<DbConn, DbErr> {
let database_url = std::env::var("DATABASE_URL").unwrap();
let db = Database::connect(&database_url)
.await
.expect("Failed to setup the database");
Migrator::up(&db, None)
.await
.expect("Failed to run migrations for tests");
Ok(db)
}
Create
Now let's write some code to create posts. Create a new file src/bin/create_post.rs
.
File: src/bin/create_post.rs
use migration::DbErr;
use sea_orm::{Set, ActiveModelTrait};
use seaorm_demo::establish_connection;
use entity::post;
#[tokio::main]
async fn main() -> Result<(), DbErr>{
let db = establish_connection().await?;
let post = post::ActiveModel {
title: Set(String::from("Amazing title 1")),
text: Set(String::from("Lorem ipsum dolor sit amet.")),
..Default::default()
};
let post: post::Model = post.insert(&db).await?;
println!("Post created with ID: {}, TITLE: {}", post.id, post.title);
Ok(())
}
We can run our new script as follows.
cargo run --bin create_post
It should look like the following.
$ cargo run --bin create_post
Compiling seaorm_demo v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 3.85s
Running `target/debug/create_post`
Post created with ID: 1, TITLE: Amazing title 1
If you wish to create more entries in the database, change the title/text in
create_post.rs
and execute the script again.
I will create one more.
$ cargo run --bin create_post
Compiling seaorm_demo v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 4.08s
Running `target/debug/create_post`
Post created with ID: 2, TITLE: Another title 2
Read
Next, we write the example for reading all the posts in the database.
File: src/bin/read_posts.rs
use migration::DbErr;
use sea_orm::EntityTrait;
use seaorm_demo::establish_connection;
use entity::post;
#[tokio::main]
async fn main() -> Result<(), DbErr>{
let db = establish_connection().await?;
let posts: Vec<post::Model> = post::Entity::find().all(&db).await?;
println!("All the posts in db:");
for post in posts {
println!("ID: {}, TITLE: {}", post.id, post.title);
}
Ok(())
}
Just like earlier, you can run this new file as follows.
cargo run --bin read_posts
And this should look like the following.
$ cargo run --bin read_posts
Compiling seaorm_demo v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 4.08s
Running `target/debug/read_posts`
All the posts in db:
ID: 1, TITLE: Amazing title 1
ID: 2, TITLE: Another title 2
Update
Now, let’s say we wanted to perform an UPDATE operation to the title of a post.
File: src/bin/update_post.rs
use migration::DbErr;
use sea_orm::{EntityTrait, Set, ActiveModelTrait};
use seaorm_demo::establish_connection;
use entity::post;
#[tokio::main]
async fn main() -> Result<(), DbErr>{
let db = establish_connection().await?;
// UPDATE titel of Post by ID
let post = post::Entity::find_by_id(1).one(&db).await?;
let mut post: post::ActiveModel = post.unwrap().into();
post.title = Set("Updated title".to_owned());
let post: post::Model = post.update(&db).await?;
println!("Post updated for ID: {} with TITLE: {}", post.id, post.title);
Ok(())
}
And we run this script with
cargo run --bin update_post
And it should look like
$ cargo run --bin update_post ⏎
Compiling seaorm_demo v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 2.80s
Running `target/debug/update_post`
Post updated for ID: 1 with TITLE: Updated title
Delete
And now for the final operation, the deletion. Create a new file src/bin/delete_post.rs
We will be deleting the post with ID 1
File: src/bin/delete_post.rs
use migration::DbErr;
use sea_orm::{EntityTrait, DeleteResult, ModelTrait};
use seaorm_demo::establish_connection;
use entity::post;
#[tokio::main]
async fn main() -> Result<(), DbErr>{
let db = establish_connection().await?;
let post = post::Entity::find_by_id(1).one(&db).await?;
let post: post::Model = post.unwrap();
let res: DeleteResult = post.delete(&db).await?;
assert_eq!(res.rows_affected, 1);
println!("{:?}", res);
Ok(())
}
And we will call this script with
cargo run --bin delete_post
And it should look like
$ cargo run --bin delete_post
Compiling seaorm_demo v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 5.42s
Running `target/debug/delete_post`
DeleteResult { rows_affected: 1 }
We can execute script read_post
again to see what posts remain in the database.
$ cargo run --bin read_posts
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/read_posts`
All the posts in db:
ID: 2, TITLE: Another title 2
Further reading
- Official SeaORM tutorial
- SeaORM examples on their official repo
Etc
- Entire code can be found at github.com/anshulxyz/seaorm_demo
- This post has taken direct inspiration from Diesel-rs' getting-started guide.
- Originally posted at my personal website
- Feel free to contact me on twitter @anshulxyz
Posted on July 16, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.