使用egg.js开发后端API接口系统 什么是Egg.js

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 Tair(兼容Redis),内存型 2GB
简介: 使用egg.js开发后端API接口系统什么是Egg.js

Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。详细的了解可以参考Egg.js的官网:https://eggjs.org/zh-cn/intro/

Egg.js 奉行『约定优于配置』,按照一套统一的约定进行应用开发,Egg 有很高的扩展性,可以按照团队的约定定制框架,团队内部采用这种方式可以减少开发人员的学习成本。

可以理解Egg.js是一个Node框架,同时它也是基于Koa框架基础上的框架,我们大概了解一下它的前身和主要特点即可。

它的特点有:

本篇随笔不是细说Egg.js 的详细内容,毕竟官网介绍还是比较清晰的,我们主要说使用它来做一个后端的API接口系统,后端肯定需要对数据库进行各种操作,用一个JS的方式来访问数据库,利用egg-sequelize插件,创建和数据库表进行绑定的模型进行操作,还是比较新鲜的,用了会发现确实很方便。用Egg.js来开发后端系统,相当于用前端的语言、做法,来开发后端系统了(虽然Egg.js 也可以用来做前端)。

我们知道,常规的Asp.net或者WebAPI 应用里面,一般有MVC,模型、视图、控制器这些对象,Egg.js 里面也有类似的概念,我们这里没有用用来做前端,那么可以不用它的视图(Egg.js 视图就是一个带变量的模板文件);

控制器就是我们这里用到需要为前端提供API入口和返回JSON的地方,类似我们Web API里面的控制器概念;模型这里可以理解为对数据库对象的封装对象吧;另外和我们常规前端开发一样(类似Vue+Element系统),获取数据的操作逻辑,我们可以封装在Service层,这样可以降低我们控制器里面的逻辑代码,同时也方便重用逻辑处理函数。MVC+Service的关系,大概如下所示。

 

2、 使用egg.js开发后端API接口系统所需插件

我依照官网的简单案例进行快速初始化,如下所示。

我们推荐直接使用脚手架,只需几条简单指令,即可快速生成项目(npm >=6.1.0):

$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i

启动项目:

$ npm run dev

其实我们还需要一些额外的插件来跑起来,我的包依赖文件如下所示。

package.json

{
  "name": "example",
  "version": "1.0.0",
  "description": "## Development",
  "dependencies": {
    "egg": "^2.10.0",
    "egg-cors": "^2.2.3",
    "egg-jwt": "^3.1.7",
    "egg-mysql": "^3.0.0",
    "egg-redis": "^2.4.0",
    "egg-scripts": "^2.5.0",
    "egg-sequelize": "^4.0.2",
    "egg-view-nunjucks": "^2.3.0",
    "moment": "^2.29.1",
    "mysql2": "^2.2.5",
    "node": "^15.10.0"
  },
  "devDependencies": {
    "autod": "^3.0.1",
    "autod-egg": "^1.0.0",
    "egg-bin": "^4.15.0",
    "egg-mock": "^3.19.2",
    "eslint": "^4.18.1",
    "eslint-config-egg": "^7.0.0",
    "factory-girl": "^5.0.2",
    "sequelize-cli": "^4.0.0"
  },

我们来看看红色部分的内容,其中

egg 是本身的框架需要的插件,这个是整个框架的核心基础;egg-scripts 这是部署eggjs项目的工具;

egg-corss 是跨域处理所需要的,用于设置csrf的配置等;

egg-jwt 是用于对用户身份认证的处理插件;

egg-mysql + Mysql2 是我们做Mysql数据库处理说需要的插件;

egg-redis 是我们用到redis操作,所需要的插件,可选。

egg-sequelize 是我们操作数据库的一个插件,提供很多方便的接口进行处理,可以搭配Mysql或者PostgreSQL、MS SQLServer数据库插件进行处理的

egg-view-nunjucks 是展示视图模板的一个插件。

moment 是一个日期处理插件,可以处理各种日期格式、转换的一个插件库。

大概就是这些,如果需要结合前端JS的处理插件,可以引入更多的内容,不过我们这里主要介绍后端访问Mysql数据库的处理操作,提供JSON数据接口的,基本上这些也够了。

 

另外,我们需要知道egg.js的目录很多是约定位置的,因此我们需要知道常规的几个文件夹的意义。我们简单了解下目录约定规范。

egg-project
├── package.json
├── app.js (可选)
├── agent.js (可选)
├── app
|   ├── router.js
│   ├── controller
│   |   └── home.js
│   ├── service (可选)
│   |   └── user.js
│   ├── middleware (可选)
│   |   └── response_time.js
│   ├── view (可选)
│   |   └── home.tpl
│   └── extend (可选)
│       ├── helper.js (可选)
├── config
|   ├── plugin.js
|   ├── config.default.js
│   ├── config.prod.js

我们这里大概知道以上文件夹和文件的意思即可。

  • app/router.js 用于配置 URL 路由规则,具体参见 Router
  • app/controller/** 用于解析用户的输入,处理后返回相应的结果,具体参见 Controller
  • app/service/** 用于编写业务逻辑层,可选,建议使用,具体参见 Service
  • app/middleware/** 用于编写中间件,可选,具体参见 Middleware
  • app/extend/** 用于框架的扩展,可选,具体参见框架扩展
  • config/config.{env}.js 用于编写配置文件,具体参见配置
  • config/plugin.js 用于配置需要加载的插件,具体参见插件

 

1)插件的配置

我们引入的插件模块,需要在app/plugin.js里面启用,如下代码所示。

app/plugin.js

'use strict';
exports.sequelize = {
  enable: true,
  package: 'egg-sequelize',
};
exports.mysql = {
  enable: true,
  package: 'egg-mysql',
};
exports.nunjucks = {
  enable: true,
  package: 'egg-view-nunjucks'
};
exports.redis = {
  enable: true,
  package: 'egg-redis',
};
exports.jwt = {
  enable: true,
  package: 'egg-jwt',
};
exports.cors = {
  enable: true,
  package: 'egg-cors',
};

为了访问Mysql数据库,我们还需要在config/config.default.js文件中配置好对应的关系。

config/config.default.js

'use strict';
module.exports = appInfo => {
  const config = exports = {};
  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_{{keys}}';
  config.jwt = {
    secret: '123456', //自定义token的加密条件字符串,可按各自的需求填写
  };
  // Mysql
  config.sequelize = {
    dialect: 'mysql',
    host: 'localhost',
    port: 3306,
    database: 'myprojectdb',
    username: 'root',
    password: '123456',
    define: {
      //freezeTableName默认值为false,会自动在表名后加s
      freezeTableName: true,
      // timestamps默认值为true,会自动添加create_time和update_time
      timestamps: false
    }
  };
  // csrf 安全配置
  config.security = {
    csrf: {
      enable: false,
      ignoreJSON: true
    },
    // 允许访问接口的白名单
    domainWhiteList: ['*'] // ['http://localhost:8080']
  };
  config.cors = {
    origin: '*',
    allowMethods: 'GET, HEAD, PUT, POST, DELETE, PATCH'
  };
  //........其他配置...............
  return config;
};

为了给前端提供Web API接口,我们需要为不同的业务对象提供路由入口,路由定义,统一在app/route.js文件中定义。

app/route.js

module.exports = app => {
    const { router, controller, jwt } = app; 
    router.get('/', controller.home.index);
    router.get('/news', controller.news.list);
    router.post('/login', controller.users.login);  //登录并生成Token
    router.resources('users', '/users', controller.users);
  };

以上我们users 是RESTful 的方式来定义路由, 我们提供了 app.router.resources('routerName', 'pathMatch', controller) 快速在一个路径上生成 CRUD 路由结构。

类似RESTful定义

router.resources('posts', '/api/posts', controller.posts);

我们只需要在 posts.js 里面实现对应的函数就可以了。

我这里的users实现了上面部分的接口,以提供列表展示-L、创建-C、获取-R、更新-U、删除-D等操作。

app\controller\users.js

'use strict';
 const Controller = require('egg').Controller;
//控制器类入口
//实现路由几个常规函数,包括列表及CRUD的操作
class UserController extends Controller {
  async index() { //展示列表数据-L
    const ctx = this.ctx;
    const query = {
      limit: ctx.helper.parseInt(ctx.query.limit),
      offset: ctx.helper.parseInt(ctx.query.offset),
    };
    var data = await ctx.service.user.list(query);
    var json = ctx.helper.json(data)
    ctx.body = json
  }
  async show() { //显示某记录具体的数据-R
    const ctx = this.ctx;
    ctx.body = await ctx.service.user.find(ctx.helper.parseInt(ctx.params.id));
  }
  async create() { //新增一个记录-C
    const ctx = this.ctx;
    const user = await ctx.service.user.create(ctx.request.body);
    ctx.status = 201;
    ctx.body = user;
  }
  async update() { //更新指定的记录-U
    const ctx = this.ctx;
    const id = ctx.helper.parseInt(ctx.params.id);
    const body = ctx.request.body;
    ctx.body = await ctx.service.user.update({
      id,
      updates: body
    });
  }
  async destroy() { //删除指定的记录-D
    const ctx = this.ctx;
    const id = ctx.helper.parseInt(ctx.params.id);
    await ctx.service.user.del(id);
    ctx.status = 200;
  }
}
module.exports = UserController;

这里UserController 控制器没有直接访问数据库,而是间接通过service对象进行操作数据库的。service中的user.js代码如下所示。

app\service\user.js

'use strict';
const Service = require('egg').Service;
//服务类入口,用于封装具体的数据库访问
class User extends Service {
  async login(usernameOrEmail, password) {
    var user = await this.ctx.model.User.findOne({ 
      where: {
          $or: [
            { username: usernameOrEmail },
            { emailaddress: usernameOrEmail }
          ]
      }
    });
    
    var success = false;
    var error = "";
    if(user) {
      success = true
    }
    return {
      success,
      error
    }
  }
  
  async list({ offset = 0, limit = 10 }) {
    return this.ctx.model.User.findAndCountAll({
      offset,
      limit,
      order: [[ 'creationtime', 'desc' ], [ 'id', 'desc' ]],
    });
  }
  async find(id) {
    const user = await this.ctx.model.User.findByPk(id);
    if (!user) {
      this.ctx.throw(404, 'user not found');
    }
    return user;
  }
  async create(user) {
    return this.ctx.model.User.create(user);
  }
  async update({ id, updates }) {
    const user = await this.ctx.model.User.findByPk(id);
    if (!user) {
      this.ctx.throw(404, 'user not found');
    }
    return user.update(updates);
  }
  async del(id) {
    const user = await this.ctx.model.User.findByPk(id);
    if (!user) {
      this.ctx.throw(404, 'user not found');
    }
    return user.destroy();
  }
}
module.exports = User;

而Service中,访问数据库主要通过 egg-sequelize 插件中提供的 this.ctx.model.User 对象进行操作数据库的

sequelize 是一个广泛使用的 ORM 框架,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源。

app\model\user.js

'use strict';
module.exports = app => {
  const { STRING, INTEGER, DATE } = app.Sequelize;
  const User = app.model.define('abpusers', {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    name: STRING(64),
    username: STRING(64),  
    phonenumber: STRING(64),  
    creationtime: DATE,
    lastmodificationtime: DATE,
  });
  return User;
};

sequelize 定义了数据库不同的类型,它的类型定义如下所示。

Sequelize.STRING                      // VARCHAR(255)
Sequelize.STRING(1234)                // VARCHAR(1234)
Sequelize.STRING.BINARY               // VARCHAR BINARY
Sequelize.TEXT                        // TEXT
Sequelize.TEXT('tiny')                // TINYTEXT
Sequelize.CITEXT                      // CITEXT      PostgreSQL and SQLite only.
Sequelize.INTEGER                     // INTEGER
Sequelize.BIGINT                      // BIGINT
Sequelize.BIGINT(11)                  // BIGINT(11)
Sequelize.FLOAT                       // FLOAT
Sequelize.FLOAT(11)                   // FLOAT(11)
Sequelize.FLOAT(11, 10)               // FLOAT(11,10)
Sequelize.REAL                        // REAL        PostgreSQL only.
Sequelize.REAL(11)                    // REAL(11)    PostgreSQL only.
Sequelize.REAL(11, 12)                // REAL(11,12) PostgreSQL only.
Sequelize.DOUBLE                      // DOUBLE
Sequelize.DOUBLE(11)                  // DOUBLE(11)
Sequelize.DOUBLE(11, 10)              // DOUBLE(11,10)
Sequelize.DECIMAL                     // DECIMAL
Sequelize.DECIMAL(10, 2)              // DECIMAL(10,2)
Sequelize.DATE                        // DATETIME for mysql / sqlite, TIMESTAMP WITH TIME ZONE for postgres
Sequelize.DATE(6)                     // DATETIME(6) for mysql 5.6.4+. Fractional seconds support with up to 6 digits of precision
Sequelize.DATEONLY                    // DATE without time.
Sequelize.BOOLEAN                     // TINYINT(1)
Sequelize.ENUM('value 1', 'value 2')  // An ENUM with allowed values 'value 1' and 'value 2'
Sequelize.ARRAY(Sequelize.TEXT)       // Defines an array. PostgreSQL only.
Sequelize.ARRAY(Sequelize.ENUM)       // Defines an array of ENUM. PostgreSQL only.
Sequelize.JSON                        // JSON column. PostgreSQL, SQLite and MySQL only.
Sequelize.JSONB                       // JSONB column. PostgreSQL only.
Sequelize.BLOB                        // BLOB (bytea for PostgreSQL)
Sequelize.BLOB('tiny')                // TINYBLOB (bytea for PostgreSQL. Other options are medium and long)
Sequelize.UUID                        // UUID datatype for PostgreSQL and SQLite, CHAR(36) BINARY for MySQL (use defaultValue: Sequelize.UUIDV1 or Sequelize.UUIDV4 to make sequelize generate the ids automatically)
Sequelize.CIDR                        // CIDR datatype for PostgreSQL
Sequelize.INET                        // INET datatype for PostgreSQL
Sequelize.MACADDR                     // MACADDR datatype for PostgreSQL
Sequelize.RANGE(Sequelize.INTEGER)    // Defines int4range range. PostgreSQL only.
Sequelize.RANGE(Sequelize.BIGINT)     // Defined int8range range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DATE)       // Defines tstzrange range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DATEONLY)   // Defines daterange range. PostgreSQL only.
Sequelize.RANGE(Sequelize.DECIMAL)    // Defines numrange range. PostgreSQL only.
Sequelize.ARRAY(Sequelize.RANGE(Sequelize.DATE)) // Defines array of tstzrange ranges. PostgreSQL only.
Sequelize.GEOMETRY                    // Spatial column.  PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT')           // Spatial column with geometry type. PostgreSQL (with PostGIS) or MySQL only.
Sequelize.GEOMETRY('POINT', 4326)     // Spatial column with geometry type and SRID.  PostgreSQL (with PostGIS) or MySQL only.

关于它的接口,可以参考下文档https://itbilu.com/nodejs/npm/sequelize-docs-v5.html 了解下。

另外,我们可以在app\extend\helper.js中定义一些常规的辅助函数,方便在控制器或者service对象中使用。

app\extend\helper.js

'use strict';
const moment = require('moment');
module.exports = {
  json(data, code, msg, addition) {
    return Object.assign({
      result: code ? 'fail' : 'success',
      code: code || 0,
      message: msg,
      data,
    }, addition);
  },
  parseInt(string) {
    if (typeof string === 'number') return string;
    if (!string) return string;
    return parseInt(string) || 0;
  },
  changeTime(time) {
    return moment(time * 1000).format('YYYY-MM-DD HH:mm:ss');
  },
  relativeTime(time) {
    return moment(new Date(time * 1000)).fromNow()
  },

最后,我们使用npm run dev跑项目

测试下我们用户列表部分的处理。

其他CRUD接口,可以结合C#代码进行客户端的测试,也可以在一个新建的Vue+Element前端项目中进行axios的调用,获取对应的JSON进行测试。

在使用egg.js开发的时候,总体还是很方便,不过就是有时候一些拼写错误,或者一些配置原因,控制台 提示信息不是很明确,需要自己掌握各种排错的经验才行。

 

专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
 转载请注明出处:撰写人:伍华聪  http://www.iqidi.com

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
7天前
|
存储 SQL API
探索后端开发:构建高效API与数据库交互
【10月更文挑战第36天】在数字化时代,后端开发是连接用户界面和数据存储的桥梁。本文深入探讨如何设计高效的API以及如何实现API与数据库之间的无缝交互,确保数据的一致性和高性能。我们将从基础概念出发,逐步深入到实战技巧,为读者提供一个清晰的后端开发路线图。
|
6天前
|
JSON 前端开发 API
后端开发中的API设计与文档编写指南####
本文探讨了后端开发中API设计的重要性,并详细阐述了如何编写高效、可维护的API接口。通过实际案例分析,文章强调了清晰的API设计对于前后端分离项目的关键作用,以及良好的文档习惯如何促进团队协作和提升开发效率。 ####
|
8天前
|
存储 SQL 数据库
深入浅出后端开发之数据库优化实战
【10月更文挑战第35天】在软件开发的世界里,数据库性能直接关系到应用的响应速度和用户体验。本文将带你了解如何通过合理的索引设计、查询优化以及恰当的数据存储策略来提升数据库性能。我们将一起探索这些技巧背后的原理,并通过实际案例感受优化带来的显著效果。
26 4
|
7天前
|
Web App开发 JavaScript 前端开发
深入浅出Node.js后端开发
【10月更文挑战第36天】本文将引导您探索Node.js的世界,通过实际案例揭示其背后的原理和实践方法。从基础的安装到高级的异步处理,我们将一起构建一个简单的后端服务,并讨论如何优化性能。无论您是新手还是有经验的开发者,这篇文章都将为您提供新的视角和深入的理解。
|
8天前
|
监控 API 持续交付
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在后端开发中的应用,分析了其优势、面临的挑战以及最佳实践策略。不同于传统的单体应用,微服务通过细粒度的服务划分促进了系统的可维护性、可扩展性和敏捷性。文章首先概述了微服务的核心概念及其与传统架构的区别,随后详细阐述了构建微服务时需考虑的关键技术要素,如服务发现、API网关、容器化部署及持续集成/持续部署(CI/CD)流程。此外,还讨论了微服务实施过程中常见的问题,如服务间通信复杂度增加、数据一致性保障等,并提供了相应的解决方案和优化建议。总之,本文旨在为开发者提供一份关于如何在现代后端系统中有效采用和优化微服务架构的实用指南。 ####
|
10天前
|
消息中间件 设计模式 运维
后端开发中的微服务架构实践与挑战####
本文深入探讨了微服务架构在现代后端开发中的应用,通过实际案例分析,揭示了其在提升系统灵活性、可扩展性及促进技术创新方面的显著优势。同时,文章也未回避微服务实施过程中面临的挑战,如服务间通信复杂性、数据一致性保障及部署运维难度增加等问题,并基于实践经验提出了一系列应对策略,为开发者在构建高效、稳定的微服务平台时提供有价值的参考。 ####
|
6天前
|
缓存 前端开发 API
探索后端开发中的API设计原则
【10月更文挑战第37天】本文旨在引导读者理解API设计的核心理念,通过简明的语言和直观的示例,揭示如何构建高效、稳定且易于维护的后端接口。我们将深入浅出地探讨RESTful API的设计规范,并通过一个简易的代码样例,展示如何在实战中应用这些原则。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的参考和启示。
|
14天前
|
API 持续交付 开发者
后端开发中的微服务架构实践与挑战
在数字化时代,后端服务的构建和管理变得日益复杂。本文将深入探讨微服务架构在后端开发中的应用,分析其在提高系统可扩展性、灵活性和可维护性方面的优势,同时讨论实施微服务时面临的挑战,如服务拆分、数据一致性和部署复杂性等。通过实际案例分析,本文旨在为开发者提供微服务架构的实用见解和解决策略。
|
12天前
|
Web App开发 存储 JavaScript
深入浅出Node.js后端开发
【10月更文挑战第31天】本文将引导你进入Node.js的奇妙世界,探索其如何革新后端开发。通过浅显易懂的语言和实际代码示例,我们将一起学习Node.js的核心概念、搭建开发环境,以及实现一个简单但完整的Web应用。无论你是编程新手还是希望拓展技术的开发者,这篇文章都将为你打开一扇通往高效后端开发的大门。
|
10天前
|
存储 关系型数据库 Java
探索后端开发:从基础到进阶
【10月更文挑战第33天】在这篇文章中,我们将深入探讨后端开发的各个方面,包括基本概念、关键技术和最佳实践。无论你是初学者还是有一定经验的开发者,这篇文章都将为你提供有价值的信息和启示。我们将通过代码示例来展示一些常见任务的实现方法,并分享一些实用的技巧和策略,帮助你提高后端开发的效率和质量。无论你是想学习新的编程语言还是想了解最新的后端技术趋势,这篇文章都会为你提供有益的指导和灵感。让我们一起开启后端开发的探索之旅吧!