nodejs构建项目

news2025/4/16 7:44:20

从零到一搭建 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 开发之旅顺利!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2334929.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

flutter 桌面应用之右键菜单

​在 Flutter 桌面应用开发中&#xff0c;context_menu 和 contextual_menu 是两款常用的右键菜单插件&#xff0c;各有特色。以下是对它们的对比分析&#xff1a;​ context_menu 集成方式&#xff1a;​通过 ContextMenuArea 组件包裹目标组件&#xff0c;定义菜单项。​掘金…

Cygwin编译安装Acise

本文记录Windows下使用Cygwin编译安装Acise的流程。 零、环境 操作系统Windows11Visual Studio CodeVisual Studio Code 1.92.0Cygwin 一、工具及依赖 1.1 Visual Studio Code 下载并安装Visual Studio Code, 同时安装以下插件&#xff0c; Task Explorer Output Colorizer …

pyqtgraph.opengl.items.GLSurfacePlotItem.GLSurfacePlotItem 报了一个错

1. 需求是这个样子的 有一个 pyqtgraph.opengl.GLViewWidget &#xff0c;在应用启动时存在QMainWindow中&#xff0c;即父对象是QMainWindow&#xff0c;当业务需要时&#xff0c;修改它的父对象变为一个QDialog&#xff0c;可以让它从QMainWindow中弹出显示在QDialog里&#…

【C++初学】课后作业汇总复习(六) 函数模板

1、函数模板 思考&#xff1a;如果重载的函数&#xff0c;其解决问题的逻辑是一致的、函数体语句相同&#xff0c;只是处理的数据类型不同&#xff0c;那么写多个相同的函数体&#xff0c;是重复劳动&#xff0c;而且还可能因为代码的冗余造成不一致性。 解决&#xff1a;使用…

【第16届蓝桥杯C++C组】--- 数位倍数

Hello呀&#xff0c;小伙伴们&#xff0c;第16届蓝桥杯也完美结束了&#xff0c;无论大家考的如何&#xff0c;都要放平心态&#xff0c;今年我刚上大一&#xff0c;也第一次参加蓝桥杯&#xff0c;刷的算法题也只有200来道&#xff0c;但是还是考的不咋滴&#xff0c;但是拿不…

Numpy和OpenCV库匹配查询,安装OpenCV ABI错误

文章目录 地址opencv-python&#xff1a;4.x版本的对应numpyopencv-python&#xff1a;5.x版本的对应numpy方法2 ps&#xff1a;装个opencv遇到ABI错误无语了&#xff0c;翻了官网&#xff0c;github文档啥都没&#xff0c;记录下 地址 opencv-python&#xff1a;4.x版本的对应…

ubuntu18.04安装miniforge3

1.下载安装文件 略&#xff08;注&#xff1a;从同事哪里拖来的安装包&#xff09; 2.修改安装文件权限 chmod x Miniforge3-Linux-x86_64.sh 3.将它安装到指定位置 micromamba activate /home/xxx/fxp/fromDukto/miniforge3 4.激活 /home/xxx/fxp/fromDukto/miniforge3…

智能手机功耗测试

随着智能手机发展,用户体验对手机的续航功耗要求越来越高。需要对手机进行功耗测试及分解优化,将手机的性能与功耗平衡。低功耗技术推动了手机的用户体验。手机功耗测试可以采用powermonitor或者NI仪表在功耗版上进行测试与优化。作为一个多功能的智能终端,手机的功耗组成极…

使用U盘安装 ubuntu 系统

1. 准备U 盘制作镜像 1.1 下载 ubuntu iso https://ubuntu.com/download/ 这里有多个版本以供下载&#xff0c;本文选择桌面版。 1.2 下载rufus https://rufus.ie/downloads/ 1.3 以管理员身份运行 rufus 设备选择你用来制作启动项的U盘&#xff0c;不能选错了&#xff1b;点…

oracle 并行度(Parallel Degree)

在Oracle数据库中&#xff0c;并行度&#xff08;Parallel Degree&#xff09; 是用于控制并行处理任务的关键配置&#xff0c;旨在通过多进程协作加速大规模数据处 一、并行度的核心概念 并行度&#xff08;DOP, Degree of Parallelism&#xff09; 表示一个操作同时使用的并…

Redis-场景缓存+秒杀+管道+消息队列

缓存一致性 1.两次更新 先更新数据库&#xff0c;再更新缓存&#xff1b;先更新缓存&#xff0c;再更新数据库&#xff1b; 出现不一致问题场景&#xff1a; 先更新数据库&#xff0c;再更新缓存&#xff1b; 先更新缓存&#xff0c;再更新数据库&#xff1b; 两次更新的适…

系统的安全及应用

仓库做了哪些优化 仓库源换成国内源不使用root用户登录将不必要的开机启动项关闭内核的调优 系统做了哪些安全加固 禁止使用root禁止使用弱命令将常见的 远程连接端口换掉 系统安全及应用 Cpu负载高 java程序 运行异常中病毒&#xff1f; ps aux - - sort %cpu %mem Cpu …

PostgreSQL内幕探索—基础知识

PostgreSQL内幕探索—基础知识 PostgreSQL&#xff08;以下简称PG&#xff09; 起源于 1986 年加州大学伯克利分校的 ‌POSTGRES 项目‌&#xff0c;最初以对象关系模型为核心&#xff0c;支持高级数据类型和复杂查询功能‌。 1996 年更名为 PostgreSQL 并开源&#xff0c;逐…

WPS复制粘贴错误 ,文件未找到 mathpage.wll

文章目录 1.错误提示图片2.解决方案1.找到MathType.wll文件和MathType Commands 2016.dotm文件并复制2.找到wps安装地址并拷贝上述两个文件到指定目录 3.重启WPS 1.错误提示图片 2.解决方案 1.找到MathType.wll文件和MathType Commands 2016.dotm文件并复制 MathType.wll地址如…

驱动开发硬核特训 · Day 6 : 深入解析设备模型的数据流与匹配机制 —— 以 i.MX8M 与树莓派为例的实战对比

&#x1f50d; B站相应的视屏教程&#xff1a; &#x1f4cc; 内核&#xff1a;博文视频 - 从静态绑定驱动模型到现代设备模型 主题&#xff1a;深入解析设备模型的数据流与匹配机制 —— 以 i.MX8M 与树莓派为例的实战对比 在上一节中&#xff0c;我们从驱动框架的历史演进出…

【UE5 C++课程系列笔记】35——HTTP基础——HTTP客户端异步请求API接口并解析响应的JSON

目录 前言 步骤 一、 搭建异步蓝图节点框架 二、异步蓝图节点嵌入到引擎的执行流程 三、获取本地时间并异步返回 四、获取网络时间并异步返回 五、源码 前言 本文以请求网络/本地时间API为例&#xff0c;介绍如何实现HTTP异步请求。 步骤 一、 搭建异步蓝图节点框架 …

手机静态ip地址怎么获取?方法与解析‌

而在某些特定情境下&#xff0c;我们可能需要为手机设置一个静态IP地址。本文将详细介绍手机静态IP地址详解及获取方法 一、什么是静态IP地址&#xff1f; 静态IP&#xff1a;由用户手动设置的固定IP地址&#xff0c;不会因网络重启或设备重连而改变。 动态IP&#xff1a;由路…

Python 基础语法汇总

Python 语法 │ ├── 基本结构 │ ├── 语句&#xff08;Statements&#xff09; │ │ ├── 表达式语句&#xff08;如赋值、算术运算&#xff09; │ │ ├── 控制流语句&#xff08;if, for, while&#xff09; │ │ ├── 定义语句&#xff08;def…

Linux上位机开发实践(OpenCV算法硬件加速)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 图像处理里面&#xff0c;opencv基本是一个标准模块。但是由于图像处理的特点&#xff0c;如果所有的算法都是cpu来做的话&#xff0c;效率会很低。…

Spring Boot MongoDB自定义连接池配置

手打不易&#xff0c;如果转摘&#xff0c;请注明出处&#xff01; 注明原文&#xff1a;http://zhangxiaofan.blog.csdn.net/article/details/144341407 一、引言 在 Spring Boot 应用中使用 MongoDB 时&#xff0c;合理配置连接池可以显著提升数据库访问的性能和稳定性。默…