Rust + Axum + Shuttle:零运维部署 REST API 完整指南

作者:API传播员 · 2025-11-03 · 阅读时间:6分钟
本文详细介绍了如何使用Rust语言结合Axum框架和Shuttle平台构建REST API服务。教程包含从项目初始化、数据库集成(PostgreSQL/SQLx)、路由处理到最终部署的完整流程,提供了CRUD操作、请求参数处理和防SQL注入等实用代码示例,适合想学习Rust后端开发的开发者。

一、为什么选择 Shuttle + Axum?

  • 零运维cargo shuttle deploy 自动构建、容器化、上线
  • Rust 原生:Axum 类型安全、异步高性能
  • 内置数据库:PostgreSQL 连接池一行注解即可拥有
  • 成本友好:免费 tier 足够开发/演示,按量付费

💡 AI 助攻
想自动生成带注释的 Cargo.toml?用「代码生成」提示词,30 秒即可拿到模板,再交给「代码优化」砍掉冗余特性,编译速度提升 20%!


二、技术栈与版本

组件 用途 版本
shuttle-service 零运维部署平台 0.46.x
axum Web 框架 0.7.x
sqlx 异步 PostgreSQL 驱动 0.7.x
serde JSON 序列化 1.0
tokio 异步运行时 1.x

三、项目初始化与结构

# 安装 Shuttle CLI
cargo install cargo-shuttle

# 创建项目
cargo shuttle init --name shuttle-example-axum
cd shuttle-example-axum

骨架:

src/
├── main.rs # 入口 + 路由
├── db.rs # 数据库连接
├── models.rs # 实体与 JSON
└── migrations.sql # 建表脚本

四、数据库与迁移:一行注解搞定

① 注解获取连接池

use shuttle_shared_db::Postgres;
use sqlx::PgPool;

#[shuttle_runtime::main]
async fn axum(
    #[Postgres] pool: PgPool,   // ← 自动注入
) -> shuttle_axum::ShuttleAxum {
    // 运行迁移
    sqlx::migrate!()
        .run(&pool)
        .await
        .expect("migrate failed");

    let state = AppState { db: pool };
    Ok(router(state).into())
}

② 迁移文件 migrations.sql

CREATE TABLE IF NOT EXISTS records (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL
);

🔍 AI 审查
把「SQL 注入风险」提交评审?「代码审查助手」可自动检查参数绑定、拼接语句,提前发现 80% 潜在 Bug!


五、模型与 JSON:自动映射

// src/models.rs
use serde::{Deserialize, Serialize};
use sqlx::FromRow;

#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct Record {
    pub id: i32,
    pub name: String,
    pub email: String,
}

#[derive(Debug, Deserialize)]
pub struct CreateRecord {
    pub name: String,
    pub email: String,
}

#[derive(Debug, Deserialize)]
pub struct UpdateRecord {
    pub name: Option<String>,
    pub email: Option<String>,
}

六、路由与业务逻辑

① 共享状态

// src/main.rs
use axum::{
    extract::{Path, State, Json},
    http::StatusCode,
    response::IntoResponse,
    routing::{get, post, put, delete},
    Router,
};
use sqlx::PgPool;

#[derive(Clone)]
struct AppState {
    db: PgPool,
}

async fn create_record(
    State(state): State<AppState>,
    Json(payload): Json<CreateRecord>,
) -> impl IntoResponse {
    let rec = sqlx::query_as!(
        Record,
        "INSERT INTO records (name, email) VALUES ($1, $2) RETURNING *",
        payload.name, payload.email
    )
    .fetch_one(&state.db)
    .await
    .unwrap();
    (StatusCode::CREATED, Json(rec))
}

② 动态路径与参数绑定

async fn get_record_by_id(
    State(state): State<AppState>,
    Path(id): Path<i32>,
) -> impl IntoResponse {
    let rec = sqlx::query_as!(
        Record,
        "SELECT * FROM records WHERE id = $1",
        id
    )
    .fetch_optional(&state.db)
    .await
    .unwrap();

    match rec {
        Some(r) => (StatusCode::OK, Json(r)),
        None => (StatusCode::NOT_FOUND, Json("")),
    }
}

③ 部分更新(PATCH)

async fn update_record(
    State(state): State<AppState>,
    Path(id): Path<i32>,
    Json(payload): Json<UpdateRecord>,
) -> impl IntoResponse {
    // 动态构建 SET 子句
    let mut sql = String::from("UPDATE records SET ");
    let mut args: Vec<String> = vec![];
    if let Some(name) = payload.name {
        sql.push_str("name = $1,");
        args.push(name);
    }
    if let Some(email) = payload.email {
        sql.push_str("email = $2,");
        args.push(email);
    }
    sql.truncate(sql.len() - 1); // 去掉末尾逗号
    sql.push_str(" WHERE id = $3 RETURNING *");

    let rec = sqlx::query_as::<_, Record>(&sql)
        .bind(args.get(0).unwrap_or(&"".to_string()))
        .bind(args.get(1).unwrap_or(&"".to_string()))
        .bind(id)
        .fetch_one(&state.db)
        .await
        .unwrap();
    (StatusCode::OK, Json(rec))
}

AI 补救
把「动态 SQL 拼接」写进注释太麻烦?用「代码文档生成器」提示词,自动在函数头部生成标准注释,提醒后续接入 sqlx-query-builder,文档一键达标!


七、路由汇总

fn router(state: AppState) -> Router {
    Router::new()
        .route("/records", post(create_record).get(list_records))
        .route("/records/:id", get(get_record_by_id).put(update_record).delete(delete_record))
        .with_state(state)
}

八、部署与测试

① 本地运行

cargo shuttle run
# 默认端口 8000

② 一键部署

cargo shuttle deploy --allow-dirty
# 自动生成域名:https://shuttle-example-axum.shuttleapp.rs

③ 快速验证

# 新增
curl -X POST https://<your-domain>.shuttleapp.rs/records \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice","email":"alice@example.com"}'

# 查询
curl https://<your-domain>.shuttleapp.rs/records/1

九、扩展方向

方向 工具/库
自定义错误 thiserror + axum::response::IntoResponse
测试 testcontainers + axum-test-helper
分页 sqlx-query-builder + limit/offset
JWT 认证 jsonwebtoken + axum-extra
OpenAPI utoipa + swagger-ui

十、总结与下一步

通过本教程,你已掌握:

  • Shuttle 零运维部署流程
  • Axum 异步路由与状态共享
  • SQLx 编译时安全查询
  • 动态 SQL 构建与错误处理

下一步可添加认证、分页、OpenAPI 文档等功能,打造生产级后端。祝编码愉快!

今晚就 push 到 GitHub,明天就能用 Shuttle 一键上线!🚀


原文链接: https://www.shuttle.dev/blog/2024/01/31/write-a-rest-api-rust