学习使用nodejs开发api

416 阅读3分钟

为什么会有这篇文章

2020年末,作为一个前端小菜鸟,去找工作说只会vue开发,感觉已经很没有优势了,除非将vue吃的很透,然后很清楚vue的源码的实现,做到精通的程度,那像我这样‘笨前端’,就要想一些方法,来弥补和大佬们之间的差距了,所以准备在这段时间,把nodejs和react框架学习一边,完成一套基于react和nodejs开发的后台管理系统,起码能做到对react和nodejs服务端有一定的了解,然后写这篇文章,仅仅是担心时间久了,忘了当初怎么写的,随意记录一下。

这篇帖子只针对nodejs开发,可能一篇的话会写的太长,其他的内容会分成另外几篇文章来写。然后说明一下就是这篇帖子,不是我自己个人去读nodejs的文档,自己写出来的,而是根据B站上的大佬的视频教程,一步步跟着实现的,文章尽量做到每个细节不出问题。

仅使用nodejs

顾名思义就是不使用诸如express,koa,egg,nest这些框架,实现简单的增删改查 + 登录的接口

首先项目的目录结构如下:

-bin --------------放置入口文件代码
    --www.js  ------入口文件
-src --------------这里面就是各个模块的代码
    -config ------配置文件目录
        --db.js --数据库配置文件
    -controller----控制器方法文件夹
    -db------------数据库访问目录
        --mysql.js -创建数据库链接
    -model --------各个路由的模型目录
    -router -------路由目录
index.js ----------入口文件需要引入的server
package.json ------npm包配置

这里没有列出路由,控制器,模型下面的文件,后面创建的时候会说明位置

首先/bin/www.js作为入口文件,代码如下

const http = require('http') // 引入http模块
const PORT = 3000
const serverHandle = require('../index') // 引入index.js
const server = http.createServer(serverHandle) // 创建http服务
server.listen(PORT) // 监听服务器端口
console.log(`http://localhost/${PORT}`)

/index.js中

const handleBlogRouter = require('./src/router/blog')
const handleUserRouter = require('./src/router/user')
const querystring = require('querystring') // 解析query
// 获取postdata
const getPostData = (req)=>{
  const promise = new Promise((resolve, reject)=>{
    if(req.method !== 'POST' || req.headers['content-type'] !=='application/json') {
      resolve({})
       return
    }
    let postData = ''
    req.on('data', chunk=>{
      postData += chunk.toString()
    })
    req.on('end', ()=>{
      if(!postData) return resolve({})
       resolve(JSON.parse(postData))
    })
  })
  return promise
}
const serverHandle = (req, res)=>{
  res.setHeader('Content-type', 'application/json')
  const url = req.url
  req.path = url.split('?')[0]
  req.query = querystring.parse(url.split('?')[1])  // 处理postdata
  getPostData(req).then(data=>{
    req.body = data
    const blogResult = handleBlogRouter(req, res)
    if(blogResult) {
      blogResult.then(blogData=>{
        return res.end(JSON.stringify(blogData))
      })
    }
    const userResult = handleUserRouter(req, res)
    if(userResult){
      userResult.then(userData=>{
        return res.end(JSON.stringify(userData))
      })
    }
   }).catch(()=>{
    res.writeHead(404, {"Content-type": "text/plain"})
    res.write("404 Note Found \n")
    res.end()
  })}
module.exports = serverHandle

配置数据库信息,src/config/db.js

const ENV = process.env.NODE_ENV // 获取当前环境:生产环境dev,开发环境production
let mysql_config;if (ENV === 'dev') {
  mysql_config = {
    host: 'localhost',
    user: 'root',
    password: 'root',
    port: 3306,
    database: 'myblog'
  }}
if (ENV === 'producttion') {
  mysql_config = {
    host: '127.0.0.1',
    user: 'root',
    password: 'root',
    port: 3306,
    database: 'myblog'
  }}
module.exports = { mysql_config }

连接数据库,/src/db/mysql.js

const mysql = require('mysql')
const { mysql_config } = require('../config/db')// 创建数据库链接配置
const con = mysql.createConnection(mysql_config)
con.connect();
function exec(sql) {
  const promise = new Promise((resolve, reject) => {
    con.query(sql, (err, result) => {
      if (err) {
        reject(err)
        return
      }
      resolve(result)
    })
  })
  return promise
}
module.exports = {  exec}

在router文件夹下新建 blog.js和user.js

/src/router/blog.js中实现增删改查操作路由

const { getList, getDetail, addNew, updateThis,  deleteThis } = require('../controller/blog')
const { SuccessModel, ErrorModel } = require('../model/resModel')
const handleBlogRouter = (req, res) => {
  const method = req.method
  const id = req.query.id
  let body = req.body
  if (method === 'GET' && req.path === '/api/blog/list') {
    const author = req.query.author || ''
    const keyword = req.query.keyword || ''
    const result = getList(author, keyword)
    return result.then(listData=>{
      return new SuccessModel(listData)
    })
  } 
 if (method === 'GET' && req.path === '/api/blog/detail') {
    const result = getDetail(id)
    return result.then(resData=>{
      return new SuccessModel(resData)
    })
  }
  if (method === 'POST' && req.path === '/api/blog/new') {
    const result = addNew(body)
    return result.then(insertData=>{
      return new SuccessModel(insertData)
    })
  }
  if (method === 'POST' && req.path === '/api/blog/update') {
    const result = updateThis(id, body)
    return result.then(updata=>{
      if(updata) {
        return new SuccessModel('更新成功')
      }else {
        return new ErrorModel('更新失败')
      }
    })
  }
  if (method === 'POST' && req.path === '/api/blog/delete') {
    const author = req.query.author || ''
    const result = deleteThis(id,author)
    return result.then(data=>{
      if(data) {
        return new SuccessModel('删除成功')
      }else {
        return new ErrorModel('删除失败')
      }
    })
}}
module.exports = handleBlogRouter

/src/router/user.js中,登录路由和返回

const { userLogin } = require('../controller/user')
const { SuccessModel, ErrorModel } = require('../model/resModel')
const handleUserRouter = (req, res) => {
  const method = req.method
  const body = req.body
  if (method === 'POST' && req.path === '/api/user/login') {
    const {username, password} = body
    const result = userLogin(username,password)
    return result.then(data=>{
      if(data) {
        return new SuccessModel(data)
      }else {
        return new ErrorModel('登录失败,用户名不存在或密码错误')
      }
    })
}}
module.exports = handleUserRouter

在model文件夹下新建resModel.js,用来处理请求结果返回信息

/src/model/resModel.js

// 公共返回
class BaseModel {
  constructor(data, message) {
    if(typeof data ==='string') {
      this.message = data
      data = null
      message = null
    } 
   if(data){
      this.data =data
    }
    if(message) {
      this.message = message
    }
  }}
// 成功返回
class SuccessModel extends BaseModel {
  constructor(data, message) {
    super(data, message)
    this.status = 0
  }}
// 错误返回
class ErrorModel extends BaseModel {
  constructor(data, message) {
    super(data, message)
    this.status = -1
  }}
module.exports = {  SuccessModel,  ErrorModel}

在controller中新建blog.js和user.js

/src/controller/blog.js,处理增删改查

const { exec } = require('../db/mysql')
const getList = (author, keyword) => {
  let sql = 'select * from blog where 1=1 '  
if (author) {
    sql += `and author=${author} `
  }  if (keyword) {
    sql += `and title like '%${keyword}' `
  }
  sql += `order by createtime desc`
  return exec(sql)}
const getDetail = (id) => {
  const sql = `select * from blog where id=${id}`
  return exec(sql).then(rows => {
    return rows[0]
  })}
const addNew = (data = {}) => {
  const { title, content, author, cover } = data
  const createtime = Date.now()
  const sql = `insert into blog (title, content, createtime, updatetime, author, cover) values ('${title}','${content}', '${createtime}','${createtime}', '${author}', '${cover}')`
  return exec(sql).then(result => {
    return { id: result.insertId }
  })}
const updateThis = (id, data = {}) => {
  const { title, content, author, cover } = data
  const updatetime = Date.now()
  const sql = `update blog set title='${title}', content='${content}', updatetime='${updatetime}', author='${author}', cover='${cover}' where id=${id}`
  return exec(sql).then(result => {
    if(result.affectedRows > 0) {
      return true
   }else {
      return false
    }
  })}
const deleteThis = (id, author) => {
  const sql = `delete from blog where id=${id} and author=${author}`
  return exec(sql).then(result=>{
    return result.affectedRows>0
  })}
module.exports = {  getList,  getDetail,  addNew,  updateThis,  deleteThis}

/src/controller/user.js,处理用户登录

const { exec } = require('../db/mysql')
const userLogin = (username, password)=>{
  const sql = `select username, name from user where username='${username}' and password='${password}'`
  return exec(sql).then(result=>{
    return result[0] || ''
  })}
module.exports = {  userLogin}

稍作总结:

使用node开发本质上写的还是JavaScript代码,他和JavaScript的区别,无非就是JavaScript=JavaScript API+ES规范,nodejs = nodejs API + ES规范,服务端和客户端更多的是思想上的转变;服务端拆分成MVC三部分,M:model处理数据返回,C:controller控制路由的方法,V:view暂时不考虑(因为我们没有视图),而路由router控制用户请求的路径,通过将各个路由分成模块,能够使项目结构更清晰。

最后,这个小项目中用到了nodemon热重启node服务,mysql、cross-env存取环境变量,详见package.json;

以上就是实现原生nodejs开发增删改查+用户登录的全部内容,登录部分后续会增加cookie验证,session缓存,以及密码加密功能,在下一篇文章了。

以上内容的完整代码已上传至github: 点击查看

数据库文件放在/src/db/myblog.sql