Node.js 微服务架构实战:基于Express + TypeScript 的企业级应用开发

绮梦之旅
绮梦之旅 2026-02-05T11:05:04+08:00
0 0 2

引言

在现代软件开发领域,微服务架构已经成为构建大规模、高可用性应用的重要方式。Node.js凭借其非阻塞I/O特性和丰富的生态系统,在微服务开发中占据重要地位。本文将深入探讨如何使用Express框架和TypeScript构建企业级的Node.js微服务,并通过Docker实现容器化部署。

微服务架构概述

什么是微服务架构

微服务架构是一种将单一应用程序拆分为多个小型、独立服务的软件设计方法。每个服务都围绕特定的业务功能构建,可以独立开发、部署和扩展。这些服务通过轻量级通信机制(通常是HTTP API)进行交互。

微服务的核心优势

  • 可扩展性:可以根据需求单独扩展特定服务
  • 技术多样性:不同服务可以使用不同的技术栈
  • 容错性:单个服务故障不会影响整个系统
  • 团队协作:小团队可以独立负责特定服务
  • 部署灵活性:支持持续集成和持续部署

技术选型分析

Express.js 框架

Express.js是Node.js最流行的Web应用框架,提供了简洁而灵活的API设计。它具有以下特点:

  • 轻量级且无强制性约定
  • 中间件机制强大
  • 丰富的生态系统
  • 良好的性能表现
const express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.json({ message: 'Hello World!' });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

TypeScript 的优势

TypeScript作为JavaScript的超集,为Node.js开发带来了类型安全和更好的开发体验:

  • 编译时类型检查
  • 更好的IDE支持
  • 代码重构更安全
  • 提高代码可维护性

项目初始化与配置

项目结构设计

microservice-project/
├── src/
│   ├── controllers/
│   ├── models/
│   ├── routes/
│   ├── services/
│   ├── middleware/
│   ├── utils/
│   └── app.ts
├── tests/
├── docker-compose.yml
├── Dockerfile
├── package.json
└── tsconfig.json

初始化项目

# 创建项目目录
mkdir microservice-project
cd microservice-project

# 初始化npm项目
npm init -y

# 安装依赖
npm install express cors helmet morgan dotenv
npm install -D typescript @types/node @types/express @types/cors @types/helmet @types/morgan ts-node nodemon

TypeScript 配置文件

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "lib": ["ES2020"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "removeComments": true,
    "noImplicitReturns": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

核心服务实现

应用入口文件

// src/app.ts
import express, { Application, Request, Response, NextFunction } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import dotenv from 'dotenv';

// 导入路由
import userRoutes from './routes/user.routes';
import productRoutes from './routes/product.routes';

// 加载环境变量
dotenv.config();

const app: Application = express();
const PORT: number = parseInt(process.env.PORT || '3000', 10);

// 中间件配置
app.use(helmet()); // 安全头部
app.use(cors()); // 跨域支持
app.use(morgan('combined')); // 日志记录
app.use(express.json()); // JSON解析
app.use(express.urlencoded({ extended: true })); // URL编码解析

// 健康检查端点
app.get('/health', (req: Request, res: Response) => {
  res.status(200).json({
    status: 'OK',
    timestamp: new Date().toISOString(),
    service: 'microservice-project'
  });
});

// 路由注册
app.use('/api/users', userRoutes);
app.use('/api/products', productRoutes);

// 全局错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Something went wrong!',
    message: err.message
  });
});

// 404处理
app.use('*', (req: Request, res: Response) => {
  res.status(404).json({
    error: 'Not Found',
    message: 'The requested resource was not found'
  });
});

export default app;

用户服务实现

// src/models/user.model.ts
export interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
  updatedAt: Date;
}

export class UserModel {
  private users: User[] = [
    {
      id: '1',
      name: 'John Doe',
      email: 'john@example.com',
      createdAt: new Date(),
      updatedAt: new Date()
    }
  ];

  async findById(id: string): Promise<User | null> {
    return this.users.find(user => user.id === id) || null;
  }

  async findAll(): Promise<User[]> {
    return this.users;
  }

  async create(userData: Omit<User, 'id' | 'createdAt' | 'updatedAt'>): Promise<User> {
    const newUser: User = {
      id: Math.random().toString(36).substr(2, 9),
      ...userData,
      createdAt: new Date(),
      updatedAt: new Date()
    };
    
    this.users.push(newUser);
    return newUser;
  }

  async update(id: string, userData: Partial<User>): Promise<User | null> {
    const index = this.users.findIndex(user => user.id === id);
    if (index === -1) return null;

    this.users[index] = {
      ...this.users[index],
      ...userData,
      updatedAt: new Date()
    };

    return this.users[index];
  }

  async delete(id: string): Promise<boolean> {
    const initialLength = this.users.length;
    this.users = this.users.filter(user => user.id !== id);
    return this.users.length < initialLength;
  }
}
// src/controllers/user.controller.ts
import { Request, Response } from 'express';
import { UserModel, User } from '../models/user.model';

class UserController {
  private userService: UserModel;

  constructor() {
    this.userService = new UserModel();
  }

  async getAllUsers(req: Request, res: Response): Promise<void> {
    try {
      const users = await this.userService.findAll();
      res.status(200).json({
        success: true,
        data: users,
        count: users.length
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to fetch users',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async getUserById(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params;
      const user = await this.userService.findById(id);
      
      if (!user) {
        res.status(404).json({
          success: false,
          error: 'User not found'
        });
        return;
      }

      res.status(200).json({
        success: true,
        data: user
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to fetch user',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async createUser(req: Request, res: Response): Promise<void> {
    try {
      const { name, email } = req.body;
      
      // 验证输入
      if (!name || !email) {
        res.status(400).json({
          success: false,
          error: 'Name and email are required'
        });
        return;
      }

      const user = await this.userService.create({ name, email });
      
      res.status(201).json({
        success: true,
        data: user
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to create user',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async updateUser(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params;
      const { name, email } = req.body;
      
      const updatedUser = await this.userService.update(id, { name, email });
      
      if (!updatedUser) {
        res.status(404).json({
          success: false,
          error: 'User not found'
        });
        return;
      }

      res.status(200).json({
        success: true,
        data: updatedUser
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to update user',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async deleteUser(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params;
      const deleted = await this.userService.delete(id);
      
      if (!deleted) {
        res.status(404).json({
          success: false,
          error: 'User not found'
        });
        return;
      }

      res.status(200).json({
        success: true,
        message: 'User deleted successfully'
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to delete user',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }
}

export default new UserController();
// src/routes/user.routes.ts
import express, { Router } from 'express';
import userController from '../controllers/user.controller';

const router: Router = express.Router();

router.get('/', userController.getAllUsers);
router.get('/:id', userController.getUserById);
router.post('/', userController.createUser);
router.put('/:id', userController.updateUser);
router.delete('/:id', userController.deleteUser);

export default router;

产品服务实现

// src/models/product.model.ts
export interface Product {
  id: string;
  name: string;
  description: string;
  price: number;
  category: string;
  stock: number;
  createdAt: Date;
  updatedAt: Date;
}

export class ProductModel {
  private products: Product[] = [
    {
      id: '1',
      name: 'Laptop',
      description: 'High-performance laptop',
      price: 999.99,
      category: 'Electronics',
      stock: 50,
      createdAt: new Date(),
      updatedAt: new Date()
    }
  ];

  async findById(id: string): Promise<Product | null> {
    return this.products.find(product => product.id === id) || null;
  }

  async findAll(): Promise<Product[]> {
    return this.products;
  }

  async findByCategory(category: string): Promise<Product[]> {
    return this.products.filter(product => product.category === category);
  }

  async create(productData: Omit<Product, 'id' | 'createdAt' | 'updatedAt'>): Promise<Product> {
    const newProduct: Product = {
      id: Math.random().toString(36).substr(2, 9),
      ...productData,
      createdAt: new Date(),
      updatedAt: new Date()
    };
    
    this.products.push(newProduct);
    return newProduct;
  }

  async update(id: string, productData: Partial<Product>): Promise<Product | null> {
    const index = this.products.findIndex(product => product.id === id);
    if (index === -1) return null;

    this.products[index] = {
      ...this.products[index],
      ...productData,
      updatedAt: new Date()
    };

    return this.products[index];
  }

  async delete(id: string): Promise<boolean> {
    const initialLength = this.products.length;
    this.products = this.products.filter(product => product.id !== id);
    return this.products.length < initialLength;
  }
}
// src/controllers/product.controller.ts
import { Request, Response } from 'express';
import { ProductModel, Product } from '../models/product.model';

class ProductController {
  private productService: ProductModel;

  constructor() {
    this.productService = new ProductModel();
  }

  async getAllProducts(req: Request, res: Response): Promise<void> {
    try {
      const products = await this.productService.findAll();
      res.status(200).json({
        success: true,
        data: products,
        count: products.length
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to fetch products',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async getProductsByCategory(req: Request, res: Response): Promise<void> {
    try {
      const { category } = req.params;
      const products = await this.productService.findByCategory(category);
      
      res.status(200).json({
        success: true,
        data: products,
        count: products.length
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to fetch products by category',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async getProductById(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params;
      const product = await this.productService.findById(id);
      
      if (!product) {
        res.status(404).json({
          success: false,
          error: 'Product not found'
        });
        return;
      }

      res.status(200).json({
        success: true,
        data: product
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to fetch product',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async createProduct(req: Request, res: Response): Promise<void> {
    try {
      const { name, description, price, category, stock } = req.body;
      
      // 验证输入
      if (!name || !price || !category) {
        res.status(400).json({
          success: false,
          error: 'Name, price and category are required'
        });
        return;
      }

      const product = await this.productService.create({ 
        name, 
        description, 
        price, 
        category, 
        stock 
      });
      
      res.status(201).json({
        success: true,
        data: product
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to create product',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async updateProduct(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params;
      const { name, description, price, category, stock } = req.body;
      
      const updatedProduct = await this.productService.update(id, { 
        name, 
        description, 
        price, 
        category, 
        stock 
      });
      
      if (!updatedProduct) {
        res.status(404).json({
          success: false,
          error: 'Product not found'
        });
        return;
      }

      res.status(200).json({
        success: true,
        data: updatedProduct
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to update product',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }

  async deleteProduct(req: Request, res: Response): Promise<void> {
    try {
      const { id } = req.params;
      const deleted = await this.productService.delete(id);
      
      if (!deleted) {
        res.status(404).json({
          success: false,
          error: 'Product not found'
        });
        return;
      }

      res.status(200).json({
        success: true,
        message: 'Product deleted successfully'
      });
    } catch (error) {
      res.status(500).json({
        success: false,
        error: 'Failed to delete product',
        message: error instanceof Error ? error.message : 'Unknown error'
      });
    }
  }
}

export default new ProductController();
// src/routes/product.routes.ts
import express, { Router } from 'express';
import productController from '../controllers/product.controller';

const router: Router = express.Router();

router.get('/', productController.getAllProducts);
router.get('/category/:category', productController.getProductsByCategory);
router.get('/:id', productController.getProductById);
router.post('/', productController.createProduct);
router.put('/:id', productController.updateProduct);
router.delete('/:id', productController.deleteProduct);

export default router;

中间件与工具类

验证中间件

// src/middleware/validation.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { validationResult } from 'express-validator';

export const validate = (req: Request, res: Response, next: NextFunction) => {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({
      success: false,
      error: 'Validation failed',
      details: errors.array()
    });
  }
  next();
};

export const validateUser = [
  (req: Request, res: Response, next: NextFunction) => {
    if (!req.body.name || !req.body.email) {
      return res.status(400).json({
        success: false,
        error: 'Name and email are required'
      });
    }
    next();
  }
];

export const validateProduct = [
  (req: Request, res: Response, next: NextFunction) => {
    if (!req.body.name || !req.body.price || !req.body.category) {
      return res.status(400).json({
        success: false,
        error: 'Name, price and category are required'
      });
    }
    next();
  }
];

日志中间件

// src/middleware/logger.middleware.ts
import { Request, Response, NextFunction } from 'express';
import winston from 'winston';

const logger = winston.createLogger({
  level: 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
    new winston.transports.File({ filename: 'logs/combined.log' })
  ]
});

if (process.env.NODE_ENV !== 'production') {
  logger.add(new winston.transports.Console({
    format: winston.format.simple()
  }));
}

export const requestLogger = (req: Request, res: Response, next: NextFunction) => {
  logger.info(`${req.method} ${req.url}`, {
    method: req.method,
    url: req.url,
    headers: req.headers,
    body: req.body,
    ip: req.ip
  });
  next();
};

export const errorLogger = (error: Error, req: Request, res: Response, next: NextFunction) => {
  logger.error(error.message, {
    stack: error.stack,
    method: req.method,
    url: req.url,
    ip: req.ip
  });
  next(error);
};

Docker容器化部署

Dockerfile 配置

# Dockerfile
FROM node:18-alpine

# 设置工作目录
WORKDIR /app

# 复制package文件
COPY package*.json ./

# 安装依赖
RUN npm ci --only=production

# 复制源代码
COPY . .

# 构建项目
RUN npm run build

# 暴露端口
EXPOSE 3000

# 创建非root用户
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
USER nextjs

# 启动应用
CMD ["npm", "start"]

Docker Compose 配置

# docker-compose.yml
version: '3.8'

services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - PORT=3000
    volumes:
      - ./logs:/app/logs
    networks:
      - microservice-network

  # 可选:添加数据库服务
  database:
    image: postgres:15-alpine
    environment:
      POSTGRES_DB: microservice_db
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    networks:
      - microservice-network

volumes:
  postgres_data:

networks:
  microservice-network:
    driver: bridge

环境配置文件

# .env
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://user:password@localhost:5432/microservice_db
JWT_SECRET=my_jwt_secret_key
LOG_LEVEL=info

服务间通信

HTTP客户端实现

// src/utils/http.client.ts
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';

export class HttpClient {
  private client: AxiosInstance;

  constructor(baseURL: string, timeout: number = 5000) {
    this.client = axios.create({
      baseURL,
      timeout,
      headers: {
        'Content-Type': 'application/json'
      }
    });

    // 请求拦截器
    this.client.interceptors.request.use(
      (config) => {
        // 添加认证头等
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );

    // 响应拦截器
    this.client.interceptors.response.use(
      (response) => {
        return response.data;
      },
      (error) => {
        return Promise.reject(error);
      }
    );
  }

  async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.get<T>(url, config);
    return response;
  }

  async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.post<T>(url, data, config);
    return response;
  }

  async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.put<T>(url, data, config);
    return response;
  }

  async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> {
    const response = await this.client.delete<T>(url, config);
    return response;
  }
}

export const httpClient = new HttpClient('http://localhost:3000');

微服务间调用示例

// src/services/external.service.ts
import { HttpClient } from '../utils/http.client';

export class ExternalService {
  private client: HttpClient;

  constructor() {
    this.client = new HttpClient('http://user-service:3000');
  }

  async getUserById(id: string) {
    try {
      const user = await this.client.get<any>(`/api/users/${id}`);
      return user;
    } catch (error) {
      console.error('Failed to fetch user:', error);
      throw new Error('User service unavailable');
    }
  }
}

测试策略

单元测试配置

// tests/user.controller.test.ts
import request from 'supertest';
import app from '../src/app';

describe('User Controller', () => {
  describe('GET /api/users', () => {
    it('should return all users', async () => {
      const response = await request(app)
        .get('/api/users')
        .expect(200)
        .expect('Content-Type', /json/);
      
      expect(response.body).toHaveProperty('success', true);
      expect(response.body).toHaveProperty('data');
    });
  });

  describe('POST /api/users', () => {
    it('should create a new user', async () => {
      const userData = {
        name: 'Jane Doe',
        email: 'jane@example.com'
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(201)
        .expect('Content-Type', /json/);
      
      expect(response.body).toHaveProperty('success', true);
      expect(response.body.data).toHaveProperty('name', userData.name);
      expect(response.body.data).toHaveProperty('email', userData.email);
    });
  });
});

集成测试

// tests/integration.test.ts
import { describe, it, beforeAll, afterAll } from 'vitest';
import request from 'supertest';
import app from '../src/app';

describe('Integration Tests', () => {
  let server: any;

  beforeAll(() => {
    server = app.listen(0);
  });

  afterAll((done) => {
    server.close(done);
  });

  it('should handle health check endpoint', async () => {
    const response = await request(server)
      .get('/health')
      .expect(200);
    
    expect(response.body).toHaveProperty('status', 'OK');
  });
});

性能优化与监控

缓存实现

// src/utils/cache.util.ts
import NodeCache from 'node-cache';

export class CacheUtil {
  private static instance: NodeCache;
  private static readonly DEFAULT_TTL = 3600; // 1小时

  static getInstance(): NodeCache {
    if (!this.instance) {
      this.instance = new NodeCache({
        stdTTL: this.DEFAULT_TTL,
        checkperiod: 120
      });
    }
    return this.instance
相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000