作为JavaScript运行时环境,Node.js的安装过程虽然简单,但版本管理却藏着不少门道。我推荐使用nvm(Node Version Manager)进行多版本管理,这在实际开发中非常实用。以macOS为例,通过Homebrew安装nvm后,可以轻松切换不同Node.js版本:
bash复制brew install nvm
nvm install 16.14.2 # 安装指定版本
nvm use 16.14.2 # 切换版本
提示:生产环境推荐使用LTS(长期支持)版本,目前最新的LTS是20.x系列。使用
nvm install --lts即可安装最新LTS版本。
Windows用户可以使用nvm-windows,同样支持多版本管理。安装完成后,验证安装是否成功:
bash复制node -v # 查看Node.js版本
npm -v # 查看npm版本
执行npm init时,那些交互式选项都有其实际意义。这里分享几个关键点:
package.json中的main字段决定了模块的入口文件scripts字段可以定义各种快捷命令,比如:json复制"scripts": {
"start": "node app.js",
"dev": "nodemon app.js",
"test": "mocha"
}
private: true可以防止意外发布私有项目到npm我习惯使用npm init -y快速生成默认配置,然后再手动修改。对于企业级项目,建议严格填写所有字段,特别是license信息。
那个经典的"Hello World"示例虽然简单,但有几个关键细节值得注意:
javascript复制// app.js
console.log("Hello, Node.js!");
// 这样写更好:
function main() {
const greeting = "Hello, Node.js!";
console.log(greeting);
return greeting;
}
module.exports = main; // 使函数可被其他模块引用
注意:直接在最外层写业务逻辑不利于测试和复用。即使是简单示例,也应该保持良好的代码结构。
执行时可以使用node -r esm app.js(如果使用ES模块),或者更专业的启动方式:
bash复制# Linux/Mac
NODE_ENV=development node app.js
# Windows
set NODE_ENV=development && node app.js
Node.js的内置模块远比表面看到的强大。以fs模块为例,实际项目中应该注意:
javascript复制const fs = require('fs');
const path = require('path');
// 安全的文件操作
function readConfig() {
const configPath = path.join(__dirname, 'config.json');
try {
const data = fs.readFileSync(configPath, 'utf8');
return JSON.parse(data);
} catch (err) {
if (err.code === 'ENOENT') {
console.error('配置文件不存在');
} else {
console.error('配置文件读取失败', err);
}
process.exit(1); // 非正常退出
}
}
Express的简单示例往往掩盖了其工程化使用的复杂性。以下是一个更符合实际项目结构的示例:
code复制project/
├── src/
│ ├── controllers/
│ ├── models/
│ ├── routes/
│ ├── middlewares/
│ └── app.js
├── config/
├── tests/
└── package.json
对应的app.js应该这样组织:
javascript复制const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const config = require('./config');
const app = express();
// 中间件栈
app.use(helmet());
app.use(cors(config.corsOptions));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// 路由
const apiRouter = require('./routes/api');
app.use('/api', apiRouter);
// 错误处理中间件
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error' });
});
module.exports = app;
经验:使用app.js作为配置入口,在bin/www中启动服务器,这是Express生成器的标准做法,有利于测试和扩展。
从回调地狱到async/await,异步编程已经发生了很大变化。以下是一个包含错误处理和性能考虑的完整示例:
javascript复制const util = require('util');
const fs = require('fs');
const readFile = util.promisify(fs.readFile);
async function processFiles(filePaths) {
const results = [];
// 控制并发数
const concurrency = 3;
const batches = Math.ceil(filePaths.length / concurrency);
for (let i = 0; i < batches; i++) {
const batch = filePaths.slice(i * concurrency, (i + 1) * concurrency);
const promises = batch.map(async (filePath) => {
try {
const content = await readFile(filePath, 'utf8');
return { filePath, content, status: 'success' };
} catch (err) {
return { filePath, error: err.message, status: 'failed' };
}
});
results.push(...await Promise.all(promises));
}
return results;
}
Node.js应用的性能问题往往出现在:
使用以下工具进行监控:
bash复制# 查看内存使用
node --inspect app.js
# 然后在Chrome的chrome://inspect中调试
# 或者使用 clinic.js
npm install -g clinic
clinic doctor -- node app.js
优化建议:
实际项目推荐使用dotenv+config的组合:
javascript复制// config/default.json
{
"server": {
"port": 3000
},
"db": {
"host": "localhost",
"name": "dev_db"
}
}
// config/production.json
{
"db": {
"host": "cluster.prod.mongodb.net"
}
}
// 使用方式
const config = require('config');
const dbConfig = config.get('db');
生产环境需要结构化日志:
javascript复制const { createLogger, format, transports } = require('winston');
const { combine, timestamp, json } = format;
const logger = createLogger({
level: 'info',
format: combine(
timestamp(),
json()
),
transports: [
new transports.File({ filename: 'logs/error.log', level: 'error' }),
new transports.File({ filename: 'logs/combined.log' }),
new transports.Console({
format: format.simple()
})
]
});
// 记录HTTP请求的中间件
function httpLogger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
logger.info({
method: req.method,
url: req.originalUrl,
status: res.statusCode,
duration: `${duration}ms`,
ip: req.ip
});
});
next();
}
现代Node.js应用应该使用Docker部署:
dockerfile复制# Dockerfile
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
USER node
EXPOSE 3000
CMD ["node", "src/app.js"]
对应的docker-compose.yml:
yaml复制version: '3'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DB_HOST=mongodb
depends_on:
- mongodb
mongodb:
image: mongo:5
volumes:
- db_data:/data/db
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=example
volumes:
db_data:
使用Jest测试框架的完整示例:
javascript复制// utils/calculator.js
module.exports = {
add: (a, b) => a + b,
divide: (a, b) => {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
};
// __tests__/calculator.test.js
const calculator = require('../utils/calculator');
describe('Calculator', () => {
describe('add', () => {
it('should return 5 when adding 2 and 3', () => {
expect(calculator.add(2, 3)).toBe(5);
});
});
describe('divide', () => {
it('should throw error when dividing by zero', () => {
expect(() => calculator.divide(5, 0)).toThrow('Division by zero');
});
});
});
使用Supertest进行API测试:
javascript复制const request = require('supertest');
const app = require('../src/app');
const db = require('../src/db');
beforeAll(async () => {
await db.connect();
});
afterAll(async () => {
await db.disconnect();
});
describe('GET /api/users', () => {
it('should return 200 and user list', async () => {
const res = await request(app)
.get('/api/users')
.expect('Content-Type', /json/)
.expect(200);
expect(Array.isArray(res.body)).toBeTruthy();
});
});
javascript复制const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const hpp = require('hpp');
const cors = require('cors');
const app = express();
// 基础防护
app.use(helmet());
// 限流
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15分钟
max: 100 // 每个IP限制100个请求
});
app.use(limiter);
// 数据清洗
app.use(mongoSanitize());
app.use(hpp());
// CORS配置
const corsOptions = {
origin: process.env.ALLOWED_ORIGINS.split(','),
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
javascript复制const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
// 生成token
function generateToken(user) {
return jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
}
// 认证中间件
function authenticate(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.sendStatus(403);
req.user = user;
next();
});
}
// 密码加密
async function hashPassword(password) {
return await bcrypt.hash(password, 12);
}
使用VS Code的launch.json配置:
json复制{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Current File",
"program": "${file}",
"skipFiles": ["<node_internals>/**"]
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 9229,
"restart": true
}
]
}
调试技巧:
debugger语句设置断点使用heapdump和Chrome DevTools:
javascript复制const heapdump = require('heapdump');
// 在怀疑有内存泄漏的地方
heapdump.writeSnapshot('/tmp/' + Date.now() + '.heapsnapshot', (err) => {
if (err) console.error(err);
else console.log('Heap snapshot written');
});
分析步骤:
安装依赖:
bash复制npm install typescript @types/node ts-node --save-dev
tsconfig.json配置:
json复制{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
示例TypeScript代码:
typescript复制import express, { Request, Response } from 'express';
interface User {
id: number;
name: string;
}
const app = express();
const port = 3000;
app.get('/users/:id', (req: Request, res: Response) => {
const user: User = {
id: parseInt(req.params.id),
name: 'John Doe'
};
res.json(user);
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
使用Apollo Server:
javascript复制const { ApolloServer, gql } = require('apollo-server-express');
const express = require('express');
const typeDefs = gql`
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`;
const resolvers = {
Query: {
books: () => [
{ title: 'Node.js设计模式', author: 'Mario Casciaro' },
{ title: '深入浅出Node.js', author: '朴灵' }
]
}
};
async function startApolloServer() {
const app = express();
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () => {
console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
});
}
startApolloServer();
创建NestJS项目:
bash复制npm i -g @nestjs/cli
nest new project-name
微服务模块示例:
typescript复制// src/math/math.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
@Controller()
export class MathController {
@MessagePattern({ cmd: 'sum' })
accumulate(data: number[]): number {
return (data || []).reduce((a, b) => a + b);
}
}
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
AppModule,
{
transport: Transport.TCP,
options: { port: 3001 }
}
);
await app.listen();
}
bootstrap();
使用gRPC进行服务通信:
protobuf复制// math.proto
syntax = "proto3";
package math;
service MathService {
rpc Add (AddRequest) returns (AddResponse);
}
message AddRequest {
repeated double numbers = 1;
}
message AddResponse {
double result = 1;
}
实现服务端:
typescript复制import { Server, ServerCredentials } from '@grpc/grpc-js';
import { MathServiceService } from './math_grpc_pb';
import { AddRequest, AddResponse } from './math_pb';
const server = new Server();
function add(call: any, callback: any) {
const numbers = call.request.getNumbersList();
const response = new AddResponse();
response.setResult(numbers.reduce((a: number, b: number) => a + b, 0));
callback(null, response);
}
server.addService(MathServiceService, { add });
server.bindAsync('0.0.0.0:50051', ServerCredentials.createInsecure(), () => {
server.start();
});
利用多核CPU:
javascript复制const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const cpuCount = os.cpus().length;
for (let i = 0; i < cpuCount; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.id} died`);
cluster.fork();
});
} else {
require('./app');
}
使用Redis实现多级缓存:
javascript复制const redis = require('redis');
const { promisify } = require('util');
const client = redis.createClient();
const getAsync = promisify(client.get).bind(client);
const setAsync = promisify(client.set).bind(client);
async function getWithCache(key, getData) {
const cached = await getAsync(key);
if (cached) {
return JSON.parse(cached);
}
const freshData = await getData();
await setAsync(key, JSON.stringify(freshData), 'EX', 3600); // 1小时过期
return freshData;
}
// 使用示例
app.get('/api/products', async (req, res) => {
const products = await getWithCache('all_products', () => {
return Product.find().exec();
});
res.json(products);
});
使用autocannon进行压力测试:
bash复制npm install -g autocannon
autocannon -c 100 -d 20 http://localhost:3000/api
关键优化指标:
根据测试结果调整: