Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.
The design and implementation of Seaography can be found on our release blog post and documentation.
Extending a SeaORM project
Since Seaography is built on top of SeaORM, you can easily build a GraphQL server from a SeaORM project.
Start by adding Seaography and GraphQL dependencies to your Cargo.toml
.
[dependencies]
sea-orm = { version = "^0.9", features = [ ... ] }
+ seaography = { version = "^0.1", features = [ "with-decimal", "with-chrono" ] }
+ async-graphql = { version = "4.0.10", features = ["decimal", "chrono", "dataloader"] }
+ async-graphql-poem = { version = "4.0.10" }
Then, derive a few macros on the SeaORM entities.
use sea_orm::entity::prelude::*;
#[derive(
Clone,
Debug,
PartialEq,
DeriveEntityModel,
+ async_graphql::SimpleObject,
+ seaography::macros::Filter,
)]
+ #[graphql(complex)]
+ #[graphql(name = "FilmActor")]
#[sea_orm(table_name = "film_actor")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub actor_id: i32,
#[sea_orm(primary_key, auto_increment = false)]
pub film_id: i32,
pub last_update: DateTimeUtc,
}
#[derive(
Copy,
Clone,
Debug,
EnumIter,
DeriveRelation,
+ seaography::macros::RelationsCompact,
)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::film::Entity",
from = "Column::FilmId",
to = "super::film::Column::FilmId",
on_update = "Cascade",
on_delete = "NoAction"
)]
Film,
#[sea_orm(
belongs_to = "super::actor::Entity",
from = "Column::ActorId",
to = "super::actor::Column::ActorId",
on_update = "Cascade",
on_delete = "NoAction"
)]
Actor,
}
We also need to define QueryRoot
for the GraphQL server. This define the GraphQL schema.
#[derive(Debug, seaography::macros::QueryRoot)]
#[seaography(entity = "crate::entities::actor")]
#[seaography(entity = "crate::entities::film")]
#[seaography(entity = "crate::entities::film_actor")]
pub struct QueryRoot;
use sea_orm::prelude::*;
pub mod entities;
pub mod query_root;
pub use query_root::QueryRoot;
pub struct OrmDataloader {
pub db: DatabaseConnection,
}
Finally, create an executable to drive the GraphQL server.
use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_poem::GraphQL;
use poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};
use sea_orm::Database;
use seaography_example_project::*;
// ...
#[handler]
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}
#[tokio::main]
async fn main() {
// ...
let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader { db: database.clone() },
tokio::spawn,
);
let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();
let app = Route::new()
.at("/", get(graphql_playground)
.post(GraphQL::new(schema)));
Server::new(TcpListener::bind("0.0.0.0:8000"))
.run(app)
.await
.unwrap();
}
Generating a project from database
If all you have is a database schema, good news! You can setup a GraphQL server without writing a single line of code.
Install seaography-cli
, it helps you generate SeaORM entities along with a full Rust project based on a database schema.
cargo install seaography-cli
Run seaography-cli
to generate code for the GraphQL server.
# The command take three arguments
seaography-cli <DATABASE_URL> <CRATE_NAME> <DESTINATION>
# MySQL
seaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql
# PostgreSQL
seaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres
# SQLite
seaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql
Checkout the example projects
We have the following examples for you, alongside with the SQL scripts to initialize the database.
All examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a demo GraphQL playground in case you can't wait to play with it.
Starting the GraphQL Server
Your GraphQL server is ready to launch! Go to the Rust project root then execute cargo run
to spin it up.
$ cargo run
Playground: http://localhost:8000
Visit the GraphQL playground at http://localhost:8000
Query Data via GraphQL
Let say we want to get the first 3 films released on or after year 2006 sorted in ascending order of its title.
{
film(
pagination: { limit: 3, page: 0 }
filters: { releaseYear: { gte: "2006" } }
orderBy: { title: ASC }
) {
data {
filmId
title
description
releaseYear
filmActor {
actor {
actorId
firstName
lastName
}
}
}
pages
current
}
}
We got the following JSON result after running the GraphQL query.
{
"data": {
"film": {
"data": [
{
"filmId": 1,
"title": "ACADEMY DINOSAUR",
"description": "An Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
"releaseYear": "2006",
"filmActor": [
{
"actor": {
"actorId": 1,
"firstName": "PENELOPE",
"lastName": "GUINESS"
}
},
{
"actor": {
"actorId": 10,
"firstName": "CHRISTIAN",
"lastName": "GABLE"
}
},
// ...
]
},
{
"filmId": 2,
"title": "ACE GOLDFINGER",
"description": "A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China",
"releaseYear": "2006",
"filmActor": [
// ...
]
},
// ...
],
"pages": 334,
"current": 0
}
}
}
Behind the scene, the following SQL were queried:
SELECT "film"."film_id",
"film"."title",
"film"."description",
"film"."release_year",
"film"."language_id",
"film"."original_language_id",
"film"."rental_duration",
"film"."rental_rate",
"film"."length",
"film"."replacement_cost",
"film"."rating",
"film"."special_features",
"film"."last_update"
FROM "film"
WHERE "film"."release_year" >= '2006'
ORDER BY "film"."title" ASC
LIMIT 3 OFFSET 0
SELECT "film_actor"."actor_id", "film_actor"."film_id", "film_actor"."last_update"
FROM "film_actor"
WHERE "film_actor"."film_id" IN (1, 3, 2)
SELECT "actor"."actor_id", "actor"."first_name", "actor"."last_name", "actor"."last_update"
FROM "actor"
WHERE "actor"."actor_id" IN (24, 162, 20, 160, 1, 188, 123, 30, 53, 40, 2, 64, 85, 198, 10, 19, 108, 90)
Under the hood, Seaography uses async_graphql::dataloader in querying nested objects to tackle the N+1 problem.
To learn more, checkout the Seaography Documentation.
Conclusion
Seaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.
However, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.
People
Seaography is created by:
- Panagiotis Karatakis - Summer of Code Contributor; developer of Seaography
- Chris Tsang - Summer of Code Mentor; lead developer of SeaQL
- Billy Chan - Summer of Code Mentor; core member of SeaQL
Top comments (0)