Added axum graphql example (#587)
* added example for axum + graphql * clean up * removed macos file * Pr/587 (#1) * Migrate on startup * Update CI * Add .gitignore * Add README Co-authored-by: Billy Chan <ccw.billy.123@gmail.com> Co-authored-by: Billy Chan <ccw.billy.123@gmail.com>
This commit is contained in:
parent
af235168db
commit
7ba6144ead
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@ -293,7 +293,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
path: [basic, actix_example, actix4_example, axum_example, rocket_example, poem_example]
|
||||
path: [basic, actix_example, actix4_example, axum_example, axum-graphql_example, rocket_example, poem_example]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
1
examples/axum-graphql_example/.env
Normal file
1
examples/axum-graphql_example/.env
Normal file
@ -0,0 +1 @@
|
||||
DATABASE_URL=sqlite:./db?mode=rwc
|
3
examples/axum-graphql_example/.gitignore
vendored
Normal file
3
examples/axum-graphql_example/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
db
|
||||
db-shm
|
||||
db-wal
|
17
examples/axum-graphql_example/Cargo.toml
Normal file
17
examples/axum-graphql_example/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "axum-graphql"
|
||||
authors = ["Aaron Leopold <aaronleopold1221@gmail.com>"]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
[workspace]
|
||||
members = [".", "entity", "migration"]
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
axum = "0.4.8"
|
||||
dotenv = "0.15.0"
|
||||
async-graphql-axum = "3.0.31"
|
||||
entity = { path = "entity" }
|
||||
migration = { path = "migration" }
|
13
examples/axum-graphql_example/README.md
Normal file
13
examples/axum-graphql_example/README.md
Normal file
@ -0,0 +1,13 @@
|
||||

|
||||
|
||||

|
||||
|
||||
# Axum-GraphQL with SeaORM example app
|
||||
|
||||
1. Modify the `DATABASE_URL` var in `.env` to point to your chosen database
|
||||
|
||||
1. Turn on the appropriate database feature for your chosen db in `entity/Cargo.toml` (the `"sqlx-sqlite",` line)
|
||||
|
||||
1. Execute `cargo run` to start the server
|
||||
|
||||
1. Visit [localhost:3000/api/graphql](http://localhost:3000/api/graphql) in browser
|
BIN
examples/axum-graphql_example/Screenshot1.png
Normal file
BIN
examples/axum-graphql_example/Screenshot1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 615 KiB |
BIN
examples/axum-graphql_example/Screenshot2.png
Normal file
BIN
examples/axum-graphql_example/Screenshot2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 570 KiB |
26
examples/axum-graphql_example/entity/Cargo.toml
Normal file
26
examples/axum-graphql_example/entity/Cargo.toml
Normal file
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "entity"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "entity"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
||||
[dependencies.async-graphql]
|
||||
version = "3.0.12"
|
||||
|
||||
[dependencies.sea-orm]
|
||||
version = "^0.6.0"
|
||||
features = [
|
||||
"macros",
|
||||
"runtime-tokio-native-tls",
|
||||
# "sqlx-postgres",
|
||||
# "sqlx-mysql",
|
||||
"sqlx-sqlite"
|
||||
]
|
||||
default-features = false
|
4
examples/axum-graphql_example/entity/src/lib.rs
Normal file
4
examples/axum-graphql_example/entity/src/lib.rs
Normal file
@ -0,0 +1,4 @@
|
||||
pub mod note;
|
||||
|
||||
pub use async_graphql;
|
||||
pub use sea_orm;
|
39
examples/axum-graphql_example/entity/src/note.rs
Normal file
39
examples/axum-graphql_example/entity/src/note.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use async_graphql::*;
|
||||
use sea_orm::{entity::prelude::*, DeleteMany};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize, SimpleObject)]
|
||||
#[sea_orm(table_name = "notes")]
|
||||
#[graphql(concrete(name = "Note", params()))]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
#[serde(skip_deserializing)]
|
||||
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 {}
|
||||
|
||||
impl Entity {
|
||||
pub fn find_by_id(id: i32) -> Select<Entity> {
|
||||
Self::find().filter(Column::Id.eq(id))
|
||||
}
|
||||
|
||||
pub fn find_by_title(title: &str) -> Select<Entity> {
|
||||
Self::find().filter(Column::Title.eq(title))
|
||||
}
|
||||
|
||||
pub fn delete_by_id(id: i32) -> DeleteMany<Entity> {
|
||||
Self::delete_many().filter(Column::Id.eq(id))
|
||||
}
|
||||
}
|
14
examples/axum-graphql_example/migration/Cargo.toml
Normal file
14
examples/axum-graphql_example/migration/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "migration"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
name = "migration"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
sea-schema = { version = "0.5.0", default-features = false, features = [ "migration", "debug-print" ] }
|
||||
dotenv = "0.15.0"
|
||||
entity = { path = "../entity" }
|
37
examples/axum-graphql_example/migration/README.md
Normal file
37
examples/axum-graphql_example/migration/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Running Migrator CLI
|
||||
|
||||
- Apply all pending migrations
|
||||
```sh
|
||||
cargo run
|
||||
```
|
||||
```sh
|
||||
cargo run -- up
|
||||
```
|
||||
- Apply first 10 pending migrations
|
||||
```sh
|
||||
cargo run -- up -n 10
|
||||
```
|
||||
- Rollback last applied migrations
|
||||
```sh
|
||||
cargo run -- down
|
||||
```
|
||||
- Rollback last 10 applied migrations
|
||||
```sh
|
||||
cargo run -- down -n 10
|
||||
```
|
||||
- Drop all tables from the database, then reapply all migrations
|
||||
```sh
|
||||
cargo run -- fresh
|
||||
```
|
||||
- Rollback all applied migrations, then reapply all migrations
|
||||
```sh
|
||||
cargo run -- refresh
|
||||
```
|
||||
- Rollback all applied migrations
|
||||
```sh
|
||||
cargo run -- reset
|
||||
```
|
||||
- Check the status of all migrations
|
||||
```sh
|
||||
cargo run -- status
|
||||
```
|
12
examples/axum-graphql_example/migration/src/lib.rs
Normal file
12
examples/axum-graphql_example/migration/src/lib.rs
Normal file
@ -0,0 +1,12 @@
|
||||
pub use sea_schema::migration::*;
|
||||
|
||||
mod m20220101_000001_create_table;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![Box::new(m20220101_000001_create_table::Migration)]
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
use entity::{
|
||||
note,
|
||||
sea_orm::{DbBackend, EntityTrait, Schema},
|
||||
};
|
||||
use sea_schema::migration::{
|
||||
sea_query::{self, *},
|
||||
*,
|
||||
};
|
||||
|
||||
pub struct Migration;
|
||||
|
||||
fn get_seaorm_create_stmt<E: EntityTrait>(e: E) -> TableCreateStatement {
|
||||
let schema = Schema::new(DbBackend::Sqlite);
|
||||
|
||||
schema
|
||||
.create_table_from_entity(e)
|
||||
.if_not_exists()
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn get_seaorm_drop_stmt<E: EntityTrait>(e: E) -> TableDropStatement {
|
||||
Table::drop().table(e).if_exists().to_owned()
|
||||
}
|
||||
|
||||
impl MigrationName for Migration {
|
||||
fn name(&self) -> &str {
|
||||
"m20220101_000001_create_table"
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let stmts = vec![get_seaorm_create_stmt(note::Entity)];
|
||||
|
||||
for stmt in stmts {
|
||||
manager.create_table(stmt.to_owned()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
let stmts = vec![get_seaorm_drop_stmt(note::Entity)];
|
||||
|
||||
for stmt in stmts {
|
||||
manager.drop_table(stmt.to_owned()).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
26
examples/axum-graphql_example/migration/src/main.rs
Normal file
26
examples/axum-graphql_example/migration/src/main.rs
Normal file
@ -0,0 +1,26 @@
|
||||
use migration::Migrator;
|
||||
use sea_schema::migration::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use dotenv::dotenv;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
#[cfg(debug_assertions)]
|
||||
dotenv().ok();
|
||||
|
||||
let fallback = "sqlite:./db?mode=rwc";
|
||||
|
||||
match std::env::var("DATABASE_URL") {
|
||||
Ok(val) => {
|
||||
println!("Using DATABASE_URL: {}", val);
|
||||
}
|
||||
Err(_) => {
|
||||
std::env::set_var("DATABASE_URL", fallback);
|
||||
println!("Set DATABASE_URL: {}", fallback);
|
||||
}
|
||||
};
|
||||
|
||||
cli::run_cli(Migrator).await;
|
||||
}
|
20
examples/axum-graphql_example/src/db.rs
Normal file
20
examples/axum-graphql_example/src/db.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use entity::sea_orm;
|
||||
use sea_orm::DatabaseConnection;
|
||||
|
||||
pub struct Database {
|
||||
pub connection: DatabaseConnection,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub async fn new() -> Self {
|
||||
let connection = sea_orm::Database::connect(std::env::var("DATABASE_URL").unwrap())
|
||||
.await
|
||||
.expect("Could not connect to database");
|
||||
|
||||
Database { connection }
|
||||
}
|
||||
|
||||
pub fn get_connection(&self) -> &DatabaseConnection {
|
||||
&self.connection
|
||||
}
|
||||
}
|
3
examples/axum-graphql_example/src/graphql/mod.rs
Normal file
3
examples/axum-graphql_example/src/graphql/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod mutation;
|
||||
pub mod query;
|
||||
pub mod schema;
|
10
examples/axum-graphql_example/src/graphql/mutation/mod.rs
Normal file
10
examples/axum-graphql_example/src/graphql/mutation/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use entity::async_graphql;
|
||||
|
||||
pub mod note;
|
||||
|
||||
pub use note::NoteMutation;
|
||||
|
||||
// Add your other ones here to create a unified Mutation object
|
||||
// e.x. Mutation(NoteMutation, OtherMutation, OtherOtherMutation)
|
||||
#[derive(async_graphql::MergedObject, Default)]
|
||||
pub struct Mutation(NoteMutation);
|
60
examples/axum-graphql_example/src/graphql/mutation/note.rs
Normal file
60
examples/axum-graphql_example/src/graphql/mutation/note.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use async_graphql::{Context, Object, Result};
|
||||
use entity::async_graphql::{self, InputObject, SimpleObject};
|
||||
use entity::note;
|
||||
use entity::sea_orm::{ActiveModelTrait, Set};
|
||||
|
||||
use crate::db::Database;
|
||||
|
||||
// I normally separate the input types into separate files/modules, but this is just
|
||||
// a quick example.
|
||||
|
||||
#[derive(InputObject)]
|
||||
pub struct CreateNoteInput {
|
||||
pub title: String,
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(SimpleObject)]
|
||||
pub struct DeleteResult {
|
||||
pub success: bool,
|
||||
pub rows_affected: u64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NoteMutation;
|
||||
|
||||
#[Object]
|
||||
impl NoteMutation {
|
||||
pub async fn create_note(
|
||||
&self,
|
||||
ctx: &Context<'_>,
|
||||
input: CreateNoteInput,
|
||||
) -> Result<note::Model> {
|
||||
let db = ctx.data::<Database>().unwrap();
|
||||
|
||||
let note = note::ActiveModel {
|
||||
title: Set(input.title),
|
||||
text: Set(input.text),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(note.insert(db.get_connection()).await?)
|
||||
}
|
||||
|
||||
pub async fn delete_note(&self, ctx: &Context<'_>, id: i32) -> Result<DeleteResult> {
|
||||
let db = ctx.data::<Database>().unwrap();
|
||||
|
||||
let res = note::Entity::delete_by_id(id)
|
||||
.exec(db.get_connection())
|
||||
.await?;
|
||||
|
||||
if res.rows_affected <= 1 {
|
||||
Ok(DeleteResult {
|
||||
success: true,
|
||||
rows_affected: res.rows_affected,
|
||||
})
|
||||
} else {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
10
examples/axum-graphql_example/src/graphql/query/mod.rs
Normal file
10
examples/axum-graphql_example/src/graphql/query/mod.rs
Normal file
@ -0,0 +1,10 @@
|
||||
use entity::async_graphql;
|
||||
|
||||
pub mod note;
|
||||
|
||||
pub use note::NoteQuery;
|
||||
|
||||
// Add your other ones here to create a unified Query object
|
||||
// e.x. Query(NoteQuery, OtherQuery, OtherOtherQuery)
|
||||
#[derive(async_graphql::MergedObject, Default)]
|
||||
pub struct Query(NoteQuery);
|
28
examples/axum-graphql_example/src/graphql/query/note.rs
Normal file
28
examples/axum-graphql_example/src/graphql/query/note.rs
Normal file
@ -0,0 +1,28 @@
|
||||
use async_graphql::{Context, Object, Result};
|
||||
use entity::{async_graphql, note, sea_orm::EntityTrait};
|
||||
|
||||
use crate::db::Database;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct NoteQuery;
|
||||
|
||||
#[Object]
|
||||
impl NoteQuery {
|
||||
async fn get_notes(&self, ctx: &Context<'_>) -> Result<Vec<note::Model>> {
|
||||
let db = ctx.data::<Database>().unwrap();
|
||||
|
||||
Ok(note::Entity::find()
|
||||
.all(db.get_connection())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?)
|
||||
}
|
||||
|
||||
async fn get_note_by_id(&self, ctx: &Context<'_>, id: i32) -> Result<Option<note::Model>> {
|
||||
let db = ctx.data::<Database>().unwrap();
|
||||
|
||||
Ok(note::Entity::find_by_id(id)
|
||||
.one(db.get_connection())
|
||||
.await
|
||||
.map_err(|e| e.to_string())?)
|
||||
}
|
||||
}
|
21
examples/axum-graphql_example/src/graphql/schema.rs
Normal file
21
examples/axum-graphql_example/src/graphql/schema.rs
Normal file
@ -0,0 +1,21 @@
|
||||
use async_graphql::{EmptySubscription, Schema};
|
||||
use entity::async_graphql;
|
||||
use migration::{Migrator, MigratorTrait};
|
||||
|
||||
use crate::{
|
||||
db::Database,
|
||||
graphql::{mutation::Mutation, query::Query},
|
||||
};
|
||||
|
||||
pub type AppSchema = Schema<Query, Mutation, EmptySubscription>;
|
||||
|
||||
/// Builds the GraphQL Schema, attaching the Database to the context
|
||||
pub async fn build_schema() -> AppSchema {
|
||||
let db = Database::new().await;
|
||||
|
||||
Migrator::up(db.get_connection(), None).await.unwrap();
|
||||
|
||||
Schema::build(Query::default(), Mutation::default(), EmptySubscription)
|
||||
.data(db)
|
||||
.finish()
|
||||
}
|
49
examples/axum-graphql_example/src/main.rs
Normal file
49
examples/axum-graphql_example/src/main.rs
Normal file
@ -0,0 +1,49 @@
|
||||
mod db;
|
||||
mod graphql;
|
||||
|
||||
use entity::async_graphql;
|
||||
|
||||
use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
|
||||
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
|
||||
use axum::{
|
||||
extract::Extension,
|
||||
response::{Html, IntoResponse},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use graphql::schema::{build_schema, AppSchema};
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
use dotenv::dotenv;
|
||||
|
||||
async fn graphql_handler(schema: Extension<AppSchema>, req: GraphQLRequest) -> GraphQLResponse {
|
||||
schema.execute(req.into_inner()).await.into()
|
||||
}
|
||||
|
||||
async fn graphql_playground() -> impl IntoResponse {
|
||||
Html(playground_source(GraphQLPlaygroundConfig::new(
|
||||
"/api/graphql",
|
||||
)))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
#[cfg(debug_assertions)]
|
||||
dotenv().ok();
|
||||
|
||||
let schema = build_schema().await;
|
||||
|
||||
let app = Router::new()
|
||||
.route(
|
||||
"/api/graphql",
|
||||
get(graphql_playground).post(graphql_handler),
|
||||
)
|
||||
.layer(Extension(schema));
|
||||
|
||||
println!("Playground: http://localhost:3000/api/graphql");
|
||||
|
||||
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
|
||||
.serve(app.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user