当前位置: 首页 > news >正文

nodejs构建项目

从零到一搭建 Node.js 框架

搭建一个 Node.js 框架是理解 Web 应用架构的绝佳方式。本指南将带您完成创建一个轻量级但功能完善的 Node.js 框架的全过程,类似于 Express 或 Koa,但规模更小,便于理解。

目录

  1. 项目初始化
  2. 创建核心应用类
  3. 路由系统
  4. 中间件系统
  5. 请求和响应对象增强
  6. 错误处理
  7. 静态文件服务
  8. 模板引擎支持
  9. 配置和环境变量
  10. 完整项目结构
  11. 使用示例
  12. 下一步

1. 项目初始化

首先,创建项目目录并初始化 npm 项目:

mkdir my-nodejs-framework
cd my-nodejs-framework
npm init -y

安装必要的依赖:

npm install --save path-to-regexp http-errors
npm install --save-dev jest nodemon

更新 package.json 中的脚本:

"scripts": {
  "start": "node index.js",
  "dev": "nodemon index.js",
  "test": "jest"
}

2. 创建核心应用类

创建 lib 目录和核心应用文件:

mkdir -p lib
touch lib/application.js

实现核心应用类:

// lib/application.js
const http = require('http');
const EventEmitter = require('events');

class Application extends EventEmitter {
  constructor() {
    super();
    this.middleware = [];
    this.settings = {};
  }

  listen(...args) {
    const server = http.createServer(this.callback());
    return server.listen(...args);
  }

  use(fn) {
    this.middleware.push(fn);
    return this;
  }

  callback() {
    return (req, res) => {
      let index = 0;
      const next = (err) => {
        // 错误处理逻辑将在后面实现
        if (err) return this.handleError(err, req, res);
        
        if (index >= this.middleware.length) return;
        const middleware = this.middleware[index++];
        
        try {
          middleware(req, res, next);
        } catch (err) {
          next(err);
        }
      };

      // 增强 req 和 res 对象
      this.enhanceReqRes(req, res);
      
      // 开始执行中间件链
      next();
    };
  }

  enhanceReqRes(req, res) {
    // 这里将添加对请求和响应对象的增强
    req.app = this;
    res.app = this;
    
    // 添加常用响应方法
    res.send = function(body) {
      if (typeof body === 'object') {
        this.setHeader('Content-Type', 'application/json');
        this.end(JSON.stringify(body));
      } else {
        this.setHeader('Content-Type', 'text/html');
        this.end(String(body));
      }
    };
    
    res.status = function(code) {
      this.statusCode = code;
      return this;
    };
    
    res.json = function(obj) {
      this.setHeader('Content-Type', 'application/json');
      this.end(JSON.stringify(obj));
    };
  }

  handleError(err, req, res) {
    // 默认错误处理
    console.error(err);
    res.statusCode = err.status || err.statusCode || 500;
    res.end(err.message || 'Internal Server Error');
  }

  set(setting, val) {
    this.settings[setting] = val;
    return this;
  }

  get(setting) {
    return this.settings[setting];
  }
}

module.exports = Application;

3. 路由系统

创建路由文件:

touch lib/router.js

实现路由系统:

// lib/router.js
const { pathToRegexp } = require('path-to-regexp');

class Router {
  constructor() {
    this.routes = {
      GET: [],
      POST: [],
      PUT: [],
      DELETE: [],
      PATCH: [],
      HEAD: [],
      OPTIONS: []
    };
    
    this.middleware = this.routes;
  }

  route(method, path, handlers) {
    const keys = [];
    const regexp = pathToRegexp(path, keys);
    
    this.routes[method].push({
      regexp,
      keys,
      handlers: Array.isArray(handlers) ? handlers : [handlers]
    });
    
    return this;
  }

  get(path, ...handlers) {
    return this.route('GET', path, handlers);
  }

  post(path, ...handlers) {
    return this.route('POST', path, handlers);
  }

  put(path, ...handlers) {
    return this.route('PUT', path, handlers);
  }

  delete(path, ...handlers) {
    return this.route('DELETE', path, handlers);
  }

  patch(path, ...handlers) {
    return this.route('PATCH', path, handlers);
  }

  head(path, ...handlers) {
    return this.route('HEAD', path, handlers);
  }

  options(path, ...handlers) {
    return this.route('OPTIONS', path, handlers);
  }

  all(path, ...handlers) {
    Object.keys(this.routes).forEach(method => {
      this.route(method, path, handlers);
    });
    return this;
  }

  match(req) {
    const { method, url } = req;
    const path = url.split('?')[0];
    
    const routes = this.routes[method] || [];
    
    for (const route of routes) {
      const match = route.regexp.exec(path);
      
      if (match) {
        req.params = {};
        
        const values = match.slice(1);
        
        for (let i = 0; i < route.keys.length; i++) {
          const key = route.keys[i];
          req.params[key.name] = values[i];
        }
        
        return route.handlers;
      }
    }
    
    return null;
  }

  middleware(req, res, next) {
    const handlers = this.match(req);
    
    if (!handlers) return next();
    
    let idx = 0;
    
    const nextHandler = (err) => {
      if (err) return next(err);
      
      const handler = handlers[idx++];
      
      if (!handler) return next();
      
      try {
        handler(req, res, nextHandler);
      } catch (err) {
        nextHandler(err);
      }
    };
    
    nextHandler();
  }
}

module.exports = () => {
  const router = new Router();
  
  // 返回中间件函数
  return function routerMiddleware(req, res, next) {
    router.middleware(req, res, next);
  };
};

4. 中间件系统

中间件系统已经在核心应用类中实现了基本功能。现在添加一些常用的内置中间件:

mkdir -p lib/middleware
touch lib/middleware/body-parser.js

实现请求体解析中间件:

// lib/middleware/body-parser.js
function bodyParser() {
  return (req, res, next) => {
    if (req.method === 'GET' || req.method === 'HEAD') {
      return next();
    }

    const contentType = req.headers['content-type'] || '';
    let body = '';

    req.on('data', chunk => {
      body += chunk.toString();
    });

    req.on('end', () => {
      if (contentType.includes('application/json')) {
        try {
          req.body = JSON.parse(body);
        } catch (err) {
          req.body = {};
          return next(err);
        }
      } else if (contentType.includes('application/x-www-form-urlencoded')) {
        req.body = Object.fromEntries(
          new URLSearchParams(body)
        );
      } else {
        req.body = body;
      }
      
      next();
    });
  };
}

module.exports = bodyParser;

5. 请求和响应对象增强

扩展 enhanceReqRes 方法来提供更多功能:

// 在 lib/application.js 中更新 enhanceReqRes 方法

enhanceReqRes(req, res) {
  req.app = this;
  res.app = this;
  
  // 解析查询参数
  const url = new URL(req.url, `http://${req.headers.host}`);
  req.query = Object.fromEntries(url.searchParams);
  
  // 请求扩展
  req.get = function(field) {
    return this.headers[field.toLowerCase()];
  };
  
  // 响应扩展
  res.set = function(field, value) {
    this.setHeader(field, value);
    return this;
  };
  
  res.send = function(body) {
    if (typeof body === 'object') {
      this.setHeader('Content-Type', 'application/json');
      this.end(JSON.stringify(body));
    } else {
      this.setHeader('Content-Type', 'text/html');
      this.end(String(body));
    }
    return this;
  };
  
  res.status = function(code) {
    this.statusCode = code;
    return this;
  };
  
  res.json = function(obj) {
    this.setHeader('Content-Type', 'application/json');
    this.end(JSON.stringify(obj));
    return this;
  };
  
  res.redirect = function(status, url) {
    if (typeof status === 'string') {
      url = status;
      status = 302;
    }
    
    this.statusCode = status;
    this.setHeader('Location', url);
    this.end();
    return this;
  };
  
  res.render = (view, locals) => {
    // 模板引擎支持将在后面实现
    if (!this.settings.views || !this.settings['view engine']) {
      throw new Error('No view engine configured');
    }
    
    // 将在模板引擎部分完成
  };
}

6. 错误处理

更新应用类中的错误处理方法:

// 在 lib/application.js 中更新 handleError 方法

handleError(err, req, res) {
  // 检查是否有错误处理中间件
  const errorHandlers = this.middleware.filter(
    fn => fn.length === 4
  );
  
  if (errorHandlers.length > 0) {
    let idx = 0;
    
    const next = (error) => {
      if (idx >= errorHandlers.length) {
        // 如果所有错误处理中间件都不处理,则使用默认处理
        this.defaultErrorHandler(error || err, req, res);
        return;
      }
      
      const handler = errorHandlers[idx++];
      
      try {
        handler(error || err, req, res, next);
      } catch (e) {
        next(e);
      }
    };
    
    next(err);
  } else {
    // 如果没有错误处理中间件,则使用默认处理
    this.defaultErrorHandler(err, req, res);
  }
}

defaultErrorHandler(err, req, res) {
  console.error(err);
  
  const statusCode = err.status || err.statusCode || 500;
  const message = this.get('env') === 'production' && statusCode === 500
    ? 'Internal Server Error'
    : err.message || 'Internal Server Error';
  
  res.statusCode = statusCode;
  
  if (req.headers.accept && req.headers.accept.includes('application/json')) {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ error: message }));
  } else {
    res.setHeader('Content-Type', 'text/html');
    res.end(`<h1>${statusCode} - ${message}</h1>`);
  }
}

7. 静态文件服务

创建静态文件服务中间件:

touch lib/middleware/static.js

实现静态文件服务:

// lib/middleware/static.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');

const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);
const createReadStream = fs.createReadStream;

function serveStatic(root, options = {}) {
  const { index = 'index.html', extensions = [] } = options;
  
  return async (req, res, next) => {
    if (req.method !== 'GET' && req.method !== 'HEAD') {
      return next();
    }
    
    // 获取请求路径
    const url = req.url.split('?')[0];
    let filePath = path.join(root, url);
    
    try {
      let stats = await stat(filePath);
      
      if (stats.isDirectory()) {
        // 尝试索引文件
        if (index) {
          filePath = path.join(filePath, index);
          stats = await stat(filePath);
        } else {
          // 如果不使用索引文件,则列出目录内容
          if (options.showDir) {
            const files = await readdir(filePath);
            return res.send(`<ul>${files.map(file => `<li><a href="${path.join(url, file)}">${file}</a></li>`).join('')}</ul>`);
          }
          
          return next();
        }
      }
      
      // 设置内容类型
      const ext = path.extname(filePath).toLowerCase();
      const contentType = {
        '.html': 'text/html',
        '.css': 'text/css',
        '.js': 'application/javascript',
        '.json': 'application/json',
        '.png': 'image/png',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.gif': 'image/gif',
        '.svg': 'image/svg+xml'
      }[ext] || 'application/octet-stream';
      
      res.setHeader('Content-Type', contentType);
      res.setHeader('Content-Length', stats.size);
      
      const stream = createReadStream(filePath);
      stream.pipe(res);
      
    } catch (err) {
      if (err.code === 'ENOENT') {
        // 文件不存在,尝试扩展名
        if (extensions.length > 0) {
          for (const ext of extensions) {
            try {
              const newPath = `${filePath}.${ext}`;
              const stats = await stat(newPath);
              
              if (stats.isFile()) {
                const stream = createReadStream(newPath);
                const contentType = {
                  'html': 'text/html',
                  'css': 'text/css',
                  'js': 'application/javascript',
                  'json': 'application/json'
                }[ext] || 'application/octet-stream';
                
                res.setHeader('Content-Type', contentType);
                res.setHeader('Content-Length', stats.size);
                
                stream.pipe(res);
                return;
              }
            } catch (e) {
              // 继续尝试下一个扩展名
            }
          }
        }
        
        // 所有尝试都失败,继续下一个中间件
        return next();
      }
      
      // 其他错误传递给错误处理
      return next(err);
    }
  };
}

module.exports = serveStatic;

8. 模板引擎支持

创建视图渲染支持:

touch lib/view.js

实现视图引擎管理:

// lib/view.js
const fs = require('fs');
const path = require('path');
const { promisify } = require('util');

const readFile = promisify(fs.readFile);

class View {
  constructor(options) {
    this.root = options.root;
    this.engines = options.engines || {};
    this.defaultEngine = options.defaultEngine;
    this.cache = options.cache || {};
    this.cacheEnabled = options.cacheEnabled !== false;
  }

  async render(name, options = {}) {
    const viewPath = this.lookup(name);
    
    if (!viewPath) {
      throw new Error(`Could not find view: ${name}`);
    }
    
    // 从缓存中获取编译后的模板
    let template;
    
    if (this.cacheEnabled && this.cache[viewPath]) {
      template = this.cache[viewPath];
    } else {
      const content = await readFile(viewPath, 'utf8');
      const engine = this.getEngine(viewPath);
      
      if (!engine) {
        throw new Error(`No engine registered for ${path.extname(viewPath)}`);
      }
      
      template = engine.compile(content, { filename: viewPath, ...options });
      
      if (this.cacheEnabled) {
        this.cache[viewPath] = template;
      }
    }
    
    // 渲染模板
    return template(options);
  }

  lookup(name) {
    // 如果有绝对路径,则直接使用
    if (path.isAbsolute(name)) {
      return this.exists(name) ? name : null;
    }
    
    // 如果没有扩展名,尝试添加默认引擎的扩展名
    let ext = path.extname(name);
    
    if (!ext && this.defaultEngine) {
      name = `${name}.${this.defaultEngine}`;
      ext = `.${this.defaultEngine}`;
    }
    
    // 在视图目录中查找文件
    const filePath = path.join(this.root, name);
    
    if (this.exists(filePath)) {
      return filePath;
    }
    
    return null;
  }

  exists(filePath) {
    try {
      fs.accessSync(filePath);
      return true;
    } catch (e) {
      return false;
    }
  }

  getEngine(filePath) {
    const ext = path.extname(filePath).slice(1);
    return this.engines[ext];
  }

  static registerEngine(ext, engine) {
    if (ext[0] === '.') ext = ext.slice(1);
    
    this.engines = this.engines || {};
    this.engines[ext] = engine;
  }
}

module.exports = View;

更新应用类中的渲染方法:

// 在 lib/application.js 中添加

const View = require('./view');

// 在 Application 类中添加
constructor() {
  super();
  this.middleware = [];
  this.settings = {};
  this.engines = {};
  this.cache = {};
}

// 添加模板引擎注册方法
engine(ext, fn) {
  if (ext[0] === '.') ext = ext.slice(1);
  this.engines[ext] = fn;
  return this;
}

// 更新 enhanceReqRes 方法中的 render 功能
res.render = (view, locals, callback) => {
  const done = callback || ((err, html) => {
    if (err) return next(err);
    res.setHeader('Content-Type', 'text/html');
    res.end(html);
  });
  
  try {
    const viewOptions = {
      root: this.get('views') || process.cwd() + '/views',
      engines: this.engines,
      defaultEngine: this.get('view engine'),
      cacheEnabled: this.get('view cache') !== false,
      cache: this.cache
    };
    
    const viewInstance = new View(viewOptions);
    
    const context = {
      ...this.locals,
      ...res.locals,
      ...locals,
      settings: this.settings
    };
    
    viewInstance.render(view, context)
      .then(html => done(null, html))
      .catch(err => done(err));
  } catch (err) {
    done(err);
  }
};

9. 配置和环境变量

创建配置管理工具:

touch lib/config.js

实现配置管理:

// lib/config.js
const fs = require('fs');
const path = require('path');

class Config {
  constructor(options = {}) {
    this.env = process.env.NODE_ENV || 'development';
    this.configPath = options.configPath || process.cwd();
    this.configObject = {};
    
    // 加载环境变量
    if (options.env !== false) {
      this.loadEnv();
    }
    
    // 加载配置文件
    if (options.files !== false) {
      this.loadConfigFiles();
    }
  }

  loadEnv() {
    // 加载 .env 文件
    try {
      const envFile = path.join(this.configPath, '.env');
      const envContent = fs.readFileSync(envFile, 'utf8');
      
      envContent.split('\n').forEach(line => {
        if (line.trim() && !line.startsWith('#')) {
          const [key, ...values] = line.split('=');
          const value = values.join('=').trim();
          process.env[key.trim()] = value;
        }
      });
    } catch (err) {
      // 如果 .env 文件不存在,则忽略
      if (err.code !== 'ENOENT') {
        console.error('Error loading .env file:', err);
      }
    }
    
    // 加载环境特定的 .env 文件,例如 .env.development
    try {
      const envFile = path.join(this.configPath, `.env.${this.env}`);
      const envContent = fs.readFileSync(envFile, 'utf8');
      
      envContent.split('\n').forEach(line => {
        if (line.trim() && !line.startsWith('#')) {
          const [key, ...values] = line.split('=');
          const value = values.join('=').trim();
          process.env[key.trim()] = value;
        }
      });
    } catch (err) {
      // 忽略错误
    }
  }

  loadConfigFiles() {
    // 加载基本配置
    try {
      const configFile = path.join(this.configPath, 'config', 'config.json');
      const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
      this.configObject = { ...this.configObject, ...config };
    } catch (err) {
      // 忽略错误
    }
    
    // 加载环境特定的配置
    try {
      const configFile = path.join(this.configPath, 'config', `${this.env}.json`);
      const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
      this.configObject = { ...this.configObject, ...config };
    } catch (err) {
      // 忽略错误
    }
  }

  get(key, defaultValue) {
    // 首先检查环境变量
    if (process.env[key]) {
      return process.env[key];
    }
    
    // 然后检查配置对象
    const parts = key.split('.');
    let value = this.configObject;
    
    for (const part of parts) {
      if (value == null) {
        return defaultValue;
      }
      value = value[part];
    }
    
    return value !== undefined ? value : defaultValue;
  }

  set(key, value) {
    const parts = key.split('.');
    let obj = this.configObject;
    
    for (let i = 0; i < parts.length - 1; i++) {
      const part = parts[i];
      
      if (!obj[part] || typeof obj[part] !== 'object') {
        obj[part] = {};
      }
      
      obj = obj[part];
    }
    
    obj[parts[parts.length - 1]] = value;
    return this;
  }
}

module.exports = Config;

将配置工具集成到应用中:

// 在 lib/application.js 中添加

const Config = require('./config');

// 在 Application 类的构造函数中添加
constructor() {
  super();
  this.middleware = [];
  this.settings = {};
  this.engines = {};
  this.cache = {};
  this.config = new Config();
  
  // 从配置中加载设置
  this.loadConfigSettings();
}

loadConfigSettings() {
  // 可以从配置中加载默认设置
  this.set('env', this.config.get('NODE_ENV') || 'development');
  this.set('port', this.config.get('PORT') || 3000);
  this.set('views', this.config.get('views') || path.join(process.cwd(), 'views'));
  this.set('view engine', this.config.get('viewEngine'));
  this.set('view cache', this.config.get('viewCache', this.get('env') === 'production'));
}

10. 完整项目结构

创建完整的框架入口文件:

touch index.js lib/index.js

lib/index.js 中导出所有框架组件:

// lib/index.js
const Application = require('./application');
const Router = require('./router');
const bodyParser = require('./middleware/body-parser');
const serveStatic = require('./middleware/static');
const View = require('./view');
const Config = require('./config');

function createApplication() {
  const app = new Application();
  return app;
}

// 导出框架组件
module.exports = Object.assign(createApplication, {
  Application,
  Router,
  bodyParser,
  static: serveStatic,
  View,
  Config
});

在根目录的 index.js 中重新导出框架:

// index.js
module.exports = require('./lib');

最终的项目结构应该如下:

my-nodejs-framework/
├── index.js                # 主入口文件
├── lib/                    # 核心库
│   ├── application.js      # 应用核心类
│   ├── config.js           # 配置管理
│   ├── index.js            # 库入口
│   ├── middleware/         # 中间件
│   │   ├── body-parser.js  # 请求体解析
│   │   └── static.js       # 静态文件服务
│   ├── router.js           # 路由系统
│   └── view.js             # 视图引擎支持
├── package.json            # 项目配置
├── README.md               # 文档
└── test/                   # 测试文件

11. 使用示例

创建一个使用你的框架的示例应用:

mkdir -p examples/basic-app
cd examples/basic-app
mkdir -p public views
touch app.js

编写示例应用:

// examples/basic-app/app.js
const myFramework = require('../../index');
const path = require('path');

const app = myFramework();
const router = myFramework.Router();

// 配置设置
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');

// 注册模板引擎
app.engine('ejs', require('ejs').__express);

// 使用中间件
app.use(myFramework.bodyParser());
app.use(myFramework.static(path.join(__dirname, 'public')));

// 路由处理
router.get('/', (req, res) => {
  res.render('index', { title: 'My Framework', message: 'Hello World!' });
});

router.get('/json', (req, res) => {
  res.json({ message: 'This is JSON response' });
});

router.get('/users/:id', (req, res) => {
  res.json({ userId: req.params.id });
});

router.post('/users', (req, res) => {
  res.status(201).json({ 
    message: 'User created', 
    user: req.body 
  });
});

// 错误处理中间件
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

// 使用路由
app.use(router);

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

创建一个简单的视图:

echo '<h1><%= title %></h1><p><%= message %></p>' > views/index.ejs

12. 下一步

恭喜!你已经从零开始构建了一个基本的 Node.js 框架。这个框架具备了路由、中间件、错误处理、静态文件服务和模板引擎等功能。下一步你可以考虑:

  1. 添加测试: 为框架的各个组件编写单元测试和集成测试。
  2. 添加更多中间件: 例如 CORS、压缩、安全头设置等。
  3. 支持 WebSockets: 添加实时通信功能。
  4. 日志系统: 实现一个可配置的日志记录系统。
  5. ORM 集成: 添加数据库访问层。
  6. 身份验证与授权: 实现用户认证和权限控制功能。
  7. 文档生成器: 为 API 路由自动生成文档。
  8. 集群支持: 添加对 Node.js 集群的支持,以提高性能。
  9. 性能优化: 进行代码性能分析和优化。
  10. 发布为 NPM 包: 准备并发布你的框架到 NPM。

基础知识详解

为了更好地理解我们构建的框架,下面是一些核心概念的详细解释:

中间件系统

中间件是 Node.js Web 框架的核心概念。在我们的框架中,中间件是一个接收 reqresnext 参数的函数:

function myMiddleware(req, res, next) {
  // 对请求/响应进行处理
  next(); // 调用下一个中间件
}

中间件链通过 next() 函数连接起来,形成一个洋葱模型:

        ┌────────────────────────┐
        │                        │
 ┌──────┤  中间件 1 (开始)       ├──────┐
 │      │                        │      │
 │      └────────────────────────┘      │
 │                                      │
 │      ┌────────────────────────┐      │
 │      │                        │      │
 │ ┌────┤  中间件 2 (开始)       ├────┐ │
 │ │    │                        │    │ │
 │ │    └────────────────────────┘    │ │
 │ │                                  │ │
 │ │    ┌────────────────────────┐    │ │
 │ │    │                        │    │ │
 │ │    │      路由处理          │    │ │
 │ │    │                        │    │ │
 │ │    └────────────────────────┘    │ │
 │ │                                  │ │
 │ │    ┌────────────────────────┐    │ │
 │ │    │                        │    │ │
 │ └────┤  中间件 2 (结束)       ├────┘ │
 │      │                        │      │
 │      └────────────────────────┘      │
 │                                      │
 │      ┌────────────────────────┐      │
 │      │                        │      │
 └──────┤  中间件 1 (结束)       ├──────┘
        │                        │
        └────────────────────────┘

路由系统

路由系统负责将请求匹配到正确的处理程序。我们使用 path-to-regexp 库将路由路径转换为正则表达式,以支持参数化路由如 /users/:id

路由匹配流程:

  1. 获取请求的 HTTP 方法和 URL
  2. 在对应方法的路由列表中查找匹配的路由
  3. 如果找到匹配的路由,提取参数并执行处理函数
  4. 如果没有找到匹配的路由,继续下一个中间件

错误处理

错误处理是 Web 应用程序的重要部分。我们的框架支持两种错误处理方式:

  1. 同步错误: 通过 try/catch 捕获并传递给 next(err)
  2. 异步错误: 通过在异步操作的 catch 块中调用 next(err) 处理

错误处理中间件有四个参数:(err, req, res, next),框架会自动识别这种模式并在发生错误时调用它。

静态文件服务

静态文件服务器负责提供静态资源如 HTML、CSS、JavaScript、图片等。它的工作原理是:

  1. 检查请求的方法是否为 GET 或 HEAD
  2. 构造文件路径
  3. 检查文件是否存在
  4. 如果是目录,尝试提供索引文件
  5. 设置正确的 Content-Type 头
  6. 创建文件流并通过管道发送到响应

模板引擎

模板引擎允许生成动态 HTML。我们的框架实现了一个灵活的视图系统:

  1. 支持注册多种模板引擎
  2. 根据文件扩展名选择正确的引擎
  3. 支持模板缓存以提高性能
  4. 提供本地变量和全局变量传递给模板

示例扩展:RESTful API

下面是一个更完整的 RESTful API 示例:

const myFramework = require('../../index');
const path = require('path');

const app = myFramework();
const router = myFramework.Router();

// 中间件
app.use(myFramework.bodyParser());
app.use((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// 模拟数据库
const users = [
  { id: '1', name: 'Alice', email: 'alice@example.com' },
  { id: '2', name: 'Bob', email: 'bob@example.com' }
];

// RESTful 路由
router.get('/api/users', (req, res) => {
  res.json(users);
});

router.get('/api/users/:id', (req, res, next) => {
  const user = users.find(u => u.id === req.params.id);
  
  if (!user) {
    const err = new Error('User not found');
    err.status = 404;
    return next(err);
  }
  
  res.json(user);
});

router.post('/api/users', (req, res) => {
  const newUser = {
    id: String(users.length + 1),
    name: req.body.name,
    email: req.body.email
  };
  
  users.push(newUser);
  res.status(201).json(newUser);
});

router.put('/api/users/:id', (req, res, next) => {
  const index = users.findIndex(u => u.id === req.params.id);
  
  if (index === -1) {
    const err = new Error('User not found');
    err.status = 404;
    return next(err);
  }
  
  users[index] = {
    id: req.params.id,
    name: req.body.name || users[index].name,
    email: req.body.email || users[index].email
  };
  
  res.json(users[index]);
});

router.delete('/api/users/:id', (req, res, next) => {
  const index = users.findIndex(u => u.id === req.params.id);
  
  if (index === -1) {
    const err = new Error('User not found');
    err.status = 404;
    return next(err);
  }
  
  const deletedUser = users[index];
  users.splice(index, 1);
  
  res.json(deletedUser);
});

// 错误处理
app.use((err, req, res, next) => {
  const statusCode = err.status || 500;
  const message = statusCode === 500 ? 'Internal Server Error' : err.message;
  
  console.error(err);
  
  res.status(statusCode).json({
    error: {
      message,
      status: statusCode
    }
  });
});

// 未找到路由处理
app.use((req, res) => {
  res.status(404).json({
    error: {
      message: 'Not Found',
      status: 404
    }
  });
});

// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`API server running on port ${PORT}`);
});

高级优化方向

如果你想进一步扩展和优化你的框架,可以考虑以下方向:

1. 依赖注入系统

创建一个依赖注入容器,使控制器和服务更容易测试:

class Container {
  constructor() {
    this.services = new Map();
  }
  
  register(name, factory, singleton = true) {
    this.services.set(name, { factory, singleton, instance: null });
    return this;
  }
  
  get(name) {
    const service = this.services.get(name);
    
    if (!service) {
      throw new Error(`Service ${name} not found`);
    }
    
    if (service.singleton) {
      if (!service.instance) {
        service.instance = service.factory(this);
      }
      return service.instance;
    }
    
    return service.factory(this);
  }
}

2. 装饰器支持

添加对 TypeScript 装饰器的支持,使路由定义更加简洁:

@Controller('/api/users')
class UserController {
  @Get('/')
  index(req, res) {
    // 获取所有用户
  }
  
  @Get('/:id')
  show(req, res) {
    // 获取单个用户
  }
  
  @Post('/')
  create(req, res) {
    // 创建用户
  }
}

3. 插件系统

实现一个插件系统,允许轻松扩展框架功能:

class Application {
  // ... 其他方法
  
  plugin(pluginFunction) {
    pluginFunction(this);
    return this;
  }
}

// 使用插件
app.plugin(require('my-framework-cache-plugin'));

4. 健康检查和监控

添加内置健康检查和监控功能:

app.use('/health', (req, res) => {
  const health = {
    uptime: process.uptime(),
    message: 'OK',
    timestamp: Date.now()
  };
  
  res.json(health);
});

5. GraphQL 支持

集成 GraphQL 作为 REST API 的替代方案:

const { graphqlHTTP } = require('express-graphql');
const { buildSchema } = require('graphql');

const schema = buildSchema(`
  type User {
    id: ID!
    name: String!
    email: String!
  }
  
  type Query {
    user(id: ID!): User
    users: [User]
  }
`);

const rootValue = {
  user: ({ id }) => users.find(u => u.id === id),
  users: () => users
};

app.use('/graphql', graphqlHTTP({
  schema,
  rootValue,
  graphiql: true
}));

性能优化技巧

为了让你的框架保持高性能,可以考虑以下优化:

1. 路由缓存

缓存编译后的路由正则表达式和参数:

class Router {
  constructor() {
    this.routes = { /* ... */ };
    this.compiledRoutes = new Map();
  }
  
  compileRoute(path) {
    if (this.compiledRoutes.has(path)) {
      return this.compiledRoutes.get(path);
    }
    
    const keys = [];
    const regexp = pathToRegexp(path, keys);
    const compiled = { regexp, keys };
    
    this.compiledRoutes.set(path, compiled);
    return compiled;
  }
}

2. 响应压缩

添加响应压缩以减少传输大小:

function compression(options = {}) {
  const { threshold = 1024 } = options;
  
  return (req, res, next) => {
    const originalEnd = res.end;
    const originalWrite = res.write;
    let body = [];
    
    // 检查客户端是否支持压缩
    const acceptEncoding = req.headers['accept-encoding'] || '';
    const supportsGzip = acceptEncoding.includes('gzip');
    
    if (!supportsGzip) {
      return next();
    }
    
    // 重写 write 和 end 方法
    res.write = function(chunk, encoding) {
      body.push(chunk);
      return true;
    };
    
    res.end = function(chunk, encoding) {
      if (chunk) {
        body.push(chunk);
      }
      
      const buffer = Buffer.concat(body);
      
      // 判断是否需要压缩
      if (buffer.length < threshold) {
        res.write = originalWrite;
        res.end = originalEnd;
        return originalEnd.call(this, buffer, encoding);
      }
      
      const zlib = require('zlib');
      zlib.gzip(buffer, (err, result) => {
        if (err) {
          res.write = originalWrite;
          res.end = originalEnd;
          return originalEnd.call(this, buffer, encoding);
        }
        
        res.setHeader('Content-Encoding', 'gzip');
        res.setHeader('Content-Length', result.length);
        originalEnd.call(this, result);
      });
    };
    
    next();
  };
}

3. HTTP/2 支持

添加对 HTTP/2 的支持以提高性能:

const http2 = require('http2');
const fs = require('fs');

class Application {
  // ... 其他方法
  
  listenHttps(port, options) {
    const server = http2.createSecureServer({
      key: fs.readFileSync(options.key),
      cert: fs.readFileSync(options.cert),
      allowHTTP1: true
    }, this.callback());
    
    return server.listen(port);
  }
}

结语

通过本指南,你已经从零开始创建了一个功能完善的 Node.js Web 框架。这个框架虽然简单,但包含了所有核心功能:路由、中间件、错误处理、静态文件服务和模板引擎支持。

这个过程不仅帮助你理解了 Web 框架的内部工作原理,还为你提供了一个可以根据自己的需求定制和扩展的基础。无论是用于学习目的还是作为实际项目的基础,这个框架都是一个很好的起点。

随着你对框架的不断改进和扩展,它可能会发展成为一个适合特定用例的专门框架。祝你的 Node.js 开发之旅顺利!

相关文章:

  • 前端开发中的问题排查与定位:HTML、CSS、JavaScript(报错的解决方式)
  • 高效的内容搜索工具推荐
  • 【工程开发】LLMC准确高效的LLM压缩工具(一)
  • MIPI协议介绍
  • (四十七)Dart 中的 `identical` 函数与 `const` 关键字
  • GM DC Monitor v2.0 数据中心监控预警平台-CMDB使用教程(第十篇)
  • 【图像处理基石】什么是通透感?
  • cropperjs 2.0裁剪图片后转base64提示“Tainted canvases may not be exported”跨域问题的解决办法。
  • 0x03.Redis 通常应用于哪些场景?
  • 【从0到1搞懂大模型】transformer先导:seq2seq、注意力机制、残差网络等(6)
  • C++ 数据结构之图:从理论到实践
  • React(1)基础入门
  • 【模拟电路】PIN光电二极管和APD雪崩光电二极管
  • I/O进程5
  • fio的资料
  • 基于FPGA的一维时间序列idct变换verilog实现,包含testbench和matlab辅助验证程序
  • L1 第6次课 for循环
  • Python学生信息查询
  • Lesson 11 One good turn deserves another
  • AtCoder Beginner Contest 401 E题 题解
  • 浙江一教师被指殴打并威胁小学生,教育局通报涉事人被行拘
  • 广西鹿寨一水文站“倒刺扶手”存安全隐患,官方通报处理情况
  • 多少Moreless:向世界展示现代中式家具的生活美学
  • 江西4人拟任县(市、区)委书记,其中一人为“80后”
  • 首次带人形机器人走科技节红毯,傅利叶顾捷:没太多包袱,很多事都能从零开始
  • 著名心血管病学专家李国庆教授逝世,享年63岁