前端也能轻松上手:Express + MongoDB 搭建你的第一个后端服务

前言

对于前端开发者而言,若希望快速搭建一个轻量级后端服务,Express 配合 MongoDB 是一个高效且易上手的选择。该技术栈特别适用于对开发效率要求高、业务数据结构相对独立、关联性不强的场景。若你的业务涉及复杂的关联关系或高事务一致性要求,建议考虑使用关系型数据库(如 MySQL)或更全面的后端框架,以避免后期维护上的困难。

我将以一个"账单管理系统"为例,带你从零开始搭建一个具备用户认证、数据增删改查等常见功能的 API 服务。无论你是想学习后端开发,还是需要为自己前端项目提供接口支持,本文都将为你提供清晰的实现路径。

一、项目创建

安装express-generator工具创建Express项目骨架,

less 复制代码
npm i express-generator -g
//使用该工具快速搭建项目结构和基础代码。
express -e projectName  //-e:添加ejs页面模板支持
npm install //安装依赖

我们将在生成的基础上,调整为一个更清晰的服务端 API 项目结构:

bash 复制代码
accounts/
├── app.js              # 应用入口
├── bin/www             # 服务启动脚本
├── config/config.js    # 配置文件(数据库、JWT 密钥等)
├── db/db.js            # 数据库连接
├── models/             # 数据模型
│   ├── AccountModel.js
│   └── UserModel.js
├── middlewares/        # 中间件
│   └── checkTokenMiddleware.js
├── routes/             # 路由
│   ├── api/            # API 接口
└── public/             # 静态资源

二、路由设计

2.1 路由模块化

这里定义的路由就是前端请求的接口,你肯定再熟悉不过了。Express 使用 express.Router() 实现路由模块化,便于管理和维护:

javascript 复制代码
// routes/api/accounts.js
const express = require('express');
const router = express.Router();

router.get('/account/:id', (req, res) => {
  let { id } = req.params;  // 获取路由参数
  /* ... */ 
});

router.post('/account', function(req, res) { /* ... */ });

module.exports = router;

在入口文件中挂载路由:

javascript 复制代码
// app.js
var accountRouter = require('./routes/api/accounts');
app.use('/api', accountRouter);  // 前缀 /api

2.2 RESTful API 设计

本项目的 API 遵循 RESTful 规范:

方法 路径 功能
GET /api/account 获取账单列表
POST /api/account 创建账单
GET /api/account/:id 获取单个账单
PATCH /api/account/:id 更新账单
DELETE /api/account/:id 删除账单

三、项目中的中间件

怎么理解 Express中的中间件,你还记得vue中的插件吗?

我觉得可以将 Express 中间件看作是 Vue 插件的"服务器端版本":

  • 都遵循 "单一职责原则" - 每个中间件/插件只做一件事
  • 都通过 "注入"方式 增强核心框架
  • 都有 "生命周期" 概念(中间件的 next() vs 插件的 install)
  • 都支持 "链式组合" (中间件链 vs 插件组合)

不过记住,Express 中间件更侧重于请求处理 ,而 Vue 插件更侧重于应用扩展

3.1 全局中间件

我们可以在应用入口app.js文件中看到大量中间件的注册:

php 复制代码
//app.js
var express = require('express');
var path = require('path');
var logger = require('morgan');
//导入路由
var accountRouter = require('./routes/api/accounts');
var authApiRouter = require('./routes/api/auth');

//导入服务配置
const {DBHOST, DBPORT, DBNAME} = require('./config/config');

var app = express();

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

app.use(logger('dev'));
// 解析 JSON 请求体
app.use(express.json());
// 解析 URL 编码的请求体
app.use(express.urlencoded({ extended: false }));
// 托管静态资源
app.use(express.static(path.join(__dirname, 'public')));

app.use('/api', accountRouter);
app.use('/api', authApiRouter);

//错误处理
app.use(function(req, res, next) {
  res.render('404');
});

//错误处理
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

3.2 应用中间件到路由

我们也可以为特定路由添加中间件,实现全局路由鉴权(是不是就跟vue中的路由守卫差不多了!):

javascript 复制代码
//routes/api/account.js
let checkTokenMiddleware = require('../../middlewares/checkTokenMiddleware');

// 所有 /account 相关路由都需要 token 验证
router.get('/account', checkTokenMiddleware, function(req, res) {
  // ...
});

router.post('/account', checkTokenMiddleware, function(req, res) {
  // ...
});

这里的中间件函数checkTokenMiddleware具体的作用我们在后面用户鉴权详细讲解。

四、数据库MongoDB安装

访问mongodb官方地址下载MongoDB Community Server

www.mongodb.com/try/downloa...

选择相应配置进行下载

配置步骤如下:

  • 将压缩包移动到 C:\Program Files 下,然后解压
  • 创建 C:\data\db 目录,mongodb 会将数据默认保存在这个文件夹
  • 以 mongodb 中 bin 目录作为工作目录,启动命令行
  • 运行命令 mongod

命令行窗口看到以下效果则代表MongoDB的服务启动成功了,我们便可以在项目中将api和数据库相结合。

五、数据库集成

npm i mongoose

5.1 MongoDB 连接封装

javascript 复制代码
// db/db.js
//参数success接收一个回调函数
module.exports = function (success, error) {
  const mongoose = require('mongoose');
  const { DBHOST, DBPORT, DBNAME } = require('../config/config.js');

  mongoose.set('strictQuery', true);
  mongoose.connect(`mongodb://${DBHOST}:${DBPORT}/${DBNAME}`);

  mongoose.connection.once('open', () => {
    success();
  });

  mongoose.connection.on('error', () => {
    error();
  });
};

将服务器启动脚本作为回调函数传入到db封装函数中

ini 复制代码
```javascript
// bin/wwww
const db = require('../db/db')

db(() => {
  var app = require('../app');
  var http = require('http');
  //启动服务,监听端口
  var port = normalizePort(process.env.PORT || '80');
  app.set('port', port);
  var server = http.createServer(app);
  server.listen(port);
  //...
  }
)
 

通过上面的操作我们借助mongoose完成了对数据库的连接

5.2 Mongoose 模型定义

/account这个api模块涉及的字段、类型校验定义成一个模型

javascript 复制代码
// models/AccountModel.js
const mongoose = require('mongoose');

let AccountSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  },
  time: Date,
  type: {
    type: Number,
    default: -1  // -1 支出,1 收入
  },
  account: {
    type: Number,
    required: true
  },
  remarks: String
});

let AccountModel = mongoose.model('accounts', AccountSchema);
module.exports = AccountModel;

结合数据模型,我们最后来完善api接口跟数据库的交互

javascript 复制代码
//routes/api/account.js
//导入 express
const express = require('express');

//导入中间件
let checkTokenMiddleware = require('../../middlewares/checkTokenMiddleware');

const router = express.Router();
//导入 moment
const moment = require('moment');
const AccountModel = require('../../models/AccountModel');

//记账本的列表
router.get('/account', checkTokenMiddleware, function (req, res, next) {
  //读取集合信息
  AccountModel.find().sort({ time: -1 }).exec((err, data) => {
    if (err) {
      res.json({
        code: '1001',
        msg: '读取失败~~',
        data: null
      })
      return;
    }
    //响应成功的提示
    res.json({
      //响应编号
      code: '0000',
      //响应的信息
      msg: '读取成功',
      //响应的数据
      data: data
    });
  })
});

//新增记录
router.post('/account', checkTokenMiddleware, (req, res) => {
  //表单验证

  //插入数据库
  AccountModel.create({
    ...req.body,
    //修改 time 属性的值
    time: moment(req.body.time).toDate()
  }, (err, data) => {
    if (err) {
      res.json({
        code: '1002',
        msg: '创建失败~~',
        data: null
      })
      return
    }
    //成功提醒
    res.json({
      code: '0000',
      msg: '创建成功',
      data: data
    })
  })
});

//删除记录
router.delete('/account/:id', checkTokenMiddleware, (req, res) => {
  //获取 params 的 id 参数
  let id = req.params.id;
  //删除
  AccountModel.deleteOne({ _id: id }, (err, data) => {
    if (err) {
      res.json({
        code: '1003',
        msg: '删除账单失败',
        data: null
      })
      return;
    }
    //提醒
    res.json({
      code: '0000',
      msg: '删除成功',
      data: {}
    })
  })
});

//获取单个账单信息
router.get('/account/:id', checkTokenMiddleware, (req, res) => {
  //获取 id 参数
  let { id } = req.params;
  //查询数据库
  AccountModel.findById(id, (err, data) => {
    if (err) {
      return res.json({
        code: '1004',
        msg: '读取失败~~',
        data: null
      })
    }
    //成功响应
    res.json({
      code: '0000',
      msg: '读取成功',
      data: data
    })
  })
});

//更新单个账单信息
router.patch('/account/:id', checkTokenMiddleware, (req, res) => {
  //获取 id 参数值
  let { id } = req.params;
  //更新数据库
  AccountModel.updateOne({ _id: id }, req.body, (err, data) => {
    if (err) {
      return res.json({
        code: '1005',
        msg: '更新失败~~',
        data: null
      })
    }
    //再次查询数据库 获取单条数据
    AccountModel.findById(id, (err, data) => {
      if (err) {
        return res.json({
          code: '1004',
          msg: '读取失败~~',
          data: null
        })
      }
      //成功响应
      res.json({
        code: '0000',
        msg: '更新成功',
        data: data
      })
    })

  });
});

module.exports = router;

到了这一步我们就真正完成了一个模块的接口开发,你就不再只是一个单纯的前端开发者了,即使后面道阻且长!

六、用户认证

6.1 登录功能的实现(基于 JWT)

npm i jsonwebtoken

定义user用户模型

javascript 复制代码
//导入 mongoose
const mongoose = require('mongoose');
//创建文档的结构对象
//设置集合中文档的属性以及属性值的类型
let UserSchema = new mongoose.Schema({
  //标题
  username: String,
  password: String
});

//创建模型对象  对文档操作的封装对象
let UserModel = mongoose.model('users', UserSchema);

//暴露模型对象
module.exports = UserModel;

实现登录接口,用户登录成功后,服务器签发一个 JWT Token 返回给客户端:

javascript 复制代码
//routes/api/auth.js
var express = require('express');
var router = express.Router();
//导入 jwt
const jwt = require('jsonwebtoken');
//导入配置文件
const { secret } = require('../../config/config')
//导入 用户的模型
const UserModel = require('../../models/UserModel');
const md5 = require('md5');

//登录操作
router.post('/login', (req, res) => {
  //获取用户名和密码
  let { username, password } = req.body;

  //查询数据库
  UserModel.findOne({ username: username, password: md5(password) }, (err, data) => {
    //判断
    if (err) {
      res.json({
        code: '2001',
        msg: '数据库读取失败~~~',
        data: null
      })
      return
    }
    //判断 data
    if (!data) {
      return res.json({
        code: '2002',
        msg: '用户名或密码错误~~~',
        data: null
      })
    }

    //创建当前用户的 token
    let token = jwt.sign({
      username: data.username,
      _id: data._id
    }, secret, {
      expiresIn: 60 * 60 * 24 * 7
    });

    //响应 token
    res.json({
      code: '0000',
      msg: '登录成功',
      data: token
    })

  })

});

//退出登录
router.post('/logout', (req, res) => {
  //销毁 session
  req.session.destroy(() => {
    res.render('success', { msg: '退出成功', url: '/login' });
  })
});

//注册用户
router.post('/reg', (req, res) => {
  //做表单验证
  //获取请求体的数据
  UserModel.create({ ...req.body, password: md5(req.body.password) }, (err, data) => {
    if (err) {
      res.status(500).send('注册失败, 请稍后再试~~');
      return
    }
    res.json({
      code: '0000',
      msg: '注册成功',
    })
  })
});

module.exports = router;

客户端将token存放在接口的请求头中传递给服务端,服务端定义token校验中间件,路由调用该中间件从而实现后续业务接口的登录认证。

javascript 复制代码
// middlewares/checkTokenMiddleware.js
const jwt = require('jsonwebtoken');
const { secret } = require('../config/config');

module.exports = (req, res, next) => {
  // 从请求头获取 token
  let token = req.get('token');

  if (!token) {
    return res.json({
      code: '2003',
      msg: 'token 缺失',
      data: null
    });
  }

  // 校验 token
  jwt.verify(token, secret, (err, data) => {
    if (err) {
      return res.json({
        code: '2004',
        msg: 'token 校验失败',
        data: null
      });
    }
    // 保存用户信息到请求对象
    req.user = data;
    //放行
    next();
  });
};

到这一步我们的后台服务就形成了完成的闭环。

七、服务启动

npm i nodemon

使用 Nodemon 热重载

json 复制代码
// package.json
{
  "scripts": {
    "start": "nodemon ./bin/www"
  }
}

npm start

八、接口功能验证

我们通过接口管理工具apifox来验证下

先调注册接口

登录接口

请求业务接口

到这一步我们就大功告成了。通过这个项目应该可以让你理解后端开发的基本流程,同时也为前端项目提供了可扩展的 API 支持,后端学习及软件开发的学习的路没有终点,当然我们探索的脚步也不会停下!

相关推荐
明天有专业课2 小时前
穿搭式的设计模式-装饰者
后端
a努力。2 小时前
中国电网Java面试被问:Dubbo的服务目录和路由链实现
java·开发语言·jvm·后端·面试·职场和发展·dubbo
爬山算法2 小时前
Hibernate(42)在Hibernate中如何实现分页?
java·后端·hibernate
爱码猿3 小时前
Springboot结合thymeleaf模板生成pdf文件
spring boot·后端·pdf
IT_陈寒3 小时前
SpringBoot 3.2实战:5个性能优化技巧让你的应用提速50%
前端·人工智能·后端
上进小菜猪3 小时前
基于 YOLOv8 的农作物叶片病害、叶片病斑精准识别项目 [目标检测完整源码]
后端
老毛肚3 小时前
Spring源码探究2.0
java·后端·spring
程序员鱼皮3 小时前
你的 IP 归属地,是咋被挖出来的?
前端·后端·计算机·程序员·互联网·编程经验
fisher_sky4 小时前
流媒体服务mediamtx和FFMpeg工具链联合实验
后端