段盛全 發表於 2025-11-18 18:40:00

详细介绍:Next.JS环境搭建,对接Rust的RESTful API

<style>pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; line-height: 1.6 !important; padding: 16px !important; margin: 16px 0 !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; tab-size: 4 !important; -moz-tab-size: 4 !important; max-width: 100% !important; box-sizing: border-box !important }
code { font-family: "Consolas", "Monaco", "Courier New", monospace !important; font-size: 14px !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; overflow-wrap: normal !important; display: inline !important; background: rgba(0, 0, 0, 0) !important; border: none !important; padding: 0 !important; margin: 0 !important; line-height: inherit !important }
pre code { background: rgba(0, 0, 0, 0) !important; border: 0 !important; border-radius: 0 !important; display: block !important; line-height: 1.6 !important; margin: 0 !important; max-width: none !important; overflow: visible !important; padding: 0 !important; white-space: pre !important; word-wrap: normal !important; word-break: normal !important; color: inherit !important }
.token.comment, .token.prolog, .token.doctype, .token.cdata { color: rgba(112, 128, 144, 1) !important; font-style: italic !important }
.token.punctuation { color: rgba(153, 153, 153, 1) !important }
.token.atrule, .token.attr-value, .token.keyword { color: rgba(0, 119, 170, 1) !important; font-weight: bold !important }
.token.function, .token.class-name { color: rgba(221, 74, 104, 1) !important; font-weight: bold !important }
.token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: rgba(102, 153, 0, 1) !important }
.token.property, .token.tag, .token.boolean, .token.number, .token.constant, .token.symbol, .token.deleted { color: rgba(153, 0, 85, 1) !important }
.cnblogs-markdown pre, .cnblogs-post-body pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; background-color: rgba(248, 248, 248, 1) !important; border: 1px solid rgba(225, 228, 232, 1) !important; border-radius: 6px !important; padding: 16px !important; margin: 16px 0 !important }
pre, pre, pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important }</style>
      <div class="htmledit_views atom-one-light" id="content_views"><p>我来一步步教你在 <strong>VS Code</strong> 里搭建 <strong>Next.js(基于 TypeScript)项目环境</strong>,并创建一个可以直接运行的项目。</p><hr><h3> 一、安装必要环境</h3><h4>1. 安装 Node.js</h4><p>Next.js 依赖 Node 环境。</p><ul><li><p>进入官网下载安装最新版 LTS(推荐):<br>https://nodejs.org/</p></li><li><p>安装后验证:</p> <pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>node -v
npm -v</code></pre> </li></ul><blockquote><p>✅ Node.js ≥ 18.17.0 或更高版本,Next.js 14+ 都没问题。</p></blockquote><hr><h4>2. 安装 VS Code 插件</h4><p>打开 VS Code,安装以下扩展(在扩展市场搜索):</p><ul><li><p><strong>ES7+ React/Redux/React-Native snippets</strong>(快速生成组件模板)</p></li><li><p><strong>Prettier - Code formatter</strong>(代码格式化)</p></li><li><p><strong>Tailwind CSS IntelliSense</strong>(若你用 Tailwind)</p></li><li><p><strong>TypeScript React (tsx)</strong>(语法高亮)</p></li></ul><hr><h3> 二、创建 Next.js 项目</h3><p>在你想放项目的目录打开终端(或用 VS Code 自带终端):</p><h4>方式 1:使用官方脚手架(推荐)</h4>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>npx create-next-app@latest my-next-app</code></pre>
<p>接下来会出现交互式选项:</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>✔ What is your project named? … my-next-app
✔ Would you like to use TypeScript? … Yes
✔ Would you like to use ESLint? … Yes
✔ Would you like to use Tailwind CSS? … Yes/No(可选)
✔ Would you like to use src/ directory? … Yes(推荐)
✔ Would you like to use App Router? (recommended) … Yes ✅
✔ Would you like to customize the default import alias (@/*)? … Yes
✔ What import alias would you like configured? … @/*</code></pre>
<blockquote><p>⚙️ 如果你只是要最基础项目,也可以:</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>npx create-next-app my-next-app --typescript --use-npm</code></pre>
</blockquote><hr><h3> 三、进入项目并运行</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>cd my-next-app
npm run dev</code></pre>
<p>浏览器打开:<br>http://localhost:3000</p><p>如果看到 “Welcome to Next.js!” 的页面,就说明环境搭建成功 </p><hr><h3> 四、VS Code 运行配置(可选)</h3><p>如果你想直接用 <strong>VS Code F5 启动项目</strong>:</p><ol><li><p>打开 <code>.vscode/launch.json</code>(若没有则新建)</p></li><li><p>填入以下内容:</p></li></ol>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>{
"version": "0.2.0",
"configurations": [
    {
      "name": "Next.js App",
      "type": "node",
      "request": "launch",
      "runtimeExecutable": "npm",
      "runtimeArgs": ["run", "dev"],
      "port": 3000,
      "console": "integratedTerminal"
    }
]
}</code></pre>
<p>然后点击 “运行和调试 (Run and Debug)” ▶️ 即可直接启动。</p><hr><h3> 五、项目结构简介(App Router 模式)</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>my-next-app/
├── app/                  # 页面与路由目录
│   ├── page.tsx          # 首页
│   ├── layout.tsx      # 页面布局
│   └── globals.css       # 全局样式
├── public/               # 静态文件
├── next.config.mjs       # Next.js 配置
├── tsconfig.json         # TypeScript 配置
├── package.json
└── tailwind.config.ts    # 若启用 Tailwind</code></pre>
<hr><h3>‍ 六、下一步建议</h3><table><thead><tr><th>任务</th><th>说明</th></tr></thead><tbody><tr><td>✅ 运行 <code>npm run build</code></td><td>构建生产版本</td></tr><tr><td>✅ 运行 <code>npm start</code></td><td>启动生产服务器</td></tr><tr><td>⚙️ 学习 <code>pages/</code> vs <code>app/</code></td><td>App Router 是新标准</td></tr><tr><td> 添加 Tailwind CSS</td><td>用于快速样式开发</td></tr><tr><td> 连接 API Routes</td><td>在 <code>/app/api</code> 下写后端接口</td></tr></tbody></table><hr><p></p><p></p><p><strong>Next.js 前端项目 + Rust REST API 后端</strong> 是一个非常干净、高性能的组合。<br> 我来详细解释一下架构思路、跨语言通信方式和具体实现步骤。</p><hr><h3> 一、总体架构</h3>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>
↓ HTTP 请求 (fetch / axios)
[ Rust Web 后端 (Actix / Axum / Rocket) ]
↓ 数据层
[ 数据库: PostgreSQL / MySQL / SQLite 等 ]</code></pre>
<p>Next.js 用来做前端 UI 和 SSR(服务端渲染),<br> Rust 用来提供高性能的 RESTful API(处理业务逻辑、数据库、AI推理等)。</p><hr><h2 style="background-color: rgba(0, 0, 0, 0)"> 二、Rust后端项目创建</h2><p>下面给你一个<strong>完整可运行</strong>的示例:演示如何 <strong>创建 Rust 项目 → 连接 MySQL → 提供 RESTful API(CRUD)</strong>。示例使用 <strong>axum</strong> 作为 web 框架,<strong>sqlx</strong> 作为 MySQL 客户端(异步连接池),并使用 <strong>tokio</strong> 作为运行时。</p><p>我会给出:</p><ol><li><p>创建项目与依赖(<code>Cargo.toml</code>)。</p></li><li><p>数据库建表 SQL。</p></li><li><p>完整代码(<code>src/main.rs</code> + 辅助模块)。</p></li><li><p>环境变量配置与运行示例(curl 请求)。</p></li></ol><hr><h2>1. 新建项目</h2><p>在终端里:</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>cargo new rust_mysql_api --bin
cd rust_mysql_api</code></pre>
<h2>2. <code>Cargo.toml</code></h2><p>把 <code>Cargo.toml</code> 换成下面内容(或在原有基础上添加依赖):</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>
name = "rust_mysql_api"
version = "0.1.0"
edition = "2021"

tokio = { version = "1.36", features = ["rt-multi-thread", "macros"] }
axum = "0.7"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7", features = ["mysql", "runtime-tokio-native-tls", "macros"] }
tower = "0.4"
thiserror = "1.0"
dotenvy = "0.15"
uuid = { version = "1.4", features = ["v4"] }</code></pre>
<blockquote><p>说明:<code>sqlx</code> 带 <code>macros</code> 会在编译时为 <code>query!</code> 等宏做检查(可选)。如果你不想用 macros,可以移除 <code>macros</code> 特性并使用动态查询。</p></blockquote><h2>3. MySQL 建表(示例)</h2><p>在你的 MySQL 中建一个简单的 <code>users</code> 表(示例):</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>CREATE DATABASE IF NOT EXISTS rust_api DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE rust_api;
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(150) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);</code></pre>
<h2>4. 环境变量</h2><p>在项目根目录创建 <code>.env</code>(示例):</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>DATABASE_URL=mysql://username:password@127.0.0.1:3306/rust_api
BIND_ADDR=127.0.0.1:3000</code></pre>
<p>把 <code>username</code>/<code>password</code>/host/port/dbname 改成你的。</p><h2>5. 完整代码:<code>src/main.rs</code></h2><p>将 <code>src/main.rs</code> 替换为下面内容(包含 CRUD 的处理函数):</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>use axum::{
    extract::{Path, State},
    response::IntoResponse,
    routing::{get, post, put, delete},
    Json, Router,
};
use serde::{Deserialize, Serialize};
use sqlx::{mysql::MySqlPoolOptions, MySql, Pool, FromRow};
use std::{net::SocketAddr, sync::Arc};
use thiserror::Error;
use dotenvy::dotenv;
use std::env;
use axum::http::StatusCode;
type DBPool = Pool<mysql>;
#
struct AppState {
    pool: Arc<dbpool>,
}
#
struct User {
    id: i32,
    name: String,
    email: String,
    created_at: chrono::NaiveDateTime,
}
#
struct NewUser {
    name: String,
    email: String,
}
#
struct UpdateUser {
    name: Option<string>,
    email: Option<string>,
}
#
enum ApiError {
    #
    Db(# sqlx::Error),
    #
    NotFound,
}
impl IntoResponse for ApiError {
    fn into_response(self) -&gt; axum::response::Response {
      match &amp;self {
            ApiError::Db(e) =&gt; {
                let body = serde_json::json!({"error": format!("db error: {}", e)});
                (StatusCode::INTERNAL_SERVER_ERROR, Json(body)).into_response()
            }
            ApiError::NotFound =&gt; {
                let body = serde_json::json!({"error": "not found"});
                (StatusCode::NOT_FOUND, Json(body)).into_response()
            }
      }
    }
}
#
async fn main() -&gt; Result&lt;(), anyhow::Error&gt; {
    dotenv().ok();
    let database_url = env::var("DATABASE_URL")
      .expect("DATABASE_URL must be set in .env or environment");
    let bind_addr = env::var("BIND_ADDR").unwrap_or_else(|_| "127.0.0.1:3000".to_string());
    // create pool
    let pool = MySqlPoolOptions::new()
      .max_connections(5)
      .connect(&amp;database_url)
      .await?;
    let state = AppState { pool: Arc::new(pool) };
    // router
    let app = Router::new()
      .route("/users", get(list_users).post(create_user))
      .route("/users/:id", get(get_user).put(update_user).delete(delete_user))
      .with_state(state);
    let addr: SocketAddr = bind_addr.parse()?;
    println!("Listening on {}", addr);
    axum::Server::bind(&amp;addr)
      .serve(app.into_make_service())
      .await?;
    Ok(())
}
// GET /users
async fn list_users(State(state): State) -&gt; Result<json<vec<user>&gt;, ApiError&gt; {
    let rows = sqlx::query_as::&lt;_, User&gt;("SELECT id, name, email, created_at FROM users ORDER BY id")
      .fetch_all(&amp;*state.pool)
      .await?;
    Ok(Json(rows))
}
// GET /users/:id
async fn get_user(Path(id): Path<i32>, State(state): State) -&gt; Result<json<user>, ApiError&gt; {
    let user = sqlx::query_as::&lt;_, User&gt;("SELECT id, name, email, created_at FROM users WHERE id = ?")
      .bind(id)
      .fetch_optional(&amp;*state.pool)
      .await?;
    match user {
      Some(u) =&gt; Ok(Json(u)),
      None =&gt; Err(ApiError::NotFound),
    }
}
// POST /usersbody: { "name": "...", "email": "..." }
async fn create_user(State(state): State, Json(payload): Json<newuser>) -&gt; Result&lt;(StatusCode, Json<user>), ApiError&gt; {
    let rec = sqlx::query("INSERT INTO users (name, email) VALUES (?, ?)")
      .bind(&amp;payload.name)
      .bind(&amp;payload.email)
      .execute(&amp;*state.pool)
      .await?;
    // 获取插入 id
    let last_id = rec.last_insert_id() as i32;
    let user = sqlx::query_as::&lt;_, User&gt;("SELECT id, name, email, created_at FROM users WHERE id = ?")
      .bind(last_id)
      .fetch_one(&amp;*state.pool)
      .await?;
    Ok((StatusCode::CREATED, Json(user)))
}
// PUT /users/:idbody: { "name": optional, "email": optional }
async fn update_user(Path(id): Path<i32>, State(state): State, Json(payload): Json<updateuser>) -&gt; Result<json<user>, ApiError&gt; {
    // 获取现有数据(确保存在)
    let existing = sqlx::query_as::&lt;_, User&gt;("SELECT id, name, email, created_at FROM users WHERE id = ?")
      .bind(id)
      .fetch_optional(&amp;*state.pool)
      .await?;
    let existing = match existing {
      Some(u) =&gt; u,
      None =&gt; return Err(ApiError::NotFound),
    };
    let new_name = payload.name.as_ref().unwrap_or(&amp;existing.name);
    let new_email = payload.email.as_ref().unwrap_or(&amp;existing.email);
    sqlx::query("UPDATE users SET name = ?, email = ? WHERE id = ?")
      .bind(new_name)
      .bind(new_email)
      .bind(id)
      .execute(&amp;*state.pool)
      .await?;
    let user = sqlx::query_as::&lt;_, User&gt;("SELECT id, name, email, created_at FROM users WHERE id = ?")
      .bind(id)
      .fetch_one(&amp;*state.pool)
      .await?;
    Ok(Json(user))
}
// DELETE /users/:id
async fn delete_user(Path(id): Path<i32>, State(state): State) -&gt; Result<statuscode, apierror=""> {
    let res = sqlx::query("DELETE FROM users WHERE id = ?")
      .bind(id)
      .execute(&amp;*state.pool)
      .await?;
    if res.rows_affected() == 0 {
      Err(ApiError::NotFound)
    } else {
      Ok(StatusCode::NO_CONTENT)
    }
}</statuscode,></appstate></i32></json<user></updateuser></appstate></i32></user></newuser></appstate></json<user></appstate></i32></json<vec<user></appstate></string></string></dbpool></mysql></code></pre>
<blockquote><p>说明:</p><ul><li><p>使用 <code>axum::State</code>(这里是 <code>State&lt;AppState&gt;</code>)传递 MySQL 池。</p></li><li><p><code>sqlx::query_as::&lt;_, User&gt;(...)</code> + <code>FromRow</code> 自动把行映射为 <code>User</code>。</p></li><li><p>错误统一用 <code>ApiError</code> 转成 HTTP 响应。</p></li></ul></blockquote><h2>6. 运行项目</h2><p>在项目根目录:</p>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code># 加载 .env(如果你用的是 bash/zsh)
export DATABASE_URL="mysql://username:password@127.0.0.1:3306/rust_api"
export BIND_ADDR="127.0.0.1:3000"
cargo run</code></pre>
<p>启动后会监听 <code>127.0.0.1:3000</code>(或 <code>.env</code> 中的 <code>BIND_ADDR</code>)。</p><h2>7. 测试 API(curl 示例)</h2><ul><li><p>创建用户:</p></li></ul>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>curl -X POST http://127.0.0.1:3000/users -H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'</code></pre>
<ul><li><p>列表:</p></li></ul>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>curl http://127.0.0.1:3000/users</code></pre>
<ul><li><p>详情:</p></li></ul>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>curl http://127.0.0.1:3000/users/1</code></pre>
<ul><li><p>更新:</p></li></ul>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>curl -X PUT http://127.0.0.1:3000/users/1 -H "Content-Type: application/json" \
-d '{"name":"Alice Updated"}'</code></pre>
<ul><li><p>删除:</p></li></ul>
<pre style="white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important"><code>curl -X DELETE http://127.0.0.1:3000/users/1</code></pre>
<h2>8. 额外建议与注意事项</h2><ol><li><p><strong>连接池配置</strong>:<code>max_connections(5)</code> 只是示例,按你应用并发调整。</p></li><li><p><strong>迁移管理</strong>:生产推荐使用迁移工具(如 <code>sqlx</code> 的 <code>sqlx-cli</code> 或 <code>diesel_cli</code>、<code>refinery</code>)来管理数据库 schema。<code>sqlx</code> 有 <code>sqlx migrate</code> 功能。</p></li><li><p><strong>事务</strong>:如果涉及多表或复合操作,使用 <code>pool.begin()</code> 获取事务并 <code>commit()</code>/<code>rollback()</code>。</p></li><li><p><strong>验证</strong>:输入需要校验(email 格式、长度等),可用 <code>validator</code> crate。</p></li><li><p><strong>安全</strong>:别把明文密码写死到代码或提交到 git,使用环境变量或 secret 管理。</p></li><li><p><strong>日志与监控</strong>:可加 <code>tracing</code> / <code>tower-http</code> 中间件记录请求/错误。</p></li><li><p><strong>编译提示</strong>:<code>sqlx</code> 的 <code>macros</code> 可能需要 <code>DATABASE_URL</code> 在编译时可访问(用于编译时检查),若造成编译问题,可在 <code>Cargo.toml</code> 移除 <code>macros</code> 特性或给 <code>sqlx</code> 提供离线配置。</p></li></ol><hr><p></p></div><br><br>
来源:https://www.cnblogs.com/yangykaifa/p/19238906
頁: [1]
查看完整版本: 详细介绍:Next.JS环境搭建,对接Rust的RESTful API