DEV Community

Cover image for Axum+SeaORM+Async-graphql: Building a GraphQL Service from Scratch
叶师傅
叶师傅

Posted on

Axum+SeaORM+Async-graphql: Building a GraphQL Service from Scratch

This article takes a learner's perspective and guides you step by step to quickly set up a GraphQL service.

First, let's understand what Axum, SeaORM, and Async-graphql are.

Axum

Axum is a high-performance, asynchronous, modular framework for building web applications. It is based on the Rust language and relies on the Tokio asynchronous runtime for handling concurrency and I/O. The main features of Axum include:

  • Ergonomics and modularity:

    • The design focuses on the developer experience, providing an easy-to-use API to organize the structure of applications.
    • Supports breaking down application logic into reusable modules, which helps build large and complex services.
  • No macro-based routing:

    • Provides a mechanism for defining and matching HTTP routes without relying on macros, allowing you to clearly declare how requests are dispatched to corresponding handlers.
  • Extractors:

    • Offers a declarative way to extract data from requests, such as query parameters, path parameters, form data, etc.
  • Middleware support:

    • Allows the writing of custom middleware through native methods provided by the axum::middleware module or by combining existing middleware for authentication, logging, error handling, etc.
  • Asynchronous services:

    • Based on Rust's asynchronous programming model, Axum can efficiently utilize system resources, achieving non-blocking I/O and high concurrency performance.
  • Feature-rich:

    • Supports handling various HTTP features, such as GET, POST requests, file uploads, WebSocket connections, and serving static resources.

Axum does not have a very good documentation; when I was learning, I mainly referred to the axum - Rust (docs.rs) library documentation.

SeaORM

SeaORM is an Object-Relational Mapping (ORM) framework written in Rust. It aims to simplify interactions with SQL databases, allowing developers to represent database tables through structured Rust types and provides a convenient, type-safe, and intuitive way to perform CRUD (Create, Read, Update, and Delete) operations.

The main features of SeaORM include:

  • Multi-database support:

    • SeaORM supports various SQL databases, such as SQLite, PostgreSQL, MySQL, etc., allowing developers to easily switch database backends without changing the code.
  • Entity definition:

    • Developers can define Entity structs, annotate them to correspond to tables in the database, and automatically map columns to the struct's fields.
  • Query builder:

    • Provides a flexible and powerful query-building API, making it simple and type-safe to write complex SQL queries.
  • Active Record pattern:

    • SeaORM follows the ActiveRecord pattern, meaning entities can directly manipulate the database like objects, for example, saving, updating, and deleting records.
  • Asynchronous support:

    • For high-performance application scenarios, SeaORM can integrate with asynchronous runtimes and database drivers (such as SQLx) to perform asynchronous I/O operations.
  • Type safety:

    • It emphasizes the advantages of Rust's type system, ensuring that many potential database access errors are caught at compile time.
  • Migration tools:

    • SeaORM also includes database migration tools to help developers manage version control and changes to the database schema.
  • Modular design:

    • It has a high degree of modularity and extensibility, allowing the selection of partial features based on project requirements.

For more information, see the SeaORM official tutorial.

Async-graphql

Async-graphql is an asynchronous GraphQL server library for the Rust language, allowing developers to build high-performance and GraphQL-compliant APIs. Async-graphql provides a complete GraphQL service implementation, including schema definition, resolvers, executors, and subscription features, and is deeply integrated with Rust's asynchronous programming model.

The main features of Async-graphql include:

  • Asynchronous support: Fully designed based on Rust's asynchronous programming model, leveraging Rust's async/await keywords and Futures to efficiently utilize system resources when handling high-concurrency requests, avoiding blocking, and reducing context-switching overhead.

  • Type safety: Ensures the safety of GraphQL APIs through Rust's powerful type system, allowing developers to define GraphQL types and catch errors at compile time.

  • Zero-cost abstractions: Follows Rust's core principles, ensuring ease of use while minimizing runtime overhead.

  • Flexible schema definition: Provides an intuitive way to define GraphQL schemas, which can be easily mapped to Rust data structures (such as enums, structs) and automatically generate corresponding GraphQL types.

  • Integration convenience: Can seamlessly integrate with various Rust ecosystem database ORMs (such as Diesel) and HTTP server frameworks (such as Actix-web) for building full-stack Rust applications.

  • Performance optimization: Due to the characteristics of the Rust language and the library's design, Async-graphql can run GraphQL queries with low memory usage and high throughput.

For more information, see the Async-graphql official tutorial.

The above is just a simple copy and paste of some concepts; the real highlight of this article is about to begin. I believe you are eager to start practicing.

Quick Start

  1. Integrate GraphQL

Shuttle

Shuttle is a cloud service platform that allows users to access and utilize server and database resources for free, aiming to provide convenience for beginners so they can quickly start learning and practicing without additional investment.

You can log in with GitHub to get three free projects.

Install Shuttle:

cargo install cargo-shuttle
Enter fullscreen mode Exit fullscreen mode

Initialize:

cargo shuttle init
Enter fullscreen mode Exit fullscreen mode

Then choose the Axum framework.

Install dependencies:

cargo add async-graphql async-graphql-axum
Enter fullscreen mode Exit fullscreen mode

Then modify main.rs:

use async_graphql::{http::GraphiQLSource, *};
use async_graphql_axum::GraphQL;
use axum::{
    response::{Html, IntoResponse},
    routing::get,
    Router,
};

// Define an enum

#[derive(Debug, Enum, PartialEq, Eq, Clone, Copy)]

enum Role {
    Admin,
    User,
}

impl Default for Role {
    fn default() -> Self {
        Self::User
    }
}

// Define a complex object

#[derive(Debug, SimpleObject, Default)]
#[graphql(complex)]
struct User {
    role: Role,
    username: String,
    email: String,
    address: String,
    age: i32,
}

#[ComplexObject]
impl User {
    async fn users(&self) -> String {
        format!("{:?}", self)
    }
}

// Define a query object

struct Query;

// Implement methods for the query object

#[Object]
impl Query {
    // Asynchronously return a string
    async fn hello(&self) -> String {
        "Hello, world!".to_string()
    }

    // Asynchronously return the GraphiQL page
    async fn graphql() -> impl IntoResponse {
        Html(GraphiQLSource::build().endpoint("/graphql").finish())
    }

    // Asynchronously return a static string
    async fn hello_world() -> &'static str {
        "Hello, world!"
    }
}

// Main function

#[shuttle_runtime::main]
async fn main() -> shuttle_axum::ShuttleAxum {
    // Build the GraphQL Schema
    let schema = Schema::build(Query, EmptyMutation, EmptySubscription)
        .finish();
    // Build the router
    let router = Router::new()
        .route("/", get(hello_world))
        .route("/graphql", get(graphql).post_service(GraphQL::new(schema)));

    // Use the router
    router
}

Enter fullscreen mode Exit fullscreen mode

Then run:

cargo shuttle run
Enter fullscreen mode Exit fullscreen mode

Enter http://127.0.0.1:8000/graphql in your browser to access the GraphQL playground.

  1. Define database tables

Set up migration

If starting with a new database, it's best to version control the database schema. SeaORM comes with a migration tool that allows you to write migrations using SeaQuery or SQL, meaning you write migration code first and then generate entities, the specific content of which can be seen in the above documentation.

Install sea-orm-cli:

cargo install sea-orm-cli
Enter fullscreen mode Exit fullscreen mode

Then initialize migration:

sea-orm-cli migrate init
Enter fullscreen mode Exit fullscreen mode

Add the package to the workspace by modifying the Cargo.toml at the project root:

[dependencies]
// add
entries = { path = "./entries" }
// add
[workspace]
members = ["migration"]
Enter fullscreen mode Exit fullscreen mode

Then write the migration file:

// Include the preset items from the sea_orm_migration library
use sea_orm_migration::prelude::*;

// Use the DeriveMigrationName trait to automatically generate migration names

#[derive(DeriveMigrationName)]
pub struct Migration;

// Implement the MigrationTrait trait, which defines the creation and deletion operations of the database table

#[async_trait]
impl MigrationTrait for Migration {
    // The up method is used to execute the upgrade operation of the database table structure, i.e., creating a new table or modifying the table structure
    async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
        // Create the User table
        manager.create_table(
            Table::create()
                .table(User::Table)
                // If the table does not exist, create it
                .if_not_exists()
                // Define theprimary key ID column as an integer type, not null, and auto-increment
                .col(ColumnDef::new(User::Id).integer().not_null().auto_increment().primary_key())
                // Username column, string type and not null
                .col(ColumnDef::new(User::Username).string().not_null())
                // Email column, string type and not null
                .col(ColumnDef::new(User::Email).string().not_null())
                // Address column, string type and not null
                .col(ColumnDef::new(User::Address).string().not_null())
                // Age column, integer type and not null
                .col(ColumnDef::new(User::Age).integer().not_null())
                // Convert the above definitions into an owned Table object
                .to_owned(),
        )
        .await?;
      ...
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. Use Shuttle's database

Shuttle Shared Databases

Step 1: Install Shuttle Shared Database dependencies

To use Shuttle's shared database service in your project, first, you need to install the corresponding Rust libraries. Execute the following command in the directory where your Cargo.toml file is located:

cargo add shuttle-shared-db sea-orm sqlx \
    -F shuttle-shared-db/postgres \
    -F shuttle-shared-db/sqlx \
    -F sea-orm/sqlx-postgres \
    -F sea-orm/runtime-tokio-native-tls
Enter fullscreen mode Exit fullscreen mode

This will add dependencies such as shuttle-shared-db, sea-orm, and sqlx, and specify related features.

Step 2: Configure the main program entry (src/main.rs)

Next, in the src/main.rs file, introduce the necessary modules and modify the main function to utilize Shuttle's database connection pool:

// Introduce necessary modules
use migration::MigratorTrait;
use sea_orm::SqlxPostgresConnector;
use shuttle_runtime::{main, ShuttleAxum};
use shuttle_shared_db::Postgres;

#[main]
async fn main(
    // Add Shuttle Shared Database's Postgres connection pool parameter
    #[shared_db(Postgres)] pool: sqlx::PgPool,
) -> ShuttleAxum {
    // Convert SQLx's PgPool to the connector required by SeaORM
    let connector = SqlxPostgresConnector::from_sqlx_postgres_pool(pool);
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Run the Shuttle application

Make sure Docker is installed, as the Shuttle framework will provide database services by starting database images locally.

Execute the following command to start your application:

cargo shuttle run
Enter fullscreen mode Exit fullscreen mode

After Shuttle successfully starts, you will see the database connection address automatically built and assigned by Shuttle in the console. This address can be used directly to connect to the shared database instance managed by Shuttle, without manual configuration. Please refer to the log information output at runtime for specific connection details, as shown in the figure below.

Now, your application has integrated Shuttle's shared database and has performed the necessary initialization and connection settings, and you can continue to develop data operations and business logic.

  1. Define entities

Step 1: Database migration

Before defining entities, first ensure that the database has been manually migrated. Use the following command to upgrade:

cargo run -p migration -- -u "postgres://postgres:postgres@localhost:22732/postgres"
Enter fullscreen mode Exit fullscreen mode

If you need to revert to a previous migration version, execute the following downgrade command:

cargo run -p migration -- -u "postgres://postgres:postgres@localhost:22732/postgres" down
Enter fullscreen mode Exit fullscreen mode

Step 2: Generate entities

After the database migration is successful, use the command-line tool (sea-orm-cli) provided by sea-orm to generate entity files:

sea-orm-cli generate entity \
    -o entity/src \
    -l \
    -u postgres://postgres:postgres@localhost:22732/postgres \
    --with-serde both \
    --model-extra-attributes='graphql(concrete(name = "Favorite", params()))' \
    --model-extra-derives async_graphql::SimpleObject
Enter fullscreen mode Exit fullscreen mode

This will automatically generate entity model code based on the database table structure.

Step 3: Review the generated directory structure and entity code

The generated entity directory and its contents are as follows:

.
├── Cargo.toml
└── src
    ├── actions.rs
    ├── comment.rs
    ├── favorite.rs
    ├── lib.rs
    ├── prelude.rs
    └── user.rs
Enter fullscreen mode Exit fullscreen mode

Take the favorite.rs file as an example; it contains the entity code automatically generated by sea-orm-codegen:

//! SeaORM Entity. Generated by sea-orm-codegen 0.12.14

// ... (omitted import part)

#[derive(...)]
#[sea_orm(table_name = "favorite")]

#[graphql(concrete(name = "Favorite" params()))] // There is a small error here
pub struct Model {
    #[sea_orm(primary_key, auto_increment = false)]
    pub user_id: i32,
    #[sea_orm(primary_key, auto_increment = false)]
    pub action_id: i32,
    pub create_time: Option,
}

// ... (omitted relationship and ActiveModelBehavior implementation part)
Enter fullscreen mode Exit fullscreen mode

Step 4: Correct the generated entity code

Since there is an error in the GraphQL annotation in the generated favorite.rs, it needs to be manually modified:

#[graphql(concrete(name = "Favorite" params()))]
// Note that a comma is added here, and the name is correctly matched with the table name
Enter fullscreen mode Exit fullscreen mode

Step 5: Initialize and configure the entity module

Enter the entity directory, initialize a new Rust project, and install the required dependencies:

cd entity
cargo init
cargo add async-graphql sea-orm serde -F serde/derive async-graphql/chrono
Enter fullscreen mode Exit fullscreen mode

Step 6: Add the entity module to the workspace

Go back to the Cargo.toml file in the root directory, configure it as a workspace member, and add related dependencies:

[dependencies]
// Other existing dependencies...
migration = { path = "./migration" }
entity = { path = "./entity" }

[workspace]
members = ["entity", "migration"]
Enter fullscreen mode Exit fullscreen mode

Now you have completed the definition and integration of entities. You can further refine or expand these entities and related service codes according to actual needs.

  1. Write services

This section will guide you on how to create a new library package in a Rust application specifically for handling logic related to database interactions. We will implement user service operations through the sea-orm framework and prepare for subsequent writing of tests.

Step 1: Create a service library

First, create a new Rust library under the project root directory:

cargo new --lib service
Enter fullscreen mode Exit fullscreen mode

Next, install the required dependencies:

cargo add entity --path ../entity
cargo add sea-orm -F debug-print,runtime-tokio-native-tls,sqlx-postgres
cargo add tokio --dev -F full
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Cargo.toml file

Open the service/Cargo.toml file and add mock functionality, configuring related dependencies:

[package]
name = "service"
version = "0.1.0"
edition = "2021"

[dependencies]
entity = { version = "0.1.0", path = "../entity" }
sea-orm = { version = "0.12.14", features = [
    "debug-print",
    "runtime-tokio-native-tls",
    "sqlx-postgres",
] }

[dev-dependencies]
tokio = { version = "1.36.0", features = ["full"] }

[features]
mock = ["sea-orm/mock"]

[[test]]
name = "mock"
required-features = ["mock"]
Enter fullscreen mode Exit fullscreen mode

Step 3: Create and configure test files

Create a file named mock.rs under the tests/ directory, where we will later write mock tests for the service.

The current project directory structure should be as follows:

.
├── Cargo.toml
├── src
│   ├── actions.rs
│   ├── comment.rs
│   ├── favorite.rs
│   ├── lib.rs
│   ├── prelude.rs
│   └── user.rs
└── tests
    └── mock.rs
Enter fullscreen mode Exit fullscreen mode

Step 4: Implement User service

In the src/user.rs file, we define the UserServer struct and implement methods for getting all users, getting a user by ID, creating a user, updating a user, and deleting a user:

use ::entity::{
    prelude::User,
    user::{ActiveModel, Model},
};
use sea_orm::*;

pub struct UserServer;

impl UserServer {
    /// Get all users
    pub async fn get_all_user(db: &DbConn) -> super::Result<()> {
        User::find().all(db).await
    }
}
Enter fullscreen mode Exit fullscreen mode

Other service classes can be derived and implemented in a similar manner.

Summary:

So far, we have successfully created a new library package service and implemented user service operations. In the next section, we will write corresponding unit tests anduse the mock functionality provided by sea-orm to verify these services.

  1. Write service tests

The directory structure is as follows:

.
├── Cargo.toml
├── src
│   ├── actions.rs
│   ├── comment.rs
│   ├── favorite.rs
│   ├── lib.rs
│   ├── prelude.rs
│   └── user.rs
└── tests
    ├── mock.rs
    └── prepare.rs
Enter fullscreen mode Exit fullscreen mode

Step 1: Set up mock data in prepare.rs

First, in the tests/prepare.rs file, create a function to initialize and configure a mock database connection. This function will return a mock DatabaseConnection object and preset query results.

use ::entity::user;
use sea_orm::*;

#[cfg(feature = "mock")]
pub fn prepare_mock_db() -> DatabaseConnection {
    // Create a new Mock database connection
    MockDatabase::new(DatabaseBackend::Postgres)
        // Add query results
        .append_query_results(vec![
            // User 1
            [user::Model {
                id: 1,
                username: "张三".to_string(),
                email: "zhangsan@example.com".to_string(),
                address: "河南省郑州市".to_string(),
                age: 25,
            }],
            // User 2
            [user::Model {
                id: 2,
                username: "李四".to_string(),
                email: "lisi@example.com".to_string(),
                address: "广东省广州市".to_string(),
                age: 30,
            }],
            // User 3
            [user::Model {
                id: 3,
                username: "王五".to_string(),
                email: "wangwu@example.com".to_string(),
                address: "上海市".to_string(),
                age: 22,
            }],
            [user::Model {
                id: 4,
                username: "张三".to_string(),
                email: "zhangsan@example.com".to_string(),
                address: "河南省郑州市".to_string(),
                age: 25,
            }],
            [user::Model {
                id: 4,
                username: "李四".to_string(),
                email: "lisi@qq.com".to_string(),
                address: "地球村".to_string(),
                age: 0,
            }],
            [user::Model {
                id: 6,
                username: "张三6".to_string(),
                email: "zhangsan6@example.com".to_string(),
                address: "河南省郑州市".to_string(),
                age: 6,
            }],
        ])
        // Add execution results
        .append_exec_results([MockExecResult {
            last_insert_id: 4,
            rows_affected: 1,
        }])
        // Convert the Mock database connection to the DatabaseConnection type
        .into_connection()
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Write service tests in mock.rs

Next, in the tests/mock.rs file, write actual service tests. Use the prepare_mock_db function to obtain a mock database connection and perform various operations on the user service.

use entity::user::Model;
use service::user::UserServer;
mod prepare;

#[tokio::test]
async fn test_get_user_by_id() {
    // Prepare mock database connection
    let db = &prepare::prepare_mock_db();

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Run the test command:

cargo test -p service -F mock --test mock -- --nocapture
Enter fullscreen mode Exit fullscreen mode

Note:

  • Each database read operation requires a corresponding query result to be added in prepare_mock_db.
  • Delete or other modification operations require corresponding exec_results to be added.
  • After each operation, the Mock database automatically removes the matching buffered data.
  • Therefore, when preparing mock results, be sure to consider how many read or delete operations are actually performed in the code.
  1. Writing API Interface Tutorial

This section will guide you on how to create GraphQL API interfaces in a Rust application. We will implement user query and operation-related APIs based on the async-graphql and sea-orm frameworks.

Step 1: Create a Rust library

First, create a new Rust library under the project root directory for writing APIs:

cargo new --lib api
Enter fullscreen mode Exit fullscreen mode

Step 2: Create file structure

Under the api/src directory, create query and mutation subdirectories, and establish corresponding modules and files within them. The final directory structure is as follows:

.
├── Cargo.toml
└── src
    ├── lib.rs
    ├── mutation
    │   ├── mod.rs
    │   └── user.rs
    └── query
        ├── mod.rs
        └── user.rs
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure Cargo.toml file

Edit the api/Cargo.toml file and add dependencies:

[package]
name = "api"
version = "0.1.0"
edition = "2021"

[dependencies]
service = { path = "../service" } // Introduce the service layer library
entity = { path = "../entity" } // Introduce the entity layer library
async-graphql = { version = "7.0.2", features = ["chrono"] }
sea-orm = { version = "0.12.14", features = [
    "sqlx-postgres",
    "runtime-tokio-native-tls",
]}
Enter fullscreen mode Exit fullscreen mode

Step 4: Write query-related APIs

In the query/user.rs file, define the UserQuery struct and implement it as a GraphQL query object:

// query/user.rs
use async_graphql::*;
use service::user::UserServer;

#[derive(Default)]
pub struct UserQuery;

#[Object]
impl UserQuery {
    pub async fn get_users(&self, ctx: &Context<'_>) -> Result<()> {
        let db = ctx.data()?;
        let users = UserServer::get_all_user(db).await?;
        Ok(users)
    }
}

Enter fullscreen mode Exit fullscreen mode

In the query/mod.rs, import and merge the query object:

// query/mod.rs
use async_graphql::MergedObject;

use self::user::UserQuery;
mod user;

#[derive(Default, MergedObject)]
pub struct Query(UserQuery);
Enter fullscreen mode Exit fullscreen mode

Step 5: Write mutation-related APIs

In the mutation/user.rs file, define the UserMutation struct and implement it as a GraphQL mutation object, and also define input objects:

// mutation/user.rs
use async_graphql::*;
use entity::user;
use service::user::UserServer;

#[derive(InputObject)]
pub struct CreateUserInput {
    username: String,
    email: String,
    address: String,
    age: i32,
}

impl Into for CreateUserInput {
    fn into(self) -> user::Model {
        user::Model {
            id: 0,
            username: self.username,
            email: self.email,
            address: self.address,
            age: self.age,
        }
    }
}

#[derive(InputObject)]
pub struct UpdateUserInput {
    id: i32,
    email: String,
    address: String,
    age: i32,
}

impl Into for UpdateUserInput {
    fn into(self) -> user::Model {
        user::Model {
            id: self.id,
            username: "".to_string(),
            email: self.email,
            address: self.address,
            age: self.age,
        }
    }
}

#[derive(Default)]
pub struct UserMutation;

#[Object]
impl UserMutation {
    // Create a user
    pub async fn create_user(
        &self,
        ctx: &Context<'_>,
        input: CreateUserInput,
    ) -> Result<()> {
        let db = ctx.data()?;
        let res = UserServer::create_user(db, input.into()).await?;
        Ok(res)
    }

    // ...
}

// mutation/mod.rs
use async_graphql::MergedObject;

use self::user::UserMutation;
mod user;

#[derive(MergedObject, Default)]
pub struct Mutation(UserMutation);
Enter fullscreen mode Exit fullscreen mode

Step 6: Build the GraphQL Schema

In the src/lib.rs file, import the query and mutation modules, and define a function to build the GraphQL schema:

// src/lib.rs
mod mutation;
mod query;
use async_graphql::*;
use mutation::Mutation;
use query::Query;
use sea_orm::DbConn;

pub fn build_schema(db: DbConn) -> Schema {
    Schema::build(Query::default(), Mutation::default(), EmptySubscription)
        .data(db)
        .finish()
}
Enter fullscreen mode Exit fullscreen mode

Now you have successfully built user query and mutation-related GraphQL API interfaces based on async-graphql and sea-orm. In practice, you can integrate these interfaces into your application server to provide services as needed.

  1. Deploy the application using Shuttle

Step 1: Integrate the API package in the main program

First, in the src/main.rs file under the project root directory, add the previously created api package as a dependency, and use the methods provided by the api to build the GraphQL schema in the main function.

// Introduce necessary modules and packages
use async_graphql::{http::GraphiQLSource, *};
use async_graphql_axum::GraphQL;
use axum::{
    response::{Html, IntoResponse},
    routing::get,
    Router,
};
use migration::MigratorTrait;
use sea_orm::SqlxPostgresConnector;
use api; // Introduce the api library

async fn graphql() -> impl IntoResponse {
    Html(GraphiQLSource::build().endpoint("/graphql").finish())
}

async fn hello_world() -> &'static str {
    "Hello, world!"
}

#[shuttle_runtime::main]
async fn main(#[shuttle_shared_db::Postgres] pool: sqlx::PgPool) -> shuttle_axum::ShuttleAxum {
    let coon = SqlxPostgresConnector::from_sqlx_postgres_pool(pool);
    migration::Migrator::up(&coon, None).await.unwrap();
    // ...
}

Enter fullscreen mode Exit fullscreen mode

Step 2: Local run and test

Run the application locally to verify that the interfaces are working properly:

cargo shuttle run
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:3000/graphql to view and test the GraphQL API interfaces, and check the welcome page at http://localhost:3000/. Upon success, you should see the GraphiQL page as shown below:

Step 3: Deploy to Shuttle

Idle Projects - Shuttle

Start a new project

Start a never-idle new project in the Shuttle environment (set --idle-minutes to 0):

cargo shuttle start --idle-minutes 0
Enter fullscreen mode Exit fullscreen mode

Deploy the application

Use the cargo shuttle deploy command to deploy the application to the Shuttle platform:

cargo shuttle deploy
Enter fullscreen mode Exit fullscreen mode

After successful deployment, you will see success messages similar to the following in the console:

Your application has now been successfully deployed to the Shuttle platform and can be accessed and managed as needed. Please note that actual deployment may require specific configurations based on the Shuttle environment.

Top comments (2)

Collapse
 
agaviria profile image
Alejandro

Thanks for the post. Did you run into this axum error? I'm running axum v0.7.5

error[E0658]: `#[diagnostic]` attribute name space is experimental
   --> /home/alex/.cargo/registry/src/index.crates.io-6f17d22bba15001f/axum-0.7.5/src/handler/mod.rs:130:5
    |
130 |     diagnostic::on_unimplemented(
    |     ^^^^^^^^^^
    |
    = note: see issue #111996 <https://github.com/rust-lang/rust/issues/111996> for more information
    = help: add `#![feature(diagnostic_namespace)]` to the crate attributes to enable

For more information about this error, try `rustc --explain E0658`.
error: could not compile `axum` (lib) due to previous error
Enter fullscreen mode Exit fullscreen mode
Collapse
 
yexiyue profile image
叶师傅

Sorry, I didn't encounter this error