写点什么

如何构建一个简单的 Node.js REST API

  • 2020-04-21
  • 本文字数:9465 字

    阅读完需:约 31 分钟

如何构建一个简单的Node.js REST API

本文最初发布于 Christos Ploutarchou 博客,经原作者授权由 InfoQ 中文站翻译并分享。


这篇文章中,我们会使用 Express、Sequelize 和 MySQL 构建一个 Node.js Rest API。这里我们将使用 Sequelize 来与 MySQL 实例交互。

所需的应用程序

  • Dockerhttps://www.docker.com/products/docker-desktop),是一套平台即服务产品,其使用系统级虚拟化,以称为容器的软件包来交付软件。容器之间彼此隔离,各自将它们的软件、库和配置文件打包在一起;它们可以通过明确定义的通道来通信。

  • Node.jshttps://nodejs.org/en/),是基于 Chrome 的 JavaScript 运行时构建的平台,可轻松构建快速且可扩展的网络应用程序。Node.js 是一个开源、跨平台的运行时环境,用于开发服务端和网络应用程序。

  • ExpressJShttps://expressjs.com/),是 node.js 上最受欢迎的 Web 框架之一。它建立在 node.js 的 HTTP 模块之上,并增加了对路由、中间件、视图系统等特性的支持。它非常简单,体积轻巧,不像其他那些想要无所不包的框架那样臃肿,也就不会牺牲开发人员手中的灵活性,让他们可以选择自己的设计。

  • Sequelizehttps://sequelize.org/),是基于 promise,支持 Postgres、MySQL、MariaDB、SQLite 和微软 SQL Server 的 Node.js ORM。它具有可靠的事务支持、关系、急切和延迟加载及读取复制等特性。

  • CORShttps://www.npmjs.com/package/cors),是用来提供 Connect/Express 中间件的 node.js 包,可使用各种选项来启用 CORS。

  • body-parserhttps://github.com/expressjs/body-parser),在处理程序之前在一个中间件中解析传入的请求主体,它在 req.body 属性下可用。

  • Postmanhttps://www.getpostman.com/),是一个 API(应用程序编程接口)开发工具,可帮助构建、测试和修改 API。它具有发出各种 HTTP 请求(GET、POST、PUT 和 PATCH 等)的能力。

Node.js Rest CRUD API 概述

我们准备构建的 Rest API 可以按标题来创建、检索、更新、删除和查找帖子(post)。


首先我们做一个 Express Web 服务器。然后我们为 MySQL 数据库添加配置,使用 Sequelize 为 Post 创建一个模型,编写控制器。接下来,我们定义用来处理所有 CRUD 操作(包括自定义查找)的路由。


下表概述了将要导出的 Rest API



下图是我们的项目结构:


现在开始创建 Node.js 应用

首先,我们创建一个文件夹:


$ mkdir node_rest_api_with_mysql$ cd node_rest_api_with_mysql
复制代码


接下来,我们使用 package.json 文件初始化 Node.js 应用:


npm initname: (nodejs-express-sequelize-mysql) version: (1.0.0) description: Node.js Rest Apis with Express, Sequelize & MySQL.entry point: (index.js) server.jstest command: git repository: keywords: nodejs, express, sequelize, mysql, rest, api, dockerauthor: Christos Ploutarchoulicense: (ISC)Is this ok? (yes) yes
复制代码


如果你的 PC 上已经安装了 MySQL,则可以忽略以下步骤

接下来,需要为 mysql 和 phpMyAdmin 安装 docker。

  1. 安装 Docker(在此处了解有关 Docker 安装的更多信息「https://docs.docker.com/install/」)

  2. 进入项目根目录

  3. up compose


docker-compose up -d
复制代码


  • 访问 phpmyadmin


your_ip:8183Server: mysqlUsername: root/rootPassword: root/pass
复制代码


  • 在终端上访问 mysql


docker exec -it mysql_container_name mysql -u root -p
复制代码

Docker phpmyadmin ENV



我们还要在项目上安装必要的模块:express、sequelize、mysql2 和 body-parser。


运行命令:


npm install express body-parser cors  sequelize mysql2 --save
复制代码


安装完成后,package.json 文件应如下所示:


{  "name": "node_rest_api_with_mysql",  "version": "1.0.0",  "description": "Node.js Rest Api with Express, Sequelize, MySQL & phpMyAdmin .",  "main": "server.js",  "scripts": {    "start": "nodemon server.js"  },  "repository": {    "type": "git",    "url": "git+https://github.com/cploutarchou/node_rest_api_with_mysql.git"  },  "keywords": [    "node",    "rest-api",    "tutorial",    "mysql",    "phpMyAdmin",    "docker",    "node.js",    "sequilize"  ],  "author": "Christos Ploutarchou",  "license": "ISC",  "bugs": {    "url": "https://github.com/cploutarchou/node_rest_api_with_mysql/issues"  },  "homepage": "https://github.com/cploutarchou/node_rest_api_with_mysql#readme",  "dependencies": {    "body-parser": "^1.19.0",    "cors": "^2.8.5",    "express": "^4.17.1",    "mysql2": "^2.1.0",    "sequelize": "^5.21.5"  },  "devDependencies": {    "nodemon": "^2.0.2"  }}
复制代码

设置 Express Web 服务器

在我们的根目录中需要创建一个新的 server.js 文件:


const express = require("express");const bodyParser = require("body-parser");const cors = require("cors");const server = express();const db = require("./models");const corsSettings = {  originL: "http://localhost:8081"};const api = require("./routes/index");server.use(cors(corsSettings));// Parse request of content-type - application/jsonserver.use(bodyParser.json());// parse requests of content-type -application/x-www-form-urlencodedserver.use(bodyParser.urlencoded({ extended: true }));create a simple routeserver.get("/", (_req, res) => {   res.json({ message: "Welcome to node.js rest api application. Created for learning purposes by Christos Ploutarchou" });});// set listening ports for requestconst port = process.env.PORT || 8080;server.listen(port, () => {  console.log("Server running on port : " + port );});
复制代码


我们在这里做的事情是:


  • 导入 express、body-parser 和 cors 模块:

  • Express 用于构建 Rest API。

  • body-parser 帮助解析请求并创建 req.body 对象。

  • cors 提供了 Express 中间件,以多种选项启用 CORS。

  • 创建一个 Express 应用,然后使用 app.use()方法添加 body-parser 和 cors 中间件。请注意,我们设置了原点:http://localhost:8081

  • 定义一个易于测试的 GET 路由。

  • 在端口 8080 上侦听传入请求。


现在运行以下命令来运行应用:


node server.js。
复制代码


在浏览器中打开 URL http://localhost:8080/,你将看到:



正确,第一步已经完成。在下一部分中我们将动用 Sequelize。

配置 MySQL 数据库和 Sequelize

在根文件夹中,我们创建一个单独的 config 文件夹,用来使用 db.config.js 文件进行配置,如下所示:


注意:如果你不使用 docker compose 项目,则需要使用本地环境凭据和信息来更新数据库信息。


module.exports = {  HOST: "localhost",  USER: "root",  PASSWORD: "pass",  DB: "restapi",  dialect: "mysql",  pool: {    max: 10,    min: 0,    acquire: 30000,    idle: 50000  }};
复制代码


前五个参数用于 MySQL 连接。


pool 是可选的,它将用于 Sequelize 连接池配置:


  • max:池中的最大连接数

  • min:池中的最小连接数

  • idle:连接释放之前可以空闲的最长时间(以毫秒为单位)

  • acquire:在引发错误之前,该池将尝试获取连接的最长时间(以毫秒为单位)


有关更多信息,你可以访问 Sequelize 构造函数的 API 参考(https://sequelize.org/master/class/lib/sequelize.js~Sequelize.html#instance-constructor-constructor)。

初始化 Sequelize

我们将在 app/models 文件夹中初始化 Sequelize,下一步中这个文件夹里会包含模型。


现在使用以下代码创建 app/models/index.js:


const dbConfig = require("../config/db.config");const Sequelize = require("sequelize");const database = new Sequelize(dbConfig.DB, dbConfig.USER, dbConfig.PASSWORD, {  host: dbConfig.HOST,  dialect: dbConfig.dialect,  operatorsAliases: false,  pool: {    max: dbConfig.pool.max,    min: dbConfig.pool.min,    acquire: dbConfig.pool.acquire,    idle: dbConfig.pool.idle  }});const db = {};db.Sequelize = Sequelize;db.databaseConf = database;db.posts = require("./Sequelize.model")(database, Sequelize);module.exports = db;
复制代码


不要忘记在 server.js 中调用 sync()方法:


const db = require("./models");db.databaseConf.sync();
复制代码


之后,你的 server.js 文件应该如下所示:


const express = require("express");const bodyParser = require("body-parser");const cors = require("cors");const server = express();const db = require("./models");const corsSettings = {  originL: "http://localhost:8081"};const api = require("./routes/index");server.use(cors(corsSettings));// Parse request of content-type - application/jsonserver.use(bodyParser.json());// parse requests of content-type -application/x-www-form-urlencodedserver.use(bodyParser.urlencoded({ extended: true }));create a simple routeserver.get("/", (_req, res) => {   res.json({ message: "Welcome to node.js rest api application. Created for learning purposes by Christos Ploutarchou" });});// set listening ports for requestconst port = process.env.PORT || 8080;server.listen(port, () => {  console.log("Server running on port : " + port );});db.databaseConf.sync();
复制代码

定义 Sequelize 模型

在 models 文件夹中,创建 Sequelize.model.js 文件,如下所示:


module.exports = (database, Sequelize) => {  return database.define("restTutorial", {    title: {      type: Sequelize.STRING    },    description: {      type: Sequelize.TEXT    },    published: {      type: Sequelize.BOOLEAN    },    publisher: {      type: Sequelize.STRING    }  });};
复制代码


这个 Sequelize 模型表示 MySQL 数据库中的 restTutorials 表。以下列将自动生成:id、title(标题)、description(描述)、published(已发布)、createdAt、updatedAt。


初始化 Sequelize 之后我们不需要编写 CRUD 函数,Sequelize 支持下列所有功能:



这些函数将用在我们的控制器上。

创建控制器

app/controllers 文件夹中,我们使用以下 CRUD 函数创建 Post.js:


  • create

  • findAll

  • findOne

  • update

  • delete

  • deleteAll

  • findAllPublished

  • findByPublisherName


const db = require('../models')const postObj = db.postsconst Op = db.Sequelize.Op// 创建并保存一个新帖子exports.create = (request, result) => {}// 将帖子对象保存到数据库postObj.create(post).then(data => {}// 获取所有帖子 (接收带条件的数据).exports.getAllPosts = (request, result) => {}// 按ID获取帖子对象exports.getPostByID = (request, result) => {}// 按id更新一个帖子对象exports.updatePostByID = (request, result) => {}// 按ID删除帖子对象exports.deletePostByID = (request, result) => {}// 从数据库删除所有帖子对象exports.deleteAllPosts = (request, result) => {}// 获取所有已发布帖子exports.getAllPublishedPosts = (request, result) => {}// 按发布者名称获取所有帖子exports.getAllPostsByPublisherName = (request, result) => {}// 按标题获取所有已发布帖子exports.getPostByTitle = (request, result) => {}
复制代码


现在我们来实现这些函数。

创建一个新的帖子对象

// 创建并保存新帖子exports.create = (request, result) => {  if (!request.body.title) {    result.status(400).send({      message: "Content cannot be empty"    });  }  // 创建一个帖子对象  const post = {    title: request.body.title,    description: request.body.description,    published: request.body.published ? request.body.published : false,    publisher: request.body.publisher ? request.body.publisher : false  };  // 将帖子对象保存到数据库  postObj.create(post).then(data => {    result.send(data);  }).catch(err => {    result.status(500).send({      message: err.message || "Some error occurred while saving."    });  });};
复制代码

获取所有对象(按帖子标题)

// 按标题获取所有已发布帖子exports.getPostByTitle = (request, result) => {  const title = request.query.title;  postObj.findAll({    where: {      publisher: { [Op.like]: <code data-enlighter-language="generic" class="EnlighterJSRAW">%${title}%</code> },      published: true    }  }).then(data => {    result.send(data);  }).catch(err => {    result.status(500).send({      message: err.message || "Something going wrong. Unable to retrieve data!"    });  });};
复制代码


在这个函数上,我们使用 request.query.title 从 Request 中获取查询字符串,并将其视为 findAll()方法的条件。

获取单个帖子对象(按帖子 ID)

// 按ID获取帖子对象exports.getPostByID = (request, result) => {  const paramID = request.params.id;  console.log(paramID);  console.log(paramID);  postObj.findAll({    where: { id: paramID }  }).then(data => {    result.send(data);  }).catch(err => {    result.status(500).send({      message: err.message || <code data-enlighter-language="generic" class="EnlighterJSRAW">Some error occurred while retrieving data with id : ${paramID}</code>    });  });};
复制代码

按 id 更新帖子对象

// 按id更新一个帖子对象exports.updatePostByID = (request, result) => {  const id = request.params.id;  postObj.update(request.body, {    where: { id: id }  }).then(num => {    if (num === 1) {      result.send({        message: "Post object successfully updated."      });    } else {      result.send({        message: <code data-enlighter-language="generic" class="EnlighterJSRAW">Cannot update Post object with id=${id}!</code>      });    }  }).catch(err => {    result.status(500).send({      message: err.message || <code data-enlighter-language="generic" class="EnlighterJSRAW">Error while updating Post object with id=${id}!</code>    });  });};
复制代码

按 ID 删除帖子对象

// 按id删除帖子对象exports.deletePostByID = (request, result) => {  const id = request.params.id;  postObj.destroy({    where: { id: id }  }).then(num => {    if (num === 1) {      result.send({        message: "Post object successfully deleted."      });    } else {      result.send({        message: <code data-enlighter-language="generic" class="EnlighterJSRAW">Cannot delete Post object with id=${id}!</code>      });    }  }).catch(err => {    result.status(500).send({      message: err.message || <code data-enlighter-language="generic" class="EnlighterJSRAW">Cannot delete Post object with id=${id}!</code>    });  });};
复制代码

从数据库中删除所有帖子对象

// 从数据库删除所有帖子对象exports.deleteAllPosts = (request, result) => {  postObj.destroy({    where: {},    truncate: false  }).then(nums => {    result.send({      message: <code data-enlighter-language="generic" class="EnlighterJSRAW">${nums} Post objects was deleted successfully!</code>    });  }).catch(err => {    result.status(500).send({      message: err.message || "Cannot delete Post objects. Something going wrong}!"    });  });};
复制代码

获取所有已发布的帖子

// 获取所有已发布帖子exports.getAllPublishedPosts = (request, result) => {  postObj.findAll({    where: { published: true }  }).then(data => {    result.send(data);  }).catch(err => {    result.status(500).send({      message: err.message || "Something going wrong. Unable to retrieve data!"    });  });};
复制代码

从数据库获取所有已发布的帖子对象

exports.getAllPosts = (request, result) => {  postObj.findAll()    .then(data => {      result.send(data);    }).catch(err => {      result.status(500).send({        message: err.message || "Some error occurred while retrieving data."      });    });};
复制代码

按发布者名称获取所有帖子

// 按发布者名称获取所有帖子exports.getAllPostsByPublisherName = (request, result) => {  const name = request.params.name;  const condition = name ? { publisher: { [Op.like]: <code data-enlighter-language="generic" class="EnlighterJSRAW">%${name}%</code> } } : null;  postObj.findAll({ where: condition }).then(data => {    result.send(data);  }).catch(err => {    result.status(500).send({      message: err.message || "Something going wrong. Unable to retrieve data!"    });  });};
复制代码

定义路由

当客户端使用 HTTP 请求(GET、POST、PUT、DELETE)发送对一个端点的请求时,我们需要设置路由来确定服务器的响应方式。


现在我们在 route/文件夹中创建一个 index.js 文件,其内容如下:


const post = require("../controllers/Post");const express = require("express");const router = express.Router();// 创建新帖子router.post("/api/posts/create", post.create);// // 检索所有帖子router.get("/api/posts/all", post.getAllPosts);// 检索所有已发布帖子router.get("/api/posts/published", post.getAllPublishedPosts);// 按发布者名称检索所有已发布帖子router.get("/api/posts/publisher", post.getAllPostsByPublisherName);// 按标题检索所有帖子router.get("/api/posts", post.getPostByTitle);// 按id检索帖子router.get("/api/posts/:id", post.getPostByID);// // 按id更新帖子router.put("/api/post/update/:id", post.updatePostByID);// // 按id删除帖子router.delete("/api/post/delete/:id", post.deletePostByID);// 删除所有帖子router.delete("/api/posts/deleteAll", post.deleteAllPosts);module.exports = router;
复制代码


你可以看到我们使用了…controllers/Post 中的一个控制器。


我们还需要在 server.js 中包含路由(在 app.listen()之前):


const api = require("./routes/index");server.use("/", api);
复制代码


更新之后,我们的 server.js 文件应该如下所示:


const express = require("express");const bodyParser = require("body-parser");const cors = require("cors");const server = express();const db = require("./models");const corsSettings = {  originL: "http://localhost:8081"};const api = require("./routes/index");server.use(cors(corsSettings));// Parse request of content-type - application/jsonserver.use(bodyParser.json());// parse requests of content-type -application/x-www-form-urlencodedserver.use(bodyParser.urlencoded({ extended: true }));server.use("/", api);// set listening ports for requestconst port = process.env.PORT || 80;server.listen(port, () => {  console.log(<code data-enlighter-language="generic" class="EnlighterJSRAW">Server running on port : ${port}</code>);});// 如果你要删除已有的表并重新同步数据库,请运行以下函数// db.dropRestApiTable();db.databaseConf.sync();
复制代码


注意:在开发过程中,你可能需要删除现有的表并重新同步数据库。因此我们要在 models/index.js 上创建一个新函数以应用这个步骤。


在 index.js 上添加以下函数:


db.dropRestApiTable = () => {  db.databaseConf.sync({ force: true }).then(() => {    console.log("restTutorial table just dropped and db re-synced.");  });};
复制代码


要删除现有表时,可以在 server.js 文件上调用该函数:


db.dropRestApiTable();
复制代码

测试 API

使用以下命令运行我们的 Node.js 应用程序:


node server.js
复制代码


Server running on port : 80Executing (default): CREATE TABLE IF NOT EXISTS restTutorials (id INTEGER NOT NULL auto_increment , title VARCHAR(255), description TEXT, published TINYINT(1), publisher VARCHAR(255), createdAt DATETIME NOT NULL, updatedAt DATETIME NOT NULL, PRIMARY KEY (id)) ENGINE=InnoDB;Executing (default): SHOW INDEX FROM restTutorials
复制代码

我们将使用 Postman 测试以上所有的 API。

1. 使用/api/posts/create API 创建一个新帖子

下面是我们的发帖请求示例:


{    "title": "JS Tutorials : Part 1",    "description": "Node.js Rest APIs with Express, Sequelize & MySQL Part 1",    "published": true,    "publisher": "Christos Ploutarchou"}
复制代码


当我们发送发帖请求(如果数据存储在数据库上)时,我们应该会收到 STATUS: 200OK


注意:如果使用 docker-compose 运行 MySQL,则可以使用以下凭据 username: root | password: pass 在 localhost:8183 上访问 phpMyAdmin。


创建了一些新帖子后,你可以在 phpMyAdmin 上运行以下查询来检查 MySQL 表


select * from posts;
复制代码


你的输出应该如下图所示:


2. 使用 GET /api/posts/all API 检索所有帖子

你应该获得如下图所示的反馈:



获取所有帖子

3. 使用 GET /api/posts/:id API 检索所有帖子


按 ID 获取帖子

4. 使用 PUT /api/post/update/:id API 更新帖子


按 ID 更新帖子

5. 使用 GET /api/posts?title=tutorial API 查找所有包含单词“tutorials”的帖子


按标题获取帖子

6. 使用 GET /api/posts/publisher/?name=Christos API 按发布者名称查找所有帖子


按发布者名称获取所有帖子

7. 使用 GET /api/posts/published API 查找所有已发布的帖子


获取所有已发布的帖子

8. 使用 DELETE /api/posts/delete/:id API 删除帖子


按帖子 ID 删除帖子

9. 使用 DELETE /api/posts/deleteAll API 删除所有帖子


删除所有帖子


你可以通过我的 GitHub 存储库下载项目的完整副本(https://github.com/cploutarchou/node_rest_api_with_mysql)。(如果你喜欢我的项目,请留下一颗星星)


原文链接:https://christosploutarchou.com/how-to-build-simple-node-js-rest-api


2020-04-21 10:261435

评论

发布
暂无评论
发现更多内容

无限道具教程最新版9.17——大熊G

大熊G

羊了个羊

《Java面试考点大全》全网首发,带你摸熟20+互联网公司的面试考点!

收到请回复

Java 云计算 开源 架构 编程语言

每日算法刷题Day11-最大公约数、数组去重

timerring

算法题 9月月更

链游版羊了个羊-SeedX,17号上线欢迎挑战

BlockChain先知

链游版羊了个羊-SeedX,17号上线欢迎挑战

EOSdreamer111

【精通内核】Linux内核rcu(顺序)锁实现原理与源码解析

小明Java问道之路

RCU Linux内核 9月月更 CopyOnWrite rcu锁

链游版羊了个羊-SeedX,17号上线欢迎挑战

石头财经

大数据ELK(三):Lucene全文检索库介绍

Lansonli

9月月更 EKL

[MyBatisPlus]MyBatisPlus简介、入门案例

十八岁讨厌编程

Java 后端开发 9月月更

[SpringBoot]SpringBoot整合第三方技术

十八岁讨厌编程

Java 后端 9月月更

2022-09-16:以下go语言代码输出什么?A:101;B:100;C:1;D:2。 package main import ( “fmt“ ) type MS struct {

福大大架构师每日一题

golang 福大大 选择题

边缘计算技术及其安全

阿泽🧸

边缘计算 9月月更

工赋开发者社区 | 大“火”的扩散模型综述又一弹!UCF等《视觉扩散模型》综述,20页pdf详述三种通用的扩散建模框架

工赋开发者社区

加速 Amazon RDS for MySQL 只读副本提升

亚马逊云科技 (Amazon Web Services)

MySQL RDS

云办公时代SaaS行业如何保证用户使用体验,提高用户留存率?

Baklib

SaaS 帮助文档

【精通内核】Linux内核rcu锁深度解析

小明Java问道之路

RCU 内核 Linux内核 9月月更 rcu锁

Java学习--SpringBoot 常用注解详解(一)

六月的雨在InfoQ

Spring Boot Spring MVC Spring注解 9月月更

Java 学习 --SpringBoot 常用注解详解(二)

六月的雨在InfoQ

springboot 注解 Spring Bean @Component 9月月更

华为云快成长直播间云会议专场,华为员工都在用的高效办公神器

科技云未来

[SSM]异常处理

十八岁讨厌编程

Java 后端 9月月更

第四章 C语言之牛客网刷题📖笔记

Dream-Y.ocean

c 算法 9月月更

【C语言】自定义类型(结构体类型+枚举类型+联合体类型)[进阶篇_ 复习专用]

Dream-Y.ocean

c 结构体 9月月更

高级前端手写面试题

helloworld1024fd

JavaScript 前端

全新 Amazon RDS for MySQL 和 PostgreSQL 多可用区部署选项

亚马逊云科技 (Amazon Web Services)

【MySQL进阶】事务一

Aion

MySQL 事务 9月月更 InnoDB存储引擎

【指针内功修炼】函数指针 + 函数指针数组 + 回调函数(二)

Albert Edison

数组 指针 C语言 9月月更 指针数组

【MySQL 进阶】事务二

Aion

MySQL 事务 9月月更 InnoDB存储引擎

社招前端一面经典手写面试题集锦

helloworld1024fd

JavaScript 前端

实时云渲染 VS 本地渲染,全面横向对比

3DCAT实时渲染

PC市场如何再起波澜?荣耀的创新或是答案

脑极体

测试左移-快速玩转Debug

转转技术团队

问题排查 测试debug

如何构建一个简单的Node.js REST API_前端_Christos Ploutarchou_InfoQ精选文章