likes
comments
collection
share

EggJS系列 | 只会JS也能写后端API

作者站长头像
站长
· 阅读数 50

前端也能编写Api

作为一名前端开发人员,我们开发的项目往往离不开后端的Api支持,我们需要与后端配合完成数据联调,数据存放等交互操作,这是单纯前端完成不了的事情,但是有没有什么方法可以让前端人员快速掌握写出Api和服务器打交道呢?

答案就是Node.js

Node.js

相信前端同学不少接触Node.js,允许在服务器端运行JavaScript代码,可以打破了前端和后端技术的界限,前端开发者也能够使用熟悉的JavaScript代码来编写后端服务,从而实现了从前端到后端的全栈开发能力

大家也多多少少会听到expressjskoa2等快速编写Api的框架,但这两个框架因为灵活,门槛低被受开发者喜爱,但是缺点是当您的项目越大,文件数量偏多的情况下,可能不太好管理,因为自由度很高,所以我不太考虑使用他们,更多的是在写Demo时,去使用它们

// 就像这样子,一个用koa2编写的简单Api就诞生了
// 导入Koa模块
const Koa = require('koa');

// 创建一个新的Koa应用实例
const app = new Koa();

// 定义一个路由处理器,返回"Hello, World!"
app.use(async ctx => {
    ctx.body = 'Hello, World!';
});

// 启动服务器,监听3000端口
app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

更多的选择

除了expresskoa框架,我们还能选择什么nodejs框架,答案有很多,比如比较火的nest.jsmidway.jsfastify.jsegg.js等框架

我们应该怎么选择一个适合我们的框架呢,这里就得先说一下我们的需求

  • 前端开发者能够更快上手编写Api
  • 框架学习成本较低

最后我们选择的egg.js这个框架,为什么选择它

  • 相比koa框架会有多一点的约束和结构,减低的学习成本
  • eggjs同样拥有高度的扩展性,它也可以直接使用koa的中间件
  • 面向复杂项目,也提供了性能优化,安全控制,分布式部署,日志管理等方案
  • 如果遇到问题在网上可查到的资料不少

快速入门Egg.js

打断一下,本文章主要讲重要的部分,一些小细节可能会被遗漏,如果您先完整学习Egg.js框架请移步(Egg.js官网)[www.eggjs.org/zh-CN/intro]

该文章不会讲得很详细,主要还是快速起步Api为主

快速搭建一个eggjs项目

npm init egg --type=simple

在创建项目的时候,这里有多个选择方向,我们这里选择simple,因为我们只要一个简单的模板

EggJS系列 | 只会JS也能写后端API

接下来,根据提示,将项目作者,描述信息填写下即可

打开项目目录,执行npm i命令进行依赖安装(可能有点小久~)

推荐使用pnpm管理器,性能更好更加快的node包管理工具

npm run dev

当项目跑起来之后,您会看到控制台上有这么几行提示,告诉我们服务启动在http://127.0.0.1:7001

> egg@1.0.0 dev
> egg-bin dev

[egg-ts-helper] create typings\app\controller\index.d.ts (2ms)
[egg-ts-helper] create typings\config\index.d.ts (16ms)
[egg-ts-helper] create typings\config\plugin.d.ts (1ms)
[egg-ts-helper] create typings\app\index.d.ts (1ms)
2024-07-06 15:18:12,442 INFO 45472 [master] node version v20.15.0
2024-07-06 15:18:12,443 INFO 45472 [master] egg version 3.26.1
2024-07-06 15:18:14,985 INFO 45472 [master] agent_worker#1:44516 started (2540ms)
2024-07-06 15:18:18,280 INFO 45472 [master] egg started on http://127.0.0.1:7001 (5837ms)

我们直接用浏览器访问它即可

会在浏览器页面中看到hi,egg的字样,这是因为我们访问这个链接时,路由指向到了controller/home.js中的index

EggJS系列 | 只会JS也能写后端API

这里的home.index其实也是一个Api,一个Get请求的Api

快速crud

Egg.js路由提供了一个便捷的方式来创建RESTful风格的路由,我们只需要在路由上编写一个路由地址即可

module.exports = app => {
  const { router, controller } = app;
  router.get('/', controller.home.index);
  // Restful Api
  router.resources('/books', controller.books.index);
};

这一行代码相当于创建了7个路由地址,请求方法分别是GetPOSTPUTDELETE,同样我们也要在controller里添加对应的处理方法

HTTP方法路由路径路由名称控制器方法
GET/booksbooksapp.controllers.books.index
GET/books/newnew_bookapp.controllers.books.new
GET/books/:idbookapp.controllers.books.show
GET/books/:id/editedit_bookapp.controllers.books.edit
POST/booksbooksapp.controllers.books.create
PUT/books/:idbookapp.controllers.books.update
DELETE/books/:idbookapp.controllers.books.destroy

我们在controller文件夹中新建books.js,并编写路由对应的方法

const { Controller } = require('egg');

class BooksController extends Controller {
  // 获取所有书籍列表
  async index() {
    const { ctx } = this;
    try {
      const books = await ctx.service.books.list();
      ctx.body = books;
    } catch (error) {
      ctx.status = 500;
      ctx.body = { error: '获取书籍列表失败' };
    }
  }

  // 新建书籍页面的渲染或数据准备
  async new() {
    const { ctx } = this;
    ctx.body = '显示新建书籍表单或数据准备';
  }

  // 获取单个书籍详情
  async show() {
    const { ctx } = this;
    const { id } = ctx.params;
    try {
      const book = await ctx.service.books.find(id);
      if (!book) {
        ctx.status = 404;
        ctx.body = { error: '未找到该书籍' };
      } else {
        ctx.body = book;
      }
    } catch (error) {
      ctx.status = 500;
      ctx.body = { error: '获取书籍详情失败' };
    }
  }

  // 编辑书籍页面的渲染或数据准备
  async edit() {
    const { ctx } = this;
    const { id } = ctx.params;
    try {
      const book = await ctx.service.books.find(id);
      if (!book) {
        ctx.status = 404;
        ctx.body = { error: '未找到要编辑的书籍' };
      } else {
        ctx.body = `编辑ID为${id}的书籍表单`;
      }
    } catch (error) {
      ctx.status = 500;
      ctx.body = { error: '获取编辑书籍信息失败' };
    }
  }

  // 创建新书籍
  async create() {
    const { ctx } = this;
    try {
      const createdBook = await ctx.service.books.create(ctx.request.body);
      ctx.status = 201; 
      ctx.body = createdBook;
    } catch (error) {
      ctx.status = 400;
      ctx.body = { error: '创建书籍失败' };
    }
  }

  // 更新书籍信息
  async update() {
    const { ctx } = this;
    const { id } = ctx.params;
    try {
      const updatedBook = await ctx.service.books.update(id, ctx.request.body);
      if (!updatedBook) {
        ctx.status = 404;
        ctx.body = { error: '未找到要更新的书籍' };
      } else {
        ctx.body = updatedBook;
      }
    } catch (error) {
      ctx.status = 500;
      ctx.body = { error: '更新书籍信息失败' };
    }
  }

  // 删除书籍
  async destroy() {
    const { ctx } = this;
    const { id } = ctx.params;
    try {
      const result = await ctx.service.books.delete(id);
      if (!result) {
        ctx.status = 404;
        ctx.body = { error: '未找到要删除的书籍' };
      } else {
        ctx.status = 204; 
      }
    } catch (error) {
      ctx.status = 500;
      ctx.body = { error: '删除书籍失败' };
    }
  }
}

module.exports = BooksController;

上面代码中包含了service部分的引用,这里需要提一下,service是一个逻辑封装的抽象层,也是为了保持Controller的逻辑更简洁,提高代码复用率

我们也需要把service代码也写一下,在app文件夹中创建service文件夹,接着在该文件夹下面创建books.js,并写入以下代码

// app/service/books.js
module.exports = class BooksService {
  constructor(ctx) {
    this.ctx = ctx;
    // 假数据存储
    this.books = [
      { id: 1, title: 'Node.js入门', author: '张三', year: 2026 },
      { id: 2, title: 'JavaScript高级程序设计', author: '李四', year: 2027 },
    ];
  }

  // 获取所有书籍
  async list() {
    return this.books;
  }

  // 根据ID查找书籍
  async find(id) {
    return this.books.find(book => book.id === parseInt(id));
  }

  // 创建新书籍
  async create(data) {
    // 模拟ID自增
    const newBook = { id: this.books.length + 1, ...data };
    this.books.push(newBook);
    return newBook;
  }

  // 更新书籍信息
  async update(id, updates) {
    const index = this.books.findIndex(book => book.id === parseInt(id));
    if (index !== -1) {
      this.books[index] = { ...this.books[index], ...updates };
      return this.books[index];
    }
    return null;
  }

  // 删除书籍
  async delete(id) {
    const index = this.books.findIndex(book => book.id === parseInt(id));
    if (index !== -1) {
      this.books.splice(index, 1);
      return true;
    }
    return false;
  }
};

到这里,一个简单的crud代码就写好了,是不是很简单

当我们发送一个请求时,egg.js先是走了router.js,判断请求路由找到对应的控制器(controller)中的函数去执行,最后通过赋值数据到ctx.body属性上进行返回数据

当然,这只是一个简单的例子,当我们实际开发的时候,需要考虑的东西就会变得比较多了,例如传参的校验,从数据库中调取数据等...

接下来还会涉及到ORM框架,Egg.js插件,路由中间件管理等更深的学习

我并不打算将这些部分通过理论或者案例的方式讲出来,而是想要通过简单理论+实践的方式搭建一个通用Api的模板,来向大家展示和学习

接下来我会通过多篇文章的方式,一步一步教大家如何快速构建一个后端Api应用

点个关注,追更不迷路👻

转载自:https://juejin.cn/post/7398045554922684428
评论
请登录