难道你不想做一个既能上线又能“让老板点头”的全栈项目吗?
开篇语
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
前言😏
前言(先来杯咖啡,深呼吸)——
说实话,我对全栈开发既爱又恨:爱的是它把前端那点儿小聪明和后端那堆“真香”逻辑揉在一起,能把一个想法在 Web 上活生生地呈现;恨的是:要把整套从 UI 到数据库、再到容器化部署打磨好的那一刻,常常需要踩太多坑。本文就是给你——愿意实战、愿意思考、也愿意笑对错误日志的同路人——的一份深度路线图与实战样板。文章既讲原理(官方文档为基),也给实操代码示例,带着一点点个人情绪与吐槽(你会发现这些吐槽其实很疗愈 😊)。
本文目标:
- 用 React(前端) + Node.js/Express(后端)+ PostgreSQL(数据库)+ Docker(容器化)做一个小而完整的全栈示例;
- 解释关键设计决定,同时给出可复制的代码片段;
- 依据官方文档的最佳实践说明实现细节与注意事项。
目录(先铺路,后攀爬)
- 为什么选这套技术栈?(动机与权衡)
- 架构概览(组件与数据流)
- 环境准备(工具与版本建议)
- 后端:用 Express + Node.js 做 REST API(代码 + 解释)
- 数据库:PostgreSQL 模式设计与使用(DDL + 示例)
- 前端:React 构建单页应用(组件、状态、路由)
- 容器化:Dockerfile + docker-compose 一键启动
- 部署与安全(小心踩坑——CORS、密码、迁移)
- 性能、扩展与运维建议(从单机到集群)
- 总结与进阶路线(再说点不那么“广告”的感想)
1. 为什么选这套技术栈?(说人话的权衡)
技术选型常被问得像面试题:那你为什么不用 X 或 Y?我的回答是:每个项目有自己的成本/收益曲线。React 社区活跃、组件化能快速迭代;Node.js 在同语言(JavaScript)下覆盖前后端,让项目协同更顺滑;Express 简洁,便于快速构建 API;PostgreSQL 是成熟的关系型数据库,支持事务、复杂查询和扩展;Docker 则让“在我电脑能跑”这句神话变成现实。官方对这些技术的定位和指南,也是我们实践的底气来源。
2. 架构概览(把整体画出来,别只看代码)
- 前端(React SPA)负责展示与交互;
- 后端(Express API)负责认证、业务逻辑与数据库访问;
- 数据库(Postgres)负责持久化与事务;
- 容器(Docker)负责将服务打包并在任意环境运行。
数据流:用户 → 浏览器(React) → API(Express) → DB(Postgres) → API → 前端渲染。
这种分工让每一层都能独立扩展:当用户激增时,可以水平扩展后端与前端的静态CDN/缓存;当数据增长时,可以做读写分离与分区。
3. 环境准备(别忽视版本)
建议版本(能跑且稳定):
- Node.js:18.x 或以上。官方文档推荐在 LTS 或当前稳定版本上运行。
- npm / pnpm / yarn:任选其一,我个人偏好 pnpm(磁盘友好)。
- PostgreSQL:13/14/15 都 OK,尽量用官方文档对应的版本参考。
- Docker & docker-compose:最新稳定版。
本地开发环境准备(示例命令):
# 安装 Node(用 nvm 推荐)
nvm install 18
nvm use 18
# PostgreSQL(本地或 Docker)
# 推荐用 docker-compose 一键启动(稍后会给出)
4. 后端:Express + Node.js 做 REST API(一步步来)
下面给出一个完整的后端示例:支持用户注册/登录(JWT)和一个简单的 Todo API(CRUD)。我会在关键处插入解释与“为什么这么做”。
项目结构(简洁示例):
server/
├─ package.json
├─ src/
│ ├─ index.js
│ ├─ routes/
│ │ ├─ auth.js
│ │ └─ todos.js
│ ├─ controllers/
│ ├─ db/
│ │ └─ index.js
│ └─ middlewares/
└─ .env
package.json(核心依赖)
{
"name": "fullstack-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"express": "^4.18.2",
"pg": "^8.9.0",
"bcrypt": "^5.1.0",
"jsonwebtoken": "^9.0.0",
"dotenv": "^16.0.0",
"helmet": "^6.0.0",
"cors": "^2.8.5"
},
"devDependencies": {
"nodemon": "^2.0.22"
}
}
说明:使用
pg与 Postgres 对接;helmet提供安全 header;cors用来配置跨域;bcrypt哈希密码;jsonwebtoken做 JWT。以上组合兼顾可用性与社区成熟度(参见官方 Express 与 Node 文档)。
src/index.js(服务启动、全局中间件)
import express from 'express';
import dotenv from 'dotenv';
import helmet from 'helmet';
import cors from 'cors';
import authRoutes from './routes/auth.js';
import todosRoutes from './routes/todos.js';
dotenv.config();
const app = express();
app.use(helmet());
app.use(cors({ origin: process.env.FRONTEND_ORIGIN || 'http://localhost:3000' }));
app.use(express.json());
app.use('/api/auth', authRoutes);
app.use('/api/todos', todosRoutes);
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
src/db/index.js(Postgres 连接封装)
import pkg from 'pg';
const { Pool } = pkg;
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
export default {
query: (text, params) => pool.query(text, params),
pool
};
src/routes/auth.js(注册与登录)
import express from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import db from '../db/index.js';
const router = express.Router();
router.post('/register', async (req, res) => {
const { username, password } = req.body;
const hashed = await bcrypt.hash(password, 10);
try {
const result = await db.query(
'INSERT INTO users (username, password_hash) VALUES ($1, $2) RETURNING id, username',
[username, hashed]
);
res.status(201).json(result.rows[0]);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Registration failed' });
}
});
router.post('/login', async (req, res) => {
const { username, password } = req.body;
try {
const { rows } = await db.query('SELECT id, username, password_hash FROM users WHERE username=$1', [username]);
const user = rows[0];
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
const match = await bcrypt.compare(password, user.password_hash);
if (!match) return res.status(401).json({ error: 'Invalid credentials' });
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.json({ token });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Login failed' });
}
});
export default router;
src/middlewares/auth.js(JWT 中间件)
import jwt from 'jsonwebtoken';
export default function auth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader) return res.status(401).json({ error: 'No token' });
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, process.env.JWT_SECRET);
req.user = payload;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
}
src/routes/todos.js(受保护的 Todo API)
import express from 'express';
import db from '../db/index.js';
import auth from '../middlewares/auth.js';
const router = express.Router();
router.use(auth);
router.get('/', async (req, res) => {
const { rows } = await db.query('SELECT id, title, done FROM todos WHERE user_id=$1', [req.user.userId]);
res.json(rows);
});
router.post('/', async (req, res) => {
const { title } = req.body;
const { rows } = await db.query(
'INSERT INTO todos (user_id, title) VALUES ($1, $2) RETURNING id, title, done',
[req.user.userId, title]
);
res.status(201).json(rows[0]);
});
// ... update & delete omitted for brevity
export default router;
小结与理由:
- 路由分层清晰(routes、controllers、middlewares)便于维护;
- 使用参数化查询(
$1)避免 SQL 注入;- 密码必须用哈希(如 bcrypt),绝不存明文;
- JWT 用于 session-less 验证,便于横向扩展。
这些做法都与社区与官方实践一致(Node/Express 官方文档与 Postgres 官方文档均强调安全性与参数化查询)。
5. 数据库:PostgreSQL 模式设计与使用(别只会 SELECT *)
数据库模式(DDL 示例):
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(100) UNIQUE NOT NULL,
password_hash VARCHAR(256) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
CREATE TABLE todos (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title TEXT NOT NULL,
done BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT now()
);
CREATE INDEX idx_todos_user ON todos(user_id);
设计提示(实战经验 + 官方建议):
- 使用外键保证数据一致性,必要时使用事务(
BEGIN/COMMIT); - 对频繁查询的字段建索引,但别滥用索引(插入慢);
- 在大型项目中考虑分表、分区、或读副本。官方文档对事务与索引有详尽说明,这里不重复赘述,请以官方手册为准。
示例:在 Node 中做事务(pg)
// 简单事务示例
async function transferTodoOwnership(fromUserId, toUserId, todoId) {
const client = await db.pool.connect();
try {
await client.query('BEGIN');
// 逻辑检查
await client.query('UPDATE todos SET user_id=$1 WHERE id=$2 AND user_id=$3', [toUserId, todoId, fromUserId]);
await client.query('COMMIT');
} catch (e) {
await client.query('ROLLBACK');
throw e;
} finally {
client.release();
}
}
6. 前端:React 构建单页应用(不只是 JSX 那么简单)
项目结构(示例):
client/
├─ package.json
├─ src/
│ ├─ App.jsx
│ ├─ index.jsx
│ ├─ pages/
│ │ ├─ Login.jsx
│ │ └─ Todos.jsx
│ ├─ components/
│ └─ services/
│ └─ api.js
关键点:组件化、状态管理(可以用 Context 或 Redux)、路由(React Router)与 API 服务分层。React 官方文档鼓励组件化思维与 hooks 使用。
src/services/api.js(API 封装)
const API_BASE = import.meta.env.VITE_API_URL || 'http://localhost:4000/api';
export async function login(username, password) {
const res = await fetch(`${API_BASE}/auth/login`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username, password})
});
return res.json();
}
export async function fetchTodos(token) {
const res = await fetch(`${API_BASE}/todos`, {
headers: { Authorization: `Bearer ${token}` }
});
return res.json();
}
src/pages/Todos.jsx(React + hooks 简单示例)
import React, { useEffect, useState } from 'react';
import { fetchTodos } from '../services/api';
export default function Todos() {
const [todos, setTodos] = useState([]);
useEffect(() => {
const token = localStorage.getItem('token');
if (!token) return;
fetchTodos(token).then(setTodos).catch(console.error);
}, []);
return (
<div>
<h1>Your Todos</h1>
<ul>
{todos.map(t => <li key={t.id}>{t.title} {t.done ? '✓' : ''}</li>)}
</ul>
</div>
);
}
UX 提示(个人感受):
- 骨架屏、加载状态、错误提示很重要,别以为只有功能正确就够了;
- 在开发阶段打开 React DevTools、Network 面板,调试跨域与 token 问题会省你很多时间。官方文档与社区指南对 hooks 与路由有许多示例,值得细读。
7. 容器化:Dockerfile + docker-compose(让“在我机上运行”变得可信)
Dockerfile(后端)
FROM node:18-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production
COPY . .
ENV NODE_ENV=production
EXPOSE 4000
CMD ["node", "src/index.js"]
docker-compose.yml(将服务与数据库组合)
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: devpass
POSTGRES_DB: appdb
volumes:
- db-data:/var/lib/postgresql/data
ports:
- "5432:5432"
server:
build: ./server
env_file: ./server/.env
ports:
- "4000:4000"
depends_on:
- db
volumes:
db-data:
小贴士:
depends_on只控制启动顺序,不等同于数据库“可用性”检查。常见的实践是在后端启动时做连接重试或使用wait-for脚本确保 DB 就绪。官方 Docker 教程与 Compose 文档提供了更详细的指导。
8. 部署与安全(别把钥匙放在 README)
安全清单(实务):
- 不要把
.env放到公共仓库; - 使用强随机
JWT_SECRET,生产环境使用更严格的过期策略并考虑刷新 token; - 对数据库用户做权限分级(应用用户只授予必需权限);
- 在 Nginx 层做 TLS(HTTPS);
- 防止暴露 Postgres 的公网端口,使用私有网络或 DBaaS。
这些做法都在官方文档或社区最佳实践中被强调,务必重视。
性能与监控:
- 后端引入请求日志与性能监控(例如:Prometheus + Grafana,或商业 APM);
- 数据库做慢查询日志与索引优化;
- 静态文件(前端)使用 CDN 与缓存策略(Cache-Control)。
9. 性能、扩展与运维建议(想长久,得提前设计)
- 水平扩展:后端无状态设计(使用 JWT 或 session 存储在 Redis)能让你随时扩容;
- 读写分离:读负载大时考虑主从复制;Postgres 官方文档对复制与备份有深入章节。
- 数据迁移:使用迁移工具(如
knex,sequelize, 或flyway)管理 schema 版本; - 备份策略:制定定期备份和恢复演练(RTO/RPO);别把“恢复”当成最后一分钟的事。
10. 总结与进阶路线(一句话的感慨)
从零搭一套能跑的全栈系统,听起来像浪漫,实际是把“细节”与“耐心”做成了习惯。用 React+Node/Express+Postgres+Docker 的好处是:技术成熟、社区大、资料全(官方文档很靠谱),坏处是:你要管理的细节比你想象的多(安全、性能、迁移、运维……)。但当你看到用户点击那颗“完成”按钮,心里一阵小确幸,那些折腾都值得了。
- Node.js 官方文档(API & 指南)。
- Express 官方站点(路由与中间件示例)。
- PostgreSQL 官方文档(事务、索引、备份策略)。
- Docker 官方文档(容器与 Compose 指南)。
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!
- 点赞
- 收藏
- 关注作者
评论(0)