1. 为什么选择Node.js开发应用
十年前我第一次接触Node.js时,就被它的非阻塞I/O模型所吸引。当时还在用传统的LAMP架构开发网站,每次遇到高并发场景就头疼不已。Node.js的出现彻底改变了我的开发生涯,现在回看这些年用Node.js构建过的项目,从简单的API服务到复杂的实时应用,这套技术栈始终保持着惊人的生产力。
Node.js特别适合以下场景:
- 需要处理大量并发连接的I/O密集型应用(如聊天服务、实时协作工具)
- 快速构建RESTful API和微服务
- 需要前后端同构的项目(配合React/Vue等框架)
- 工具链开发(构建工具、CLI程序等)
提示:虽然Node.js擅长I/O密集型任务,但对于CPU密集型计算(如视频转码、复杂算法)建议还是用Go或Rust等语言实现,通过子进程方式调用。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境配置指南
2.1 Node.js版本管理
我强烈推荐使用nvm(Node Version Manager)来管理Node.js版本。这是我电脑上的.nvmrc配置示例:
bash复制# 安装最新LTS版本
nvm install --lts
# 创建项目专用版本
nvm use 18.16.0
echo "18.16.0" > .nvmrc
这样当其他开发者进入项目目录时,运行nvm use就会自动切换到指定版本。我团队曾经因为开发和生产环境Node版本不一致导致过诡异的BUG,这个习惯帮我们省去了很多麻烦。
2.2 包管理器选择
npm虽然是Node.js自带的包管理器,但我更推荐yarn或pnpm。特别是pnpm,它采用硬链接方式存储依赖,能显著节省磁盘空间。这是我常用的初始化命令:
bash复制# 使用pnpm初始化项目
pnpm init
# 安装常用开发依赖
pnpm add -D typescript @types/node eslint prettier
3. 项目架构设计
3.1 基础目录结构
经过多个项目的迭代,我总结出这样的目录结构:
code复制project-root/
├── src/
│ ├── controllers/ # 业务逻辑
│ ├── services/ # 核心服务
│ ├── models/ # 数据模型
│ ├── routes/ # 路由定义
│ ├── middlewares/ # 中间件
│ └── utils/ # 工具函数
├── tests/ # 测试代码
├── configs/ # 配置文件
└── scripts/ # 构建脚本
这种结构特别适合中小型项目,当项目规模扩大时,可以考虑按功能模块拆分(如user/, product/等目录)。
3.2 现代JavaScript实践
我建议从一开始就使用TypeScript。这是tsconfig.json的基础配置:
json复制{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
4. 核心模块实现
4.1 创建HTTP服务器
不使用任何框架,用原生模块实现一个基础服务器:
typescript复制import http from 'http';
const server = http.createServer((req, res) => {
// 简单路由处理
if (req.url === '/api/status') {
res.writeHead(200, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ status: 'OK' }));
}
res.writeHead(404);
res.end();
});
server.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
4.2 使用Express框架
虽然原生模块能工作,但实际项目中我推荐Express:
typescript复制import express from 'express';
import helmet from 'helmet';
const app = express();
// 安全中间件
app.use(helmet());
app.use(express.json());
// 路由示例
app.get('/api/users', async (req, res) => {
try {
const users = await UserService.listAll();
res.json(users);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
5. 数据库集成
5.1 MongoDB连接
这是我常用的MongoDB连接方案:
typescript复制import mongoose from 'mongoose';
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGO_URI!, {
maxPoolSize: 10, // 连接池大小
socketTimeoutMS: 45000,
});
console.log('MongoDB connected');
} catch (err) {
console.error('Database connection error', err);
process.exit(1);
}
};
5.2 定义数据模型
使用mongoose定义用户模型的示例:
typescript复制interface IUser {
username: string;
email: string;
passwordHash: string;
createdAt: Date;
}
const userSchema = new mongoose.Schema<IUser>({
username: { type: String, required: true, unique: true },
email: { type: String, required: true, unique: true },
passwordHash: { type: String, required: true, select: false },
createdAt: { type: Date, default: Date.now }
});
export const User = mongoose.model<IUser>('User', userSchema);
6. 身份认证实现
6.1 JWT认证流程
这是我实现JWT认证的典型代码:
typescript复制import jwt from 'jsonwebtoken';
import bcrypt from 'bcryptjs';
const generateToken = (userId: string) => {
return jwt.sign({ id: userId }, process.env.JWT_SECRET!, {
expiresIn: '30d'
});
};
const comparePassword = async (plainText: string, hash: string) => {
return bcrypt.compare(plainText, hash);
};
// 登录控制器示例
const login = async (email: string, password: string) => {
const user = await User.findOne({ email }).select('+passwordHash');
if (!user || !(await comparePassword(password, user.passwordHash))) {
throw new Error('Invalid credentials');
}
return {
token: generateToken(user._id.toString()),
user: {
id: user._id,
username: user.username,
email: user.email
}
};
};
7. 测试策略
7.1 单元测试配置
使用Jest进行测试的配置:
javascript复制// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: ['src/**/*.ts'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
}
}
};
7.2 API测试示例
使用supertest进行接口测试:
typescript复制import request from 'supertest';
import app from '../src/app';
describe('GET /api/status', () => {
it('should return 200 OK', async () => {
const res = await request(app).get('/api/status');
expect(res.statusCode).toEqual(200);
expect(res.body).toHaveProperty('status', 'OK');
});
});
8. 性能优化技巧
8.1 集群模式
利用多核CPU的集群方案:
typescript复制import cluster from 'cluster';
import os from 'os';
if (cluster.isPrimary) {
const cpuCount = os.cpus().length;
console.log(`Primary ${process.pid} is running`);
// Fork workers
for (let i = 0; i < cpuCount; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork(); // 自动重启
});
} else {
// Worker进程启动应用
require('./server');
}
8.2 缓存策略
Redis缓存实现示例:
typescript复制import redis from 'redis';
const client = redis.createClient({
url: process.env.REDIS_URL
});
const getWithCache = async (key: string, fetchData: () => Promise<any>, ttl = 3600) => {
const cached = await client.get(key);
if (cached) return JSON.parse(cached);
const data = await fetchData();
await client.setEx(key, ttl, JSON.stringify(data));
return data;
};
9. 部署方案
9.1 Docker化部署
这是我常用的Dockerfile:
dockerfile复制FROM node:18-alpine
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install
COPY . .
RUN pnpm build
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]
9.2 PM2进程管理
生产环境推荐使用PM2:
bash复制# 启动应用
pm2 start dist/server.js -i max --name "api-server"
# 保存进程列表
pm2 save
# 设置开机启动
pm2 startup
10. 监控与日志
10.1 健康检查端点
typescript复制app.get('/health', (req, res) => {
const healthcheck = {
status: 'UP',
timestamp: Date.now(),
uptime: process.uptime(),
memoryUsage: process.memoryUsage()
};
res.json(healthcheck);
});
10.2 结构化日志
使用winston进行日志记录:
typescript复制import winston from 'winston';
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.simple()
}));
}
在项目开发过程中,我发现这些实践特别重要:
- 始终使用process manager(如PM2)运行生产环境应用
- 对敏感配置使用环境变量(推荐dotenv)
- 为所有异步操作添加错误处理
- 使用TypeScript可以避免大量运行时错误
- 编写测试代码虽然前期耗时,但长期来看能节省大量调试时间
