手搓最简前后端协作-node

目录

一、封装函数操作数据库

二、封装http服务

三、前端渲染

将三条目录下对应得三段代码分别为三个文件放到一个目录下,然后node执行就可以看效果了

就是这段时间不是在实习吗,然后想着再看看后端相关的,就是对数据的增删改查至少想要了解一下,这些其实学校也学到,但是真的就是了解了解,在工作中怎么运用起来还是需要自己再理一理,然后这篇文章就是我用最简单的js写三个文件,一个用来封装增删改查的函数,一个搭建后端服务,一个就写前端界面调用后端接口,完成对数据库的操作

一、封装函数操作数据库

想要操作mysql,就会有对应的库,就不是自己写sql语法来的,而是用别人封装好的一些库,用js语法实现对数据库的操作,类似java啊啥的也是,用maven下载好相应的包后,用对用的语法操作数据库,js的话就是npm install mysql2这样,然后直接创建函数开始操作数据库

下面的操作就是创建了一个pool对象,然后这个pool就可以对数据库进行操作了,执行sql查询语句,对数据库进行增删改查,主要是这个查询接口,将数据库中的字段和分页当前页和当前页显示数作为参数进行查询,默认显示当前页就是不传参数,后面根据搜索就是添加参数

javascript 复制代码
const mysql = require('mysql2/promise')

// 调用pool.query()执行SQL,然后返回结果,插入时返回结果result指的新增那条记录
const pool = mysql.createPool({
  host: 'localhost',
  user: 'root',
  password: 'root',
  database: 'test_db',
  connectionLimit: 10
})

async function createUser(name, age, email) {
  const [result] = await pool.query(
    'INSERT INTO user(name, age, email) VALUES(?, ?, ?)',
    [name, age, email]
  )
  console.log(`新增成功: ${result.insertId}`)
  return result.insertId
}

async function getUserList(queryParams) {
  const { page = 1, pageSize = 10, name, age, email } = queryParams

  const whereArr = []
  const params = []

  if (name !== undefined && name !== '') {
    whereArr.push('name LIKE ?') // LIKE就是模糊查询
    params.push(`%${name}%`) // 包含的就可以查
  }

  if (age !== undefined && age !== '') {
    whereArr.push('age = ?')
    params.push(age)
  }

  if (email !== undefined && email !== '') {
    whereArr.push('email LIKE ?')
    params.push(`%${email}%`)
  }

  let whereSql = ''
  if (whereArr.length > 0) {
    whereSql = 'WHERE ' + whereArr.join(' AND ')
  }

  const offset = (Number(page) - 1) * Number(pageSize)

  const sqlList = `
  SELECT * FROM user
  ${whereSql}
  LIMIT ? OFFSET ? 
  `
  const [list] = await pool.query(sqlList, [...params, Number(pageSize), offset])

  const sqlCount = `
  SELECT COUNT(*) AS total FROM user
  ${whereSql}
  `
  const [countRes] = await pool.query(sqlCount, params)
  const total = countRes[0].total

  return {
    list,
    total
  }
}

async function findUserById(id) {
  const [rows] = await pool.query('SELECT * FROM user WHERE id = ?', [id])
  console.log('查询单条:',rows[0])
  return rows[0]
}

async function deleteUser(id) {
  const [result] = await pool.query('DELETE FROM user WHERE id = ?', [id])
  console.log(`删除成功:${result.affectedRows}`)
  return result.affectedRows
}

async function updateUser(id, name, age, email) {
  const [result] = await pool.query(
    'UPDATE user SET name = ?, age = ?, email = ? WHERE id = ?',
    [name, age, email, id]
  )
  console.log(`更新成功:${result.affectedRows}`)
  const [rows] = await pool.query('SELECT * FROM user WHERE id = ?', [id])
  console.log('更新后查询单条:',rows[0])
  return rows[0]
}

module.exports = {
  createUser,
  getUserList,
  findUserById,
  deleteUser,
  updateUser
}

就是node自带了很多变量:module、require, __filename, __dirname等都是可以直接使用,不用在什么import,或者require('')导入

二、封装http服务

因为要显示网页,网页的数据要从外部获取就得用http服务,创建一个http对象(有两参数:请求来的req,和相应回去的res),res.setHeader就是设置一些请求相应的一些规则,

并在这个开启的http服务中,匹配前端传来的地址后面接的api标识和规定对应标识执行相应的功能,没啥玄乎的,就是判断字符串的变化,可以执行不同操作数据库的内容

javascript 复制代码
const http = require('http')
const fs = require('fs')
const path = require('path')
const url = require('url')
const db = require('./try3')

const server = http.createServer(async (req, res) => { 
  // req 请求对象,res 响应对象
  res.setHeader('Access-Control-Allow-Origin', '*') // 允许所有域名访问
  res.setHeader('Content-Type', 'application/json') // 返回json数据

  const urlObj = url.parse(req.url, true)
  const pathname = urlObj.pathname

  if (pathname === '/' && req.method === 'GET') {
    res.setHeader('Content-Type', 'text/html;charset=utf-8') //返回html网页,
    // 后端设置这个setHeader是:告诉前端:我返回的是JSON
    // 前端设置这个是:告诉后端:我发送的是JSON
    const html = fs.readFileSync(path.join(__dirname, './index2.html'), 'utf8')
    res.end(html)
    return
  }

  function getBody() { // 获取请求体,将一段一段的数据拼成字符串,然后转成json
    return new Promise(resolve => {
      let buf = ''
      req.on('data', chunk => buf += chunk)
      req.on('end', () => resolve(JSON.parse(buf)))
    })
  }


  try {
    // 查询所有
    if (pathname === '/api/list' && req.method === 'GET') {
      const query = urlObj.query // 获取请求参数

      const result = await db.getUserList(query)
      res.end(JSON.stringify({
        code: 200,
        msg: 'success',
        data: result.list,
        total: result.total,
        page: Number(query.page) || 1,
        pageSize: Number(query.pageSize) || 10
      }))
    }
  
    // 新增
    else if (pathname === '/api/add' && req.method === 'POST') {
      const body = await getBody()
      const id = await db.createUser(body.name, body.age, body.email)
      res.end(JSON.stringify({code: 200, msg: 'success', data: {id}}))
    }
  
    //修改
    else if (pathname === '/api/update' && req.method === 'POST') {
      const body = await getBody()
      const data = await db.updateUser(body.id, body.name, body.age, body.email)
      if (data) {
        res.end(JSON.stringify({ code: 200, msg: '更新成功', data }))
      } else {
        res.end(JSON.stringify({ code: 400, msg: '更新失败' }))
      }
    }
  
    // 删除
    else if (pathname === '/api/delete' && req.method === 'POST') {
      const body = await getBody()
      const row = await db.deleteUser(body.id)
      res.end(JSON.stringify({ code: 200, msg: '删除成功', data: {row}}))
    }
  
    else {
      res.end(JSON.stringify({ code: 404, msg: '接口不存在' }))
    }

  } catch (error) {
    res.end(JSON.stringify({ code: 500, msg: '服务器错误' }))
  }
})

const PORT = 3000
server.listen(PORT, () => {
  console.log(`服务器启动成功,监听端口:${PORT}`)
})

三、前端渲染

前端渲染页面与调用对应标识所对应的后端函数,后端函数封装有操作数据库的功能

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>用户管理CRUD</title>
  <style>
    body {
      max-width: 1200px;
      margin: 30px auto;
      padding: 0 20px;
    }
    .search-box {
      margin-bottom: 15px;
      padding: 12px;
      border: 1px solid #eee;
      border-radius: 6px;
      display: flex;
      gap: 8px;
      align-items: center;
      flex-wrap: wrap;
    }
    .mask {
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,.4);
      display: none;
      align-items: center;
      justify-content: center;
    }
    .form-box {
      background: #fff;
      border: 1px solid #ccc;
      padding: 16px;
      border-radius: 8px;
      width: 360px;
    }
    .close {
      text-align: right;
    }
    .item {
      margin: 10px 0;
      display: flex;
      align-items: center;
    }
    .item label {
      width: 70px;
    }
    input {
      padding: 6px;
      flex: 1;
      width: auto;
    }
    button {
      padding: 7px 12px;
      cursor: pointer;
      border: 1px solid #bbb;
      background: #fff;
      border-radius: 4px;
    }
    button:hover {
      background-color: #f5f5f5;
    }
    table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 15px;
    }
    th, td {
      border: 1px solid #aaa;
      padding: 9px;
      text-align: center;
    }
    th {
      background-color: #f8f8f8;
    }
    .page-box {
      margin-top: 12px;
      display: flex;
      gap: 8px;
      align-items: center;
    }
    select {
      padding: 6px;
    }
  </style>
</head>
<body>
  <h2>用户管理页面操作</h2>

  <div class="search-box">
    <input id="searchName" type="text" placeholder="姓名">
    <input id="searchAge" type="text" placeholder="年龄">
    <input id="searchEmail" type="text" placeholder="邮箱">
    <button onclick="queryUser()">搜索</button>
    <button onclick="resetSearch()">重置</button>
    <button onclick="showForm()">新增</button>
  </div>

  <!-- 遮罩+弹窗结构 -->
  <div class="mask" id="mask">
    <div class="form-box">
      <div class="close"><button onclick="closeForm()">X</button></div>
      <h4>新增/编辑用户</h4>
      <div class="item">
        <label>ID: </label>
        <input id="uid" placeholder="自动生成" disabled>
      </div>
      <div class="item">
        <label>姓名: </label>
        <input id="name" placeholder="请输入姓名">
      </div>
      <div class="item">
        <label>年龄: </label>
        <input id="age" placeholder="请输入年龄">
      </div>
      <div class="item">
        <label>邮箱: </label>
        <input id="email" placeholder="请输入邮箱">
      </div>
      <div class="item">
        <button onclick="button()">保存</button>
        <button onclick="closeForm()">取消</button>
      </div>
    </div>
  </div>

  <table>
    <thead>
      <tr>
        <th>序号</th>
        <th>ID</th>
        <th>姓名</th>
        <th>年龄</th>
        <th>邮箱</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody id="tableBody"></tbody>
  </table>

  <!-- 分页区域 -->
  <div class="page-box" id="pageBox"></div>
  
  <script>
    const baseUrl = 'http://localhost:3000';

    let tableDataList = []
    
    // 分页参数
    let pageInfo = {
      page: 1,
      pageSize: 10,
      total: 0
    }
    
    // 搜索参数
    let searchParams = {
      name: '',
      age: '',
      email: ''
    }

    // 加载列表,拼接全部查询参数 + 分页
    async function loadList() {
      let url = baseUrl + '/api/list'
      const searchStr = new URLSearchParams()
      if (searchParams.name) searchStr.append('name', searchParams.name)
      if (searchParams.age) searchStr.append('age', searchParams.age)
      if (searchParams.email) searchStr.append('email', searchParams.email)
      // 追加分页参数
      searchStr.append('page', pageInfo.page)
      searchStr.append('pageSize', pageInfo.pageSize)

      if (searchStr.size > 0) {
        url += "?" + searchStr.toString()
      }

      const res = await fetch(url)
      const json = await res.json()
      tableDataList = json.data
      pageInfo.total = json.total

      const html = tableDataList.map((item, index) => {
        const num = (pageInfo.page - 1) * pageInfo.pageSize + index + 1
        return `
          <tr>
            <td>${num}</td>
            <td>${item.id}</td>
            <td>${item.name}</td>
            <td>${item.age}</td>
            <td>${item.email}</td>
            <td>
              <button onclick="update(${item.id})">编辑</button>
              <button onclick="del(${item.id})">删除</button>
            </td>
          </tr>
        `
      }).join('')
      document.getElementById('tableBody').innerHTML = html

      renderPage()
    }

    // 渲染分页按钮
    function renderPage() {
      const totalPage = Math.ceil(pageInfo.total / pageInfo.pageSize)
      let pageHtml = `
        <span>每页</span>
        <select id="sizeSelect" onchange="changePageSize()">
          <option value="5" ${pageInfo.pageSize===5?'selected':''}>5</option>
          <option value="10" ${pageInfo.pageSize===10?'selected':''}>10</option>
          <option value="20" ${pageInfo.pageSize===20?'selected':''}>20</option>
        </select>
        <span>条</span>
        <span>共${pageInfo.total}条</span>
        <button onclick="prevPage()" ${pageInfo.page <= 1 ? 'disabled' : ''}>上一页</button>
        <span>第${pageInfo.page}/${totalPage || 1}页</span>
        <button onclick="nextPage()" ${pageInfo.page >= totalPage ? 'disabled' : ''}>下一页</button>
      `
      document.getElementById('pageBox').innerHTML = pageHtml
    }

    // 切换每页条数
    function changePageSize() {
      pageInfo.pageSize = Number(document.getElementById('sizeSelect').value)
      pageInfo.page = 1
      loadList()
    }

    // 上一页
    function prevPage() {
      if (pageInfo.page > 1) {
        pageInfo.page--
        loadList()
      }
    }
    
    // 下一页
    function nextPage() {
      const totalPage = Math.ceil(pageInfo.total / pageInfo.pageSize)
      if (pageInfo.page < totalPage) {
        pageInfo.page++
        loadList()
      }
    }

    // 填充表单(编辑)
    function fillForm(row) {
      document.getElementById('uid').value = row.id
      document.getElementById('name').value = row.name
      document.getElementById('age').value = row.age
      document.getElementById('email').value = row.email
    }

    // 清空表单
    function clearForm() {
      document.getElementById('uid').value = ''
      document.getElementById('name').value = ''
      document.getElementById('age').value = ''
      document.getElementById('email').value = ''
    }
    
    // 新增显示弹框
    function showForm() {
      document.getElementById('mask').style.display = 'flex'
    }
    
    // 关闭弹框
    function closeForm() {
      document.getElementById('mask').style.display = 'none'
      clearForm()
    }
    
    // 编辑显示弹框(根据id查找数据)
    function update(id) {
      const row = tableDataList.find(item => item.id === id)
      if (!row) return
      document.getElementById('mask').style.display = 'flex'
      fillForm(row)
    }

    // 新增和编辑的保存
    async function button() {
      const id = document.getElementById('uid').value
      if (id) { // 编辑
        const data = {
          id:id,
          name: document.getElementById('name').value,
          age: Number(document.getElementById('age').value),
          email: document.getElementById('email').value
        }
        const res = await fetch(baseUrl + '/api/update', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data)
        })
        const ret = await res.json()
        alert(ret.msg)
        closeForm()
        loadList()
      } else { // 新增
        const data = {
          name: document.getElementById('name').value,
          age: Number(document.getElementById('age').value),
          email: document.getElementById('email').value
        }
        const res = await fetch(baseUrl + '/api/add', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(data)
        })
        const ret = await res.json()
        alert(ret.msg)
        closeForm()
        loadList()
      }
    }

    // 删除
    async function del(id) {
      if (!confirm('确认删除?')) return
      await fetch(baseUrl + '/api/delete', {
        method: 'POST',
        headers: { 'Content-Type':'application/json'},
        body: JSON.stringify({id})
      })
      loadList()
    }

    // 搜索,搜索时重置回第一页
    function queryUser() {
      searchParams.name = document.getElementById('searchName').value.trim()
      searchParams.age = document.getElementById('searchAge').value.trim()
      searchParams.email = document.getElementById('searchEmail').value.trim()
      pageInfo.page = 1
      loadList()
    }

    // 重置搜索条件
    function resetSearch() {
      document.getElementById('searchName').value = ""
      document.getElementById('searchAge').value = ""
      document.getElementById('searchEmail').value = ""
      searchParams.name = ''
      searchParams.age = ''
      searchParams.email = ''
      pageInfo.page = 1
      loadList()
    }

    loadList()
  </script>
</body>
</html>

这样三个文件完成了各自分工,操作数据库的函数文件,http服务文件,前端渲染文件实现了一个简洁的管理用户界面

感觉越学越回去了,学到后面了解了vue,nestjs,各种图形库,但是用着用着就像看看底层咋来的,这个简单的html + js希望能够在2026年帮你看一看,不过现在ai确实太强大,像这种代码,两分钟不到就可以生成,也可以自己理一理

相关推荐
吳所畏惧1 小时前
宝塔面板Redis密码修改指南:SSH命令修改 vs 面板UI界面修改,哪个更靠谱?
运维·服务器·数据库·redis·缓存·ssh
DFT计算杂谈1 小时前
无 Root 权限在 Tesla K80 零门槛部署 DeepSeek 大模型
linux·服务器·网络·数据库·机器学习
HPFBoy2 小时前
log4net 数据库存日志正确配置
数据库
kyriewen2 小时前
我让AI给前端项目做了一次完整的Code Review——它和人类的差距,比我想的大得多
前端·javascript·ai编程
liuxiaowei32 小时前
【绝版教程】新版MySQL DBA高级实战进阶班 MySQL8.0 姜承尧-腾讯数据库总监
数据库·mysql·dba
破碎的南瓜2 小时前
sqli--sql注入过关笔记(1,2,3,4,5,9)
数据库·笔记·sql
এ慕ོ冬℘゜2 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
sugar__salt4 小时前
Vue3 表单双向绑定与响应式核心 API 技术详解
前端·javascript·vue.js
吠品4 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库