Next.JS环境搭建,对接Rust的RESTful API
我来一步步教你在 VS Code 里搭建 Next.js(基于 TypeScript)项目环境,并创建一个可以直接运行的项目。
🧩 一、安装必要环境
1. 安装 Node.js
Next.js 依赖 Node 环境。
-
进入官网下载安装最新版 LTS(推荐):
👉 https://nodejs.org/ -
安装后验证:
node -v npm -v
✅ Node.js ≥ 18.17.0 或更高版本,Next.js 14+ 都没问题。
2. 安装 VS Code 插件
打开 VS Code,安装以下扩展(在扩展市场搜索):
-
ES7+ React/Redux/React-Native snippets(快速生成组件模板)
-
Prettier - Code formatter(代码格式化)
-
Tailwind CSS IntelliSense(若你用 Tailwind)
-
TypeScript React (tsx)(语法高亮)
🚀 二、创建 Next.js 项目
在你想放项目的目录打开终端(或用 VS Code 自带终端):
方式 1:使用官方脚手架(推荐)
npx create-next-app@latest my-next-app
接下来会出现交互式选项:
✔ 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? … @/*
⚙️ 如果你只是要最基础项目,也可以:
npx create-next-app my-next-app --typescript --use-npm
🧱 三、进入项目并运行
cd my-next-app
npm run dev
浏览器打开:
👉 http://localhost:3000
如果看到 “Welcome to Next.js!” 的页面,就说明环境搭建成功 🎉
🧰 四、VS Code 运行配置(可选)
如果你想直接用 VS Code F5 启动项目:
-
打开
.vscode/launch.json
(若没有则新建) -
填入以下内容:
{"version": "0.2.0","configurations": [{"name": "Next.js App","type": "node","request": "launch","runtimeExecutable": "npm","runtimeArgs": ["run", "dev"],"port": 3000,"console": "integratedTerminal"}]
}
然后点击 “运行和调试 (Run and Debug)” ▶️ 即可直接启动。
🧩 五、项目结构简介(App Router 模式)
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
🧑💻 六、下一步建议
任务 | 说明 |
---|---|
✅ 运行 npm run build | 构建生产版本 |
✅ 运行 npm start | 启动生产服务器 |
⚙️ 学习 pages/ vs app/ | App Router 是新标准 |
💅 添加 Tailwind CSS | 用于快速样式开发 |
🔗 连接 API Routes | 在 /app/api 下写后端接口 |
Next.js 前端项目 + Rust REST API 后端 是一个非常干净、高性能的组合。
我来详细解释一下架构思路、跨语言通信方式和具体实现步骤。
🧱 一、总体架构
[Next.js 14+ (前端)]↓ HTTP 请求 (fetch / axios)
[ Rust Web 后端 (Actix / Axum / Rocket) ]↓ 数据层
[ 数据库: PostgreSQL / MySQL / SQLite 等 ]
Next.js 用来做前端 UI 和 SSR(服务端渲染),
Rust 用来提供高性能的 RESTful API(处理业务逻辑、数据库、AI推理等)。
🚀 二、Rust后端项目创建
下面给你一个完整可运行的示例:演示如何 创建 Rust 项目 → 连接 MySQL → 提供 RESTful API(CRUD)。示例使用 axum 作为 web 框架,sqlx 作为 MySQL 客户端(异步连接池),并使用 tokio 作为运行时。
我会给出:
-
创建项目与依赖(
Cargo.toml
)。 -
数据库建表 SQL。
-
完整代码(
src/main.rs
+ 辅助模块)。 -
环境变量配置与运行示例(curl 请求)。
1. 新建项目
在终端里:
cargo new rust_mysql_api --bin
cd rust_mysql_api
2. Cargo.toml
把 Cargo.toml
换成下面内容(或在原有基础上添加依赖):
[package]
name = "rust_mysql_api"
version = "0.1.0"
edition = "2021"[dependencies]
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"] }
说明:
sqlx
带macros
会在编译时为query!
等宏做检查(可选)。如果你不想用 macros,可以移除macros
特性并使用动态查询。
3. MySQL 建表(示例)
在你的 MySQL 中建一个简单的 users
表(示例):
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
);
4. 环境变量
在项目根目录创建 .env
(示例):
DATABASE_URL=mysql://username:password@127.0.0.1:3306/rust_api
BIND_ADDR=127.0.0.1:3000
把 username
/password
/host/port/dbname 改成你的。
5. 完整代码:src/main.rs
将 src/main.rs
替换为下面内容(包含 CRUD 的处理函数):
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>;#[derive(Clone)]
struct AppState {pool: Arc<DBPool>,
}#[derive(Debug, Serialize, Deserialize, FromRow)]
struct User {id: i32,name: String,email: String,created_at: chrono::NaiveDateTime,
}#[derive(Debug, Serialize, Deserialize)]
struct NewUser {name: String,email: String,
}#[derive(Debug, Serialize, Deserialize)]
struct UpdateUser {name: Option<String>,email: Option<String>,
}#[derive(Error, Debug)]
enum ApiError {#[error("Database error: {0}")]Db(#[from] sqlx::Error),#[error("Not found")]NotFound,
}impl IntoResponse for ApiError {fn into_response(self) -> axum::response::Response {match &self {ApiError::Db(e) => {let body = serde_json::json!({"error": format!("db error: {}", e)});(StatusCode::INTERNAL_SERVER_ERROR, Json(body)).into_response()}ApiError::NotFound => {let body = serde_json::json!({"error": "not found"});(StatusCode::NOT_FOUND, Json(body)).into_response()}}}
}#[tokio::main]
async fn main() -> Result<(), anyhow::Error> {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 poollet pool = MySqlPoolOptions::new().max_connections(5).connect(&database_url).await?;let state = AppState { pool: Arc::new(pool) };// routerlet 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(&addr).serve(app.into_make_service()).await?;Ok(())
}// GET /users
async fn list_users(State(state): State<AppState>) -> Result<Json<Vec<User>>, ApiError> {let rows = sqlx::query_as::<_, User>("SELECT id, name, email, created_at FROM users ORDER BY id").fetch_all(&*state.pool).await?;Ok(Json(rows))
}// GET /users/:id
async fn get_user(Path(id): Path<i32>, State(state): State<AppState>) -> Result<Json<User>, ApiError> {let user = sqlx::query_as::<_, User>("SELECT id, name, email, created_at FROM users WHERE id = ?").bind(id).fetch_optional(&*state.pool).await?;match user {Some(u) => Ok(Json(u)),None => Err(ApiError::NotFound),}
}// POST /users body: { "name": "...", "email": "..." }
async fn create_user(State(state): State<AppState>, Json(payload): Json<NewUser>) -> Result<(StatusCode, Json<User>), ApiError> {let rec = sqlx::query("INSERT INTO users (name, email) VALUES (?, ?)").bind(&payload.name).bind(&payload.email).execute(&*state.pool).await?;// 获取插入 idlet last_id = rec.last_insert_id() as i32;let user = sqlx::query_as::<_, User>("SELECT id, name, email, created_at FROM users WHERE id = ?").bind(last_id).fetch_one(&*state.pool).await?;Ok((StatusCode::CREATED, Json(user)))
}// PUT /users/:id body: { "name": optional, "email": optional }
async fn update_user(Path(id): Path<i32>, State(state): State<AppState>, Json(payload): Json<UpdateUser>) -> Result<Json<User>, ApiError> {// 获取现有数据(确保存在)let existing = sqlx::query_as::<_, User>("SELECT id, name, email, created_at FROM users WHERE id = ?").bind(id).fetch_optional(&*state.pool).await?;let existing = match existing {Some(u) => u,None => return Err(ApiError::NotFound),};let new_name = payload.name.as_ref().unwrap_or(&existing.name);let new_email = payload.email.as_ref().unwrap_or(&existing.email);sqlx::query("UPDATE users SET name = ?, email = ? WHERE id = ?").bind(new_name).bind(new_email).bind(id).execute(&*state.pool).await?;let user = sqlx::query_as::<_, User>("SELECT id, name, email, created_at FROM users WHERE id = ?").bind(id).fetch_one(&*state.pool).await?;Ok(Json(user))
}// DELETE /users/:id
async fn delete_user(Path(id): Path<i32>, State(state): State<AppState>) -> Result<StatusCode, ApiError> {let res = sqlx::query("DELETE FROM users WHERE id = ?").bind(id).execute(&*state.pool).await?;if res.rows_affected() == 0 {Err(ApiError::NotFound)} else {Ok(StatusCode::NO_CONTENT)}
}
说明:
使用
axum::State
(这里是State<AppState>
)传递 MySQL 池。
sqlx::query_as::<_, User>(...)
+FromRow
自动把行映射为User
。错误统一用
ApiError
转成 HTTP 响应。
6. 运行项目
在项目根目录:
# 加载 .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
启动后会监听 127.0.0.1:3000
(或 .env
中的 BIND_ADDR
)。
7. 测试 API(curl 示例)
-
创建用户:
curl -X POST http://127.0.0.1:3000/users -H "Content-Type: application/json" \-d '{"name":"Alice","email":"alice@example.com"}'
-
列表:
curl http://127.0.0.1:3000/users
-
详情:
curl http://127.0.0.1:3000/users/1
-
更新:
curl -X PUT http://127.0.0.1:3000/users/1 -H "Content-Type: application/json" \-d '{"name":"Alice Updated"}'
-
删除:
curl -X DELETE http://127.0.0.1:3000/users/1
8. 额外建议与注意事项
-
连接池配置:
max_connections(5)
只是示例,按你应用并发调整。 -
迁移管理:生产推荐使用迁移工具(如
sqlx
的sqlx-cli
或diesel_cli
、refinery
)来管理数据库 schema。sqlx
有sqlx migrate
功能。 -
事务:如果涉及多表或复合操作,使用
pool.begin()
获取事务并commit()
/rollback()
。 -
验证:输入需要校验(email 格式、长度等),可用
validator
crate。 -
安全:别把明文密码写死到代码或提交到 git,使用环境变量或 secret 管理。
-
日志与监控:可加
tracing
/tower-http
中间件记录请求/错误。 -
编译提示:
sqlx
的macros
可能需要DATABASE_URL
在编译时可访问(用于编译时检查),若造成编译问题,可在Cargo.toml
移除macros
特性或给sqlx
提供离线配置。