如何用nodejs写一个简单后端服务

闲话少说,开始上代码。

javascript 复制代码
// ============================================
// 1. 导入Node.js内置模块
// ============================================
const http = require('http');        // HTTP模块:用于创建Web服务器
const url = require('url');          // URL模块:用于解析URL地址
const querystring = require('querystring'); // 查询字符串模块:解析URL参数
const fs = require('fs');            // 文件系统模块:读写文件(可选)
const path = require('path');        // 路径模块:处理文件路径(可选)

// ============================================
// 2. 服务器配置
// ============================================
const PORT = process.env.PORT || 3000;  // 端口号:优先使用环境变量,否则用3000
const HOST = 'localhost';               // 主机名:本地开发用localhost

// ============================================
// 3. 路由存储结构
// ============================================
// routes对象用于存储所有注册的路由
// 格式:routes[HTTP方法][URL路径] = 处理函数
const routes = {
    'GET': {},      // 存储GET请求的路由
    'POST': {},     // 存储POST请求的路由
    'PUT': {},      // 存储PUT请求的路由
    'DELETE': {}    // 存储DELETE请求的路由
};

// ============================================
// 4. 路由注册函数
// ============================================
/**
 * 注册一个路由
 * @param {string} method - HTTP方法(GET、POST、PUT、DELETE)
 * @param {string} path - URL路径(支持 :id 这样的动态参数)
 * @param {function} handler - 处理函数,接收(req, res, params)三个参数
 * 
 * 使用示例:
 *   addRoute('GET', '/api/users', (req, res) => { ... })
 *   addRoute('GET', '/api/users/:id', (req, res, params) => { ... })
 */
function addRoute(method, path, handler) {
    routes[method][path] = handler;
}

// ============================================
// 5. 解析请求体(POST/PUT请求的数据)
// ============================================
/**
 * 解析HTTP请求体中的数据
 * @param {object} req - HTTP请求对象
 * @returns {Promise} 返回解析后的数据
 * 
 * 支持三种格式:
 *   1. application/json -> 返回JSON对象
 *   2. application/x-www-form-urlencoded -> 返回键值对对象
 *   3. 其他格式 -> 返回原始字符串
 */
function parseBody(req) {
    return new Promise((resolve, reject) => {
        let body = '';
        
        // 监听数据事件:接收请求体数据块
        req.on('data', chunk => {
            body += chunk.toString();  // 将Buffer转为字符串并拼接
        });
        
        // 监听结束事件:所有数据接收完毕
        req.on('end', () => {
            try {
                const contentType = req.headers['content-type'] || '';
                
                // 根据Content-Type解析不同格式的数据
                if (contentType.includes('application/json')) {
                    // JSON格式:{"name":"张三","age":25}
                    resolve(JSON.parse(body));
                } else if (contentType.includes('application/x-www-form-urlencoded')) {
                    // 表单格式:name=张三&age=25
                    resolve(querystring.parse(body));
                } else {
                    // 其他格式:直接返回字符串
                    resolve(body);
                }
            } catch (error) {
                reject(error);  // 解析失败,返回错误
            }
        });
        
        // 监听错误事件
        req.on('error', reject);
    });
}

// ============================================
// 6. 响应工具函数
// ============================================
/**
 * 统一发送JSON格式的响应
 * @param {object} res - HTTP响应对象
 * @param {number} statusCode - HTTP状态码(200、404、500等)
 * @param {object} data - 要返回的数据(会自动转为JSON)
 * 
 * 使用示例:
 *   sendResponse(res, 200, { code: 0, data: user, message: 'success' })
 */
function sendResponse(res, statusCode, data) {
    res.statusCode = statusCode;                           // 设置状态码
    res.setHeader('Content-Type', 'application/json');     // 设置响应头为JSON格式
    res.end(JSON.stringify(data));                         // 将数据转为JSON并发送
}

// ============================================
// 7. 定义具体的路由处理函数
// ============================================

// 7.1 GET /api/users - 获取所有用户列表
addRoute('GET', '/api/users', async (req, res) => {
    // 模拟数据库中的用户数据
    const users = [
        { id: 1, name: '张三', age: 25 },
        { id: 2, name: '李四', age: 30 },
        { id: 3, name: '王五', age: 28 }
    ];
    
    // 返回成功响应
    sendResponse(res, 200, { 
        code: 0,          // 业务状态码:0表示成功
        data: users,      // 实际数据
        message: 'success' 
    });
});

// 7.2 GET /api/users/:id - 根据ID获取单个用户
addRoute('GET', '/api/users/:id', async (req, res, params) => {
    // 从URL参数中获取ID并转为数字
    const id = parseInt(params.id);
    
    // 模拟数据库中的用户数据
    const users = [
        { id: 1, name: '张三', age: 25 },
        { id: 2, name: '李四', age: 30 },
        { id: 3, name: '王五', age: 28 }
    ];
    
    // 在数组中查找ID匹配的用户
    const user = users.find(u => u.id === id);
    
    if (user) {
        // 找到用户:返回200成功
        sendResponse(res, 200, { 
            code: 0, 
            data: user, 
            message: 'success' 
        });
    } else {
        // 没找到用户:返回404未找到
        sendResponse(res, 404, { 
            code: 404, 
            message: '用户不存在' 
        });
    }
});

// 7.3 POST /api/users - 创建新用户
addRoute('POST', '/api/users', async (req, res) => {
    try {
        // 解析请求体中的JSON数据
        const body = await parseBody(req);
        const { name, age } = body;
        
        // 验证必填参数
        if (!name || !age) {
            return sendResponse(res, 400, { 
                code: 400, 
                message: '缺少必要参数: name, age' 
            });
        }
        
        // 创建新用户(使用时间戳作为ID)
        const newUser = { 
            id: Date.now(),      // 用时间戳作为唯一ID
            name, 
            age: parseInt(age) 
        };
        
        // 返回创建成功(201状态码)
        sendResponse(res, 201, { 
            code: 0, 
            data: newUser, 
            message: '创建成功' 
        });
    } catch (error) {
        // JSON解析失败,返回400错误
        sendResponse(res, 400, { 
            code: 400, 
            message: '参数格式错误,请使用JSON格式' 
        });
    }
});

// ============================================
// 8. 404处理函数(路由未匹配)
// ============================================
/**
 * 处理未匹配的路由,返回404错误
 */
function handleNotFound(req, res) {
    sendResponse(res, 404, { 
        code: 404, 
        message: '接口不存在' 
    });
}

// ============================================
// 9. 创建HTTP服务器
// ============================================
const server = http.createServer(async (req, res) => {
    // 9.1 设置CORS跨域响应头
    res.setHeader('Access-Control-Allow-Origin', '*');  // 允许所有域名访问
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');  // 允许的HTTP方法
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');  // 允许的请求头
    
    // 9.2 处理OPTIONS预检请求
    // 浏览器在发送复杂请求前会先发送OPTIONS请求,这里直接返回200
    if (req.method === 'OPTIONS') {
        res.statusCode = 200;
        res.end();
        return;
    }

    try {
        // 9.3 解析请求URL
        const parsedUrl = url.parse(req.url, true);  // true表示解析查询参数
        const pathname = parsedUrl.pathname;         // 获取路径部分,如 /api/users/1
        const method = req.method;                   // 获取HTTP方法,如 GET
        
        // 9.4 记录请求日志
        console.log(`${new Date().toISOString()} - ${method} ${pathname}`);
        
        // 9.5 精确匹配路由
        // 检查是否有完全匹配的路由,如 '/api/users'
        if (routes[method] && routes[method][pathname]) {
            await routes[method][pathname](req, res);
            return;
        }
        
        // 9.6 动态参数匹配路由(支持 :id 这种格式)
        // 遍历所有已注册的路由,尝试匹配带参数的路由
        const routeKeys = Object.keys(routes[method] || {});
        for (const route of routeKeys) {
            // 将路由路径和请求路径都按 '/' 分割成数组
            const routeParts = route.split('/');   // ['', 'api', 'users', ':id']
            const pathParts = pathname.split('/'); // ['', 'api', 'users', '1']
            
            // 只有路径长度相同才可能匹配
            if (routeParts.length === pathParts.length) {
                const params = {};  // 存储提取的动态参数
                let match = true;   // 是否匹配的标志
                
                // 逐个比较路径的每一部分
                for (let i = 0; i < routeParts.length; i++) {
                    if (routeParts[i].startsWith(':')) {
                        // 如果路由部分以 ':' 开头,说明是动态参数
                        // 把对应的值存入params对象
                        params[routeParts[i].slice(1)] = pathParts[i];
                        // 例如:':id' -> '1',存入 params.id = '1'
                    } else if (routeParts[i] !== pathParts[i]) {
                        // 如果是静态部分但不匹配,则匹配失败
                        match = false;
                        break;
                    }
                }
                
                // 如果所有部分都匹配成功,调用对应的处理函数
                if (match) {
                    await routes[method][route](req, res, params);
                    return;
                }
            }
        }
        
        // 9.7 没有匹配到任何路由,返回404
        handleNotFound(req, res);
    } catch (error) {
        // 9.8 捕获所有未处理的错误,返回500
        console.error('服务器错误:', error);
        sendResponse(res, 500, { 
            code: 500, 
            message: '服务器内部错误' 
        });
    }
});

// ============================================
// 10. 启动服务器
// ============================================
server.listen(PORT, () => {
    console.log(`✅ 服务器运行在 http://${HOST}:${PORT}`);
    console.log(`📋 可用接口:`);
    console.log(`   GET    http://${HOST}:${PORT}/api/users`);
    console.log(`   GET    http://${HOST}:${PORT}/api/users/:id`);
    console.log(`   POST   http://${HOST}:${PORT}/api/users`);
    console.log(`\n💡 提示: 按 Ctrl+C 停止服务器`);
});

// ============================================
// 11. 优雅关闭(处理进程终止信号)
// ============================================
/**
 * 当收到 SIGTERM 信号时(如使用PM2停止),优雅地关闭服务器
 */
process.on('SIGTERM', () => {
    console.log('收到终止信号,正在关闭服务器...');
    server.close(() => {
        console.log('服务器已关闭');
        process.exit(0);  // 退出进程
    });
});

// 处理 Ctrl+C 强制退出
process.on('SIGINT', () => {
    console.log('\n收到中断信号,正在关闭服务器...');
    server.close(() => {
        console.log('服务器已关闭');
        process.exit(0);
    });
});
相关推荐
kiracrimson1 小时前
npm 入门:包管理器、依赖与 npx
前端·npm·node.js
花花鱼3 小时前
使用 nvm 管理多 Node.js 版本 + pnpm 完整实操,解决 pnpm Node 版本报错
node.js
定栓4 小时前
Webpack迁到Vite第四节:SVG 图标系统
前端·webpack·node.js
小天源17 小时前
智叟云集系统架构解析:从品牌 AI 可见度监测到前后端协同
人工智能·react.js·系统架构·node.js·react·ai监测·ai品牌监测
用户6122716650241 天前
电费 9 毛/天:我让一台 9 年前的旧 MacBook 替我上班,它比入门云服务器还能打
node.js
PegasusYu1 天前
Node.js使用nedb轻量级数据库测试脚本
数据库·electron·node.js·脚本·测试·轻量级·nedb
WebInfra1 天前
Rsdoctor 2.0 发布:Agent 驱动的全自动优化工具
前端·javascript·node.js
DevUp2 天前
标着 JavaScript 的 Wiki 与知识库:既不是 CMS 也不是 SSG(番外三)
javascript·node.js·cms
毕业设计7032 天前
(免费领源码)基于node的学生公寓管理系统06412- java、PHP、python、C#、小程序、大数据、单片机、网络工程等)
vue.js·mysql·node.js·maven·mybatis·express·b/s