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

news/2025/11/18 18:42:05/文章来源:https://www.cnblogs.com/yangykaifa/p/19238906

我来一步步教你在 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 启动项目

  1. 打开 .vscode/launch.json(若没有则新建)

  2. 填入以下内容:

{"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 作为运行时。

我会给出:

  1. 创建项目与依赖(Cargo.toml)。

  2. 数据库建表 SQL。

  3. 完整代码(src/main.rs + 辅助模块)。

  4. 环境变量配置与运行示例(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"] }

说明:sqlxmacros 会在编译时为 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;
#[derive(Clone)]
struct AppState {pool: Arc,
}
#[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,email: Option,
}
#[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) -> Result>, 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, State(state): State) -> Result, 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, Json(payload): Json) -> Result<(StatusCode, Json), 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, State(state): State, Json(payload): Json) -> Result, 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, State(state): State) -> Result {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. 额外建议与注意事项

  1. 连接池配置max_connections(5) 只是示例,按你应用并发调整。

  2. 迁移管理:生产推荐使用迁移工具(如 sqlxsqlx-clidiesel_clirefinery)来管理数据库 schema。sqlxsqlx migrate 功能。

  3. 事务:如果涉及多表或复合操作,使用 pool.begin() 获取事务并 commit()/rollback()

  4. 验证:输入需要校验(email 格式、长度等),可用 validator crate。

  5. 安全:别把明文密码写死到代码或提交到 git,使用环境变量或 secret 管理。

  6. 日志与监控:可加 tracing / tower-http 中间件记录请求/错误。

  7. 编译提示sqlxmacros 可能需要 DATABASE_URL 在编译时可访问(用于编译时检查),若造成编译问题,可在 Cargo.toml 移除 macros 特性或给 sqlx 提供离线配置。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/969237.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P14510 夜里亦始终想念着你 miss 题解

验题人题解。 观察所有 \(\tt 0\) 的位置,容易发现 \(\forall i\),从左到右第 \(i\) 个 \(\tt 0\) 所在位置的奇偶性是固定的。且任意一个这样的棋盘都能通过题面中的操作得到。 考察操作的可逆性,容易想到令每个 \…

2025年11月高温轴承工厂排行榜,高温轴承公司推荐,耐高温轴承供应厂家,耐高温轴承源头厂家-骄铭轴承

2025年11月高温轴承工厂排行榜,高温轴承公司推荐,耐高温轴承供应厂家,耐高温轴承源头厂家-骄铭轴承2025年11月高温轴承工厂排行榜,高温轴承公司推荐,耐高温轴承供应厂家,耐高温轴承源头厂家-骄铭轴承在工业生产的…

B4185 [中山市赛 2024/科大国创杯小学组 2023] 倍数子串/子串 题解

奥数题。 一个数字是五的倍数的话,这个数末尾一定是零或五,如果是四的倍数的话,末尾两位一定是四的倍数,我们可以枚举每一个数作为末尾,如果该数的这一位和上一位组成的十位数为四的倍数,那么以这两个数为末尾的…

20251117 - Manacher

前言 怎么又有 ABB 啊,连考三次,罚时吃饱了!Manacher,俗称马拉车,是一个可以线性的求出一个串的最长回文子串。 如何求解最长回文子串呢? 马拉车方法零: 首先枚举字符串的左端点和右端点,在判断区间是否是回文…

Prufer序列和Cayley定理

OI Wiki讲的还挺好的。 标号无根树 其实就是把一棵无根树标一下号,让每一个节点是唯一的。 双射 通俗来讲就是一个 \(A\) 可以对应到一个唯一确定的 \(B\),反之也是如此。 Prufer序列 构造 Prufer 序列的构建方法如下…

完整教程:PB级数据洪流下的抉择:从大数据架构师视角,深度解析时序数据库选型与性能优化(聚焦Apache IoTDB)

完整教程:PB级数据洪流下的抉择:从大数据架构师视角,深度解析时序数据库选型与性能优化(聚焦Apache IoTDB)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; dis…

软件工程学习日志2025.11.18

嗨,各位小伙伴,今天开始我想开一个新坑,用几天时间,聊聊如何为一个非常特殊的行业——医疗器械——设计一套进销存管理系统。这可不是普通的商品管理,它关乎人的健康与安全,所以咱们设计的系统,也得有点“医者仁…

11.14 事务的四大特性 并发事务问题

原子性 一致性 隔离性 持久性 脏读:一个事务读到另一个事务的未提交数据 不可重复读:一个事务读取同一条记录,数据不同 幻读:一个事务在读是未发现的数据却在插入时出现

SQL逻辑查询语句执行顺序

details summary::before { content: "▶"; position: absolute; left: 8px; transition: all 0.3s ease } details[open] summary::before { transform: rotate(90deg) } summary { list-style: none; curs…

解码死锁的产生与解决

死锁 死锁是多线程 / 多进程并发编程中常见的严重问题,指两个或多个线程 / 进程因互相争抢资源而陷入无限等待状态,若无外力干预将永久无法继续执行。其本质是资源分配与调度不当导致的 “资源僵局”,典型现实类比是…

uniapp的rich-text在渲染长数字与长字母时不换行

在rich-text的容器上新增 容器 容器 容器 不是rich-text组件本身word-break: break-all; word-wrap: break-word; 出现此问题是因为浏览器将其视为了一个单词所以未进行换行

头部厂商易路AI HR实战解析:从人海战术到智能闭环的合规跃迁

在连锁零售与餐饮行业,食品安全是企业的生命线,而员工健康证管理则是保障这条生命线的第一道防线。对于拥有数千家门店、数万名员工的行业巨头而言,健康证管理绝非一项简单的事务性工作,它关乎企业合规的基石、品牌…

【微信小程序 + 登录流程】微信小程序授权登录完整流程,一篇搞定!(含代码实现) - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

linux auto

您提到的“Linux auto”可能是指“Linux 自动化”或“Linux 自动化工具”,我将为您介绍一些常见的 Linux 自动化工具和命令,帮助您实现自动化任务。一、Linux 自动化工具 1. Ansible功能:基于 YAML 的自动化工具,支…

记录相关的操作

details summary::before { content: "▶"; position: absolute; left: 8px; transition: all 0.3s ease } details[open] summary::before { transform: rotate(90deg) } summary { list-style: none; curs…

P9846 [ICPC 2021 Nanjing R] Paimons Tree

派蒙题。 首先发现填完权值后的直接必定是原树直径中的某一条,但是无法确定是其中哪一条,所以这个发现是没用的/tx。 发现按照题目的染色方法,每个时刻染色的边必然为一条路径。 观察到 \(n\) 的范围很小,首先考虑…

linux audio

在 Linux 系统中,音频相关操作可以通过多种方式实现,包括使用内核音频驱动、 ALSA(Advanced Linux Sound Architecture)或 JACK 音频服务器等。以下是一些常见的 Linux 音频操作命令和脚本示例,适用于大多数 Linu…

不同方向的箭头符号

1. 基础方向箭头 ▶ 右箭头 (U+25B6) ◀ 左箭头 (U+25C0) ▼ 下箭头 (U+25BC) ▲ 上箭头 (U+25B2)2. 实心箭头 🔺 实心上三角 (U+1F53A) 🔻 实心下三角 (U+1F53B) ⏩ 快进右箭头 (U+23E9) ⏪ 快退左箭头 (U+23EA)3…

11.13 表子查询 内连接补充 事务

SELECT xx.*FROM(SELECT。。。。。; 内连接出来的是笛卡尔积,必须用条件消除 事务就是捆绑的操作集合 事务操作 SELECT @@autocommit ==1,自动提交 SET @@zutocommit=0; 手动提交: commint; 执行失败要回滚事务 ro…

Elasticsearch 7.17 集群添加账号密码

Elasticsearch 7.17 集群添加账号密码1. 环境信息 1.1 主机列表IP 主机名 操作系统 JAVA_HOME10.0.0.22 SY-AFP-ES01 Red Hat Enterprise Linux release 8.6 (Ootpa) /opt/app/middles/jdk1.8.0_47110.0.0.23 SY-AFP-E…