我从零开始构建了一个 Full-Stack Invoice App。以下是完整拆解
发布: (2026年5月2日 GMT+8 05:47)
3 分钟阅读
原文: Dev.to
Source: Dev.to
大多数发票工具要么太贵,要么太复杂。我在一周内自行构建了一个,已上线部署,并记录了每个技术决策和经验教训。
Live Demo & Source Code
- Live app: https://invoxa-eta.vercel.app
- GitHub repository: https://github.com/Carter254g/invoxa
Tech Stack
- Frontend: React
- Backend: Node.js with Express
- Database: PostgreSQL
- Deployment: Frontend on Vercel, backend on Render
Authentication Middleware
每个受保护的路由在到达控制器之前都会经过同一个中间件。
// auth.js
const auth = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token provided' });
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
};
简洁、可复用,并用一个函数保护所有路由。
Automatic Database Migrations
服务器在启动时会自动创建表,因此克隆仓库的任何人都能在几秒钟内获得可用的数据库。
// server.js
migrate.createTables().then(() => {
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
});
Global Axios Interceptor
无需手动为每个请求附加 JWT 令牌,拦截器会全局添加它。
// api.js
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
写一次,免去后顾之忧。
Deployment Gotchas
- Render SSL for PostgreSQL – 在生产环境的
pgPool 配置中添加rejectUnauthorized: false,否则连接会失败。 - CORS configuration – 明确列出你的 Vercel 域名;通配符在使用凭证时无效。
- JWT expiry values – 环境变量必须是字符串。如果值为
undefined,jwt.sign会抛出静默错误。请提供默认值。
这些问题花了我两小时;现在它们对你来说不花一分钱。
Dashboard Overview
仪表盘显示:
- 发票总数
- 客户总数
- 已收收入
- 未结余额
所有数据均通过实时数据库查询在每次加载时计算——没有虚假数据或硬编码的数字。
Additional Features
- 发票 PDF 导出
- 通过 Nodemailer 发送邮件
- 循环发票
- 支付网关集成
Conclusion
如果此拆解对你有帮助,请考虑在 GitHub 上给仓库加星并留下评论。
Tags: javascript, node, react, webdev