Guide to getting started with SeaORM: an ORM for Rust
Tags: rust, programming, tutorial, database
Published: 2022-07-16 18:12:52


> 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](https://www.sea-ql.org/SeaORM/docs/index/).

**This guide assumes that you're using SQLite.** Before we start, make sure you
have [libsqlite3](https://www.sqlite.org/index.html) installed for your system.

The entire code for this guide is in this [repo](https://github.com/anshulxyz/seaorm_demo).

In this guide, we're going to build simple examples of each piece of CRUD operations.

I am using [Rust](https://www.rust-lang.org/) v1.62 and `sea-orm` v0.9

## Initialize a new project

```txt
cargo new seaorm_demo --lib
cd seaorm_demo
```

First, let's add SeaORM and tokio to our dependencies.

File: `Cargo.toml`


```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

```txt
cargo install sea-orm-cli
```

We'll write a migration file to setup our database and table schema.

```txt
sea-orm-cli migrate init
```

It will generate a module called `migration`. Now our project structure should look like this.

```txt
.
├── 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`


```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.

```txt
export DATABASE_URL='sqlite://posts.sqlite?mode=rwc'
```

Next, we'll run the migration.

```txt
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.

```txt
$ 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.

```txt
cargo new entity --lib
```

Next, generate the entities.

```txt
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`


```toml
[dependencies]
sea-orm = { version = "0.9" }
```

The generated entity should look like the following.

File: `entity/src/post.rs`


```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`


```toml
[workspace]
members = [".", "entity", "migration"]

[dependencies]
entity = { path = "entity" }
migration = { path = "migration" }
```

And now your project structure should look like

```txt
.
├── 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`


```toml
[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`


```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`


```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.

```txt
cargo run --bin create_post
```

It should look like the following.

```txt
$ 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.

```txt
$ 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`


```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.

```txt
cargo run --bin read_posts
```

And this should look like the following.

```txt
$ 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`


```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

```txt
cargo run --bin update_post
```

And it should look like

```txt
$ 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`


```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

```txt
cargo run --bin delete_post
```

And it should look like

```txt
$ 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.

```txt
$ 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](https://www.sea-ql.org/sea-orm-tutorial/)
- SeaORM examples on their official [repo](https://github.com/SeaQL/sea-orm/tree/master/examples)

## Etc

- Entire code can be found at [github.com/anshulxyz/seaorm_demo](https://github.com/anshulxyz/seaorm_demo)
- This post has taken direct inspiration from Diesel-rs' [getting-started](https://diesel.rs/guides/getting-started) guide.
- Originally posted at my [personal website](https://anshulchauhan.com/getting-started-seaorm)
- Feel free to contact me on twitter [@anshulxyz](https://twitter.com/anshulxyz)