Seaography 2.0: A Powerful and Extensible GraphQL Framework 🧭
Source: Dev.to
What is Seaography
Seaography is a GraphQL framework built on top of SeaORM that automatically generates resolvers with DataLoader integration to solve the N+1 problem. It offers extensive customization options, easy addition of custom endpoints, and fine‑grained authorization (RBAC, hooks/guards).
A short demo shows how to:
- Generate SeaORM entities from an existing database (e.g., the Sakila SQLite demo)
- Spin up a GraphQL server (compatible with Axum, Actix, Poem)
- Run queries via GraphQL Playground
You can follow the same steps in the quick‑start guide.
What kinds of queries are supported?
Filter, Ordering and Pagination
{
film(
filters: {
title: "{ contains: \"sea\" } # ⬅ like '%sea%'"
and: [{ releaseYear: { gt: "2000" } }, { length: { gt: 120 } }]
}
orderBy: { filmId: ASC }
pagination: { page: { page: 0, limit: 10 } }
# cursor‑based pagination is also supported:
# pagination: { cursor: { limit: 10, cursor: "Int[3]:100" } }
) {
nodes {
filmId
title
description
}
paginationInfo {
pages
current
}
}
}
Nested Relational Query
{
film(
# filter by related entity
having: {
actor: { firstName: { eq: "David" } }
category: { name: { eq: "Documentary" } }
}
) {
nodes {
filmId
title
actor {
nodes {
firstName
lastName
}
}
inventory {
nodes {
store {
address {
address
city {
city
}
}
}
}
}
}
}
}
Join paths used
film -> film_actor -> actor
-> inventory -> store -> address -> city
A DataLoader resolves these relations efficiently, eliminating the N+1 problem.
Mutations: create, update, delete
Full CRUD is supported (CreateOne, CreateBatch, Update, Delete).
mutation {
filmTextCreateBatch(
data: [
{ filmId: 1, title: "\"Foo bar\"", description: "\"Lorem ipsum dolor sit amet\"" }
{ filmId: 2, title: "\"Fizz buzz\"", description: "\"Consectetur adipiscing elit\"" }
]
) {
filmId
title
description
}
}
Custom Query
Seaography 2.0 introduces macros that let you write custom query endpoints while reusing the framework’s pagination and connection utilities.
Custom Query with pagination
Assume a Customer entity generated from the Sakila schema:
//! Entity from the sakila schema, generated by sea-orm-cli
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "customer")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub customer_id: i32,
pub store_id: i32,
pub first_name: String,
pub last_name: String,
// …
}
Create a custom endpoint that returns customers belonging to the store of the current request:
use seaography::{apply_pagination, Connection, CustomFields, PaginationInput};
pub struct Operations;
#[CustomFields]
impl Operations {
async fn customer_of_current_store(
ctx: &Context,
pagination: PaginationInput,
) -> async_graphql::Result> {
let db = ctx.data::()?;
let session = ctx.data::()?;
let query = customer::Entity::find()
.filter(customer::Column::StoreId.eq(session.store_id));
// Seaography handles pagination and execution
let connection = apply_pagination(&CONTEXT, db, query, pagination).await?;
Ok(connection)
}
}
The endpoint is exposed as:
customer_of_current_store(
pagination: PaginationInput
): CustomerConnection!
Example query
{
customer_of_current_store(pagination: { page: { page: 0, limit: 10 } }) {
nodes {
storeId
customerId
firstName
lastName
email
}
paginationInfo {
pages
current
}
}
}
With just a few lines of code you add a fully functional, paginated API endpoint; the heavy lifting is done by Seaography + SeaORM.
How does it work?
- Bridge – Seaography maps SeaORM types to Async‑GraphQL, allowing any SeaORM entity to be used as GraphQL output.
- Schema generation – On application startup, Seaography transforms the meta‑information of SeaORM entities into a GraphQL schema on‑the‑fly.
- Request lifecycle
- Async‑GraphQL parses the incoming HTTP request and builds a request context.
- Your HTTP handler intercepts the request, providing the context (e.g., database connection, session) to Seaography.
- Resolvers (generated or custom) execute queries via SeaORM, optionally using DataLoaders for efficient relation loading.
- Pagination, filtering, ordering, and authorization hooks are applied automatically before the final response is returned.