Node.js MySQL教程:Express js REST API分步入门指南

  1. Node.js和MySQL很好地融合在一起。在此Node.js MySQL教程中,我们将研究如何逐步使用它们来使用Express js构建用于引用的REST API。
  2. Node.js MySQL教程逐步构建使用Express js引用的REST API
  3. 目录

  4. 为什么使用Node.js MySQL教程
  5. Node.js MySQL教程的先决条件
  6. Node.js MySQL教程步骤
    1. 适用于Node.js MySQL的Setup Express JS教程
      1. 删除公用文件夹
      2. 删除现有路线并为报价创建新路线
    2. 使用报价表设置MySQL
    3. 使用MySQL连接Node.js
      1. 在Express应用中安装mysql2
    4. 显示报价-获取API
      1. 对Node.js MySQL教程的引号进行分页
      2. 下一步
    5. 保存新报价-Node.js MySQL的POST API教程
  7. TLDR;我想快速运行
  8. 结论
  9. 为什么使用Node.js MySQL教程

  10. Node.js已普遍与NoSQL数据库(尤其是Mongo DB)结合在一起。如果将较旧的代码库移至Node.js,则无法选择数据库。大多数旧系统甚至新系统都使用关系数据库管理系统(例如MySQL)。这导致需要逐步的Node.js MySQL教程。
  11. 如果您要开始一个新项目,请不要盲目使用像Mongo DB这样的NoSQL。这段来自2010年关于MySQL与Mongo的有趣但准确的视频仍然很有意义。
  12. 如果项目绝对需要NoSQL数据库,则使用NoSQL数据库即可。DYp码友部落

  13. Node.js MySQL教程的先决条件

  14. 您已在计算机上安装了Node.js(或可以运行Node.js的Docker容器)。我们将使用Node.js版本12和npm 6.14。
  15. 您熟悉Node.js的一般工作原理,并且还了解Node.js框架。本指南将使用Express js。
  16. 一些git知识会很有帮助
  17. 您可以访问在本地或远程运行的MySQL实例。我建议使用远程MySQL。您应该知道RDBMS的工作方式。
  18. 您可以使用IDE进行编码,而我将使用VS代码,但是您可以针对此Node.js MySQL教程使用任何编辑器或IDE。
  19. Node.js MySQL教程步骤

  20. 我们将使用Express js构建一个简单的REST API,该API可以给出引号。在深入探讨这些步骤之前,我真的建议您对REST(代表性状态转移)进行复习最好阅读REST动词并运行一些cURL命令到POST API。
  21. 鉴于您已经运行了Node.js(无论是在计算机上还是在Docker上),我们都可以首先设置Express:
  22. 适用于Node.js MySQL的Setup Express JS教程

  23. 要设置快递,我们将使用快递生成器。您可以使用以下命令为此Node.js MySQL教程生成一个没有任何视图引擎的express js应用程序:
  24. npx express-generator --no-view --git nodejs-mysql
  25. 没有视图且git忽略的Node.js Express生成器的输出
  26. 要快速检查输出,请执行以下操作:
  27. cd nodejs-mysql && npm install && DEBUG=nodejs-mysql:* npm start
  28. 您应该在浏览器上看到以下输出:
  29. 浏览器上的默认快递输出
  30. 在此拉取请求中可以看到生成的基本Express应用程序。
  31. 删除公用文件夹

  32. 当我们使用Express构建此Node.js教程中引用的REST API时,我们将删除公用文件夹。对于本教程,我们不需要任何CSS或js,因为我们将处理JSON。
  33. 要删除公用文件夹,请运行以下命令:
  34. rm -rf public
  35. 删除现有路线并为报价创建新路线

  36. 此时,我们将对路线进行一些更改。我们将删除routes/users.js不再需要文件。然后,我们将添加routes/quoets.js如下所示文件:
  37. const express = require('express');
    const router = express.Router();
    
    /* GET quotes listing. */
    router.get('/', function(req, res, next) {
      res.json({
        data: [
          {
            quote: 'There are only two kinds of languages: the ones people complain about and the ones nobody uses.',
            author: 'Bjarne Stroustrup'
          }
        ],
        meta: {
          page: 1
        }
      });
    });
    
    module.exports = router;
  38. 现在,/routes将仅显示一个引号的静态输出,如上所示。在接下来的步骤中,我们将使其动态化。
  39. 之后,我们需要将引号路由与app.js文件链接起来链接后,我的app.js文件如下所示:
  40. const express = require('express');
    const cookieParser = require('cookie-parser');
    const logger = require('morgan');
    
    const indexRouter = require('./routes/index');
    const quotesRouter = require('./routes/quotes');
    
    const app = express();
    
    app.use(logger('dev'));
    app.use(express.json());
    app.use(express.urlencoded({ extended: false }));
    app.use(cookieParser());
    
    app.use('/', indexRouter);
    app.use('/quotes', quotesRouter);
    
    module.exports = app;
  41. 要做的另一件事是,更改index.js文件以显示JSON输出,而不是呈现视图/ HTML模板。更改之后,index.js文件如下所示:
  42. const express = require('express');
    const router = express.Router();
    
    /* GET home page. */
    router.get('/', function(req, res, next) {
      res.json({message: 'ok'});
    });
    
    module.exports = router;
  43. 我也改变了所有的varconst,因为它更有意义。如果您想一次性查看所有更改,则可以在此pull request中使用它。
  44. 您可以通过在命令行上运行以下命令来查看最近的更改:
  45. DEBUG=nodejs-mysql:* npm start
  46. 当您点击http://localhost:3000/quotes浏览器时,应该看到以下内容
  47. 引用使用Express js的Node.js MySQL教程的API静态输出
  48. 使用报价表设置MySQL

  49. 此时,对于本Node.js MySQL教程,我们将创建一个Quotes API。消费者可以获取报价,也可以添加新报价。为此,我们将使用一个名为的表quote,其结构如下:
  50. CREATE TABLE `quote` 
    ( `id` INT(11) NOT NULL AUTO_INCREMENT , 
     `quote` VARCHAR(255) NOT NULL , 
     `author` VARCHAR(255) NOT NULL , 
     `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP , 
     `updated_at` DATETIME on update CURRENT_TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , 
     PRIMARY KEY (`id`), 
     INDEX `idx_author` (`author`), UNIQUE `idx_quote_uniqie` (`quote`)
    ) 
     ENGINE = InnoDB CHARSET=utf8mb4 COLLATE utf8mb4_general_ci;
  51. 这是一个包含5列的简单表。第一个是id自动递增的主键。然后是quoteauthor这两个列都是varchar。
  52. 之后是created_atupdated_at两个日期列的默认值均为,CURRENT_TIMESTAMP因此我们无需从代码中发送这些值。此外,由于以下原因,更新行时,updated_at将自动更新:on update CURRENT_TIMESTAMP
  53. 您可以运行上面的命令来创建quote表,也可以使用以下插入SQL查询来填充一些与编程相关的良好引号:
  54. INSERT INTO `quote` (`id`, `quote`, `author`) VALUES
    (1, 'There are only two kinds of languages: the ones people complain about and the ones nobody uses.', 'Bjarne Stroustrup'),
    (3, 'Any fool can write code that a computer can understand. Good programmers write code that humans can understand.', 'Martin Fowler'),
    (4, 'First, solve the problem. Then, write the code.', 'John Johnson'),
    (5, 'Java is to JavaScript what car is to Carpet.', 'Chris Heilmann'),
    (6, 'Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.', 'John Woods'),
    (7, "I'm not a great programmer; I'm just a good programmer with great habits.", 'Kent Beck'),
    (8, 'Truth can only be found in one place: the code.', 'Robert C. Martin'),
    (9, "If you have to spend effort looking at a fragment of code and figuring out what it's doing, then you should extract it into a function and name the function after the 'what'.", 'Martin Fowler'),
    (10, 'The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.', 'Donald Knuth'),
    (11, 'SQL, Lisp, and Haskell are the only programming languages that I’ve seen where one spends more time thinking than typing.', 'Philip Greenspun'),
    (12, 'Deleted code is debugged code.', 'Jeff Sickel'),
    (13, 'There are two ways of constructing a software design: One way is to make it so simple that there are obviously no deficiencies and the other way is to make it so complicated that there are no obvious deficiencies.', 'C.A.R. Hoare'),
    (14, 'Simplicity is prerequisite for reliability.', 'Edsger W. Dijkstra'),
    (15, 'There are only two hard things in Computer Science: cache invalidation and naming things.', 'Phil Karlton'),
    (16, 'Measuring programming progress by lines of code is like measuring aircraft building progress by weight.', 'Bill Gates');
  55. 上面的查询将在表中添加15个引号,这应该是get quotes API的良好起点。请注意,id 2丢失了,这是有意完成的。您也可以在此处查看数据库初始化文件。
  56. 使用MySQL连接Node.js

  57. 在这一阶段,我们将从Node.js Express应用程序连接到MySQL。为此,我们将使用Mysql2库。如果您问为什么不使用默认Mysql。这是由于两个主要原因:
  58. Mysql2有用于包装的承诺开箱
  59. Mysql2支持更快,更安全的预备语句
  60. 如果要对这两个库进行直接比较,请直接与npm比较。
  61. 在Express应用中安装mysql2

  62. 要开始使用Nodejs查询MySQL数据库,我们首先要mysql2使用npm安装该库:
  63. npm install --save mysql2
  64. 上面的命令将安装该mysql2库并将其添加到package.json文件中。下一步是更新/quotes路由以显示数据库报价,而不是静态报价。
  65. 显示报价-获取API

  66. 当你点击http://localhost:3000启动Express JS应用后,你可以看到如下:
  67. {
      "data": [
        {
        "quote": "There are only two kinds of languages: the ones people complain about and the ones nobody uses.",
        "author": "Bjarne Stroustrup"
        }
      ],
      "meta": {
      	"page": 1
      }
    }
  68. 让我们从数据库表中提取相似的数据,quote并在其上即兴创作。
  69. 为了显示多个引号,我们将使用动态引号来更改静态响应。为此,我们将需要连接到数据库。让我们创建一个配置文件,该文件具有config.js在项目根目录(除了app.js之外)所调用的数据库凭据,如下所示
  70. const env = process.env;
    
    const config = {
      db: { /* do not put password or any sensitive info here, done only for demo */
        host: env.DB_HOST || 'remotemysql.com',
        user: env.DB_USER || '2ZE90yGC6G',
        password: env.DB_PASSWORD || 'JZFqXibSmX',
        database: env.DB_NAME || '2ZE90yGC6G',
        waitForConnections: true,
        connectionLimit: env.DB_CONN_LIMIT || 2,
        queueLimit: 0,
        debug: env.DB_DEBUG || false
      },
      listPerPage: env.LIST_PER_PAGE || 10,
    };
      
    module.exports = config;
  71. 我们创建了一个config.js文件,文件具有从环境变量中获取的数据库凭据。如果未设置环境变量,则使用后备值。
  72. 之后,db.js/services文件夹中创建文件,如下所示:
  73. const mysql = require('mysql2/promise');
    const config = require('../config');
    const pool = mysql.createPool(config.db);
    
    async function query(sql, params) {
      const [rows, fields] = await pool.execute(sql, params);
    
      return rows;
    }
    
    module.exports = {
      query
    }
  74. 在这个简单的数据库包装器中,我们为MySQL创建了一个连接池。由于我们的配置有connectionLimit2个,它将最多创建2个与数据库的连接。然后有一个简单的query方法可以使用给定的参数运行SQL查询。
  75. 之后,创建一个/services/quotes.js包含以下内容文件:
  76. const db = require('../services/db');
    
    async function getMultiple(){
      const data = await db.query('SELECT id, quote, author FROM quote');
      const meta = {page: 1};
    
      return {
        data,
        meta
      }
    }
    
    module.exports = {
      getMultiple
    }
  77. 至此,这是一个非常简单的文件,其中包含db上面创建服务。然后有一个getMultiple函数暴露在module.exports中。注意,这将查询数据库上的所有记录,在此阶段应为15。我们将在下一步进行分页。
  78. 因此,我们将把getMultiple函数/services/quotes.js文件中的路由连接/quotes起来,/routes/quotes.js如下所示:
  79. const express = require('express');
    const router = express.Router();
    const quotes = require('../services/quotes');
    
    /* GET quotes listing. */
    router.get('/', async function(req, res, next) {
      try {
        res.json(await quotes.getMultiple());
      } catch (err) {
        console.error(`Error while getting quotes `, err.message);
        next(err);
      }
    });
    
    module.exports = router;
  80. 我认为这是安装的正确时机nodemon,我建议如下进行全局安装:
  81. npm install -g nodemon #you might need sudo depending on your config
  82. 使用nodemon,您可以在每次代码更改时重新启动Node.js服务器,这在开发时非常有帮助。您现在可以使用nodemon如下所示运行该应用,以在浏览器上检查结果:
  83. DEBUG=nodejs-mysql:* nodemon bin/www
  84. http://localhost:3000浏览器上单击时,应该在浏览器上看到类似的输出或很多输出JSON
  85. 引用数据库表中使用Express js的Node.js MySQL教程的API动态输出
  86. 如果返回并检查您运行的控制台,nodemon应该会看到类似以下内容:
  87. 引用使用Express js的Node.js MySQL教程的API nodemon输出
  88. 如果更改任何文件,服务器将由于nodemon而重新启动。在此带有express js的Node.js MySQL教程的下一步中,我们将在页面上用10个引号对结果进行分页。如果您想查看代码更改,那么我们在获取请求中所做的就是从数据库中获取报价。
  89. 对Node.js MySQL教程的引号进行分页

  90. 在这一关头,我们将开始对报价单进行分页,每页10个报价单。的,已经在/config.js文件中的行号处放置了14,因为listPerPage: env.LIST_PER_PAGE || 10,我们现在将使用它。
  91. 我们将/helper.js在根目录上添加一个如下所示:
  92. function getOffset(currentPage = 1, listPerPage) {
      return (currentPage - 1) * [listPerPage];
    }
    
    function emptyOrRows(rows) {
      if (!rows) {
        return [];
      }
      return rows;
    }
    
    module.exports = {
      getOffset,
      emptyOrRows
    }
  93. 我们将使用此helper.js文件来计算偏移量。如果rows变量为空,则另一个函数将返回一个空数组,否则它将返回行。
  94. 接下来,我们将更新查询以获取报价/services/quotes.js更改报价服务如下所示:
  95. const db = require('./db');
    const helper = require('../helper');
    const config = require('../config');
    
    async function getMultiple(page = 1){
      const offset = helper.getOffset(page, config.listPerPage);
      const rows = await db.query(
        'SELECT id, quote, author FROM quote LIMIT ?,?', 
        [offset, config.listPerPage]
      );
      const data = helper.emptyOrRows(rows);
      const meta = {page};
    
      return {
        data,
        meta
      }
    }
    
    module.exports = {
      getMultiple
    }
  96. 与旧的报价服务相比,分页功能的主要区别在于查询具有偏移量和限制传递给它。注意,我们使用的是准备好的语句,该语句使查询免受SQL注入的影响。您可以在此stackoverflow答案中阅读有关带预准备语句的SQL注入预防的更多信息。
  97. 为获得分页功能而更改的另一个文件是/routes/quotes.js现在,新的quotes.js路由如下所示:
  98. const express = require('express');
    const router = express.Router();
    const quotes = require('../services/quotes');
    
    /* GET quotes listing. */
    router.get('/', async function(req, res, next) {
      try {
        res.json(await quotes.getMultiple(req.query.page));
      } catch (err) {
        console.error(`Error while getting quotes `, err.message);
        next(err);
      }
    });
    
    module.exports = router;
  99. 这里唯一的变化是我们正在将page查询参数传递getMultiple函数。这将使分页等/quotes?page=2
  100. 如果您运行该应用程序并点击浏览器,http://localhost:3000/quotes?page=2则会看到5个如下的引号:
  101. 引用使用Express js的Node.js MySQL教程的API分页输出
  102. 所以这里发生的事情,主要的变化是我们构造SELECT查询的方式。根据页码,我们计算偏移量并传递不同的查询:
  103. 对于第1页,查询为 SELECT id, quote, author FROM quote LIMIT 0,10
  104. 对于第2页,查询变为 SELECT id, quote, author FROM quote LIMIT 10,10
  105. 如您所见,偏移量计算使获得下一组10个报价(其中10为否)成为可能。我们要根据配置列出的项目数量。一口气理解这可能是一个很大的变化,请查看此请求请求,以了解使分页功能生效的所有代码。
  106. 下一步

  107. 随着基本GET API的启动和运行,您可以为其添加更多功能,例如:
  108. 添加新路线,例如/quotes/{id}按ID获得单引号
  109. 您可以添加报价过滤器/搜索功能(例如作者)
  110. 您还可以使用SQL按单词搜索,就像%computer%可以给所有带有计算机一词的引号一样
  111. 为了使练习变得有趣,请添加一个新列,category并更新API。
  112. 在新创建的GET引号REST API端点之上,您还需要构建其他内容,由我自己决定。下一步是创建POST API以创建新的报价。
  113. 保存新报价-Node.js MySQL的POST API教程

  114. 要创建新报价,我们将需要Post API。在继续进行之前,让我们先清除一下假设:
  115. 在本演示中,我们不会使用复杂的验证库(例如Joi)。
  116. 我们将使响应代码尽可能简单
  117. 我们将不会构建PUT(更新)和DELETE端点。您可以运行INSERT查询,因为UPDATE和DELETE相似,只是在请求正文中传递的引号ID不同。

  1. 让我们来了解一下POST quotes API的代码。首先,我们将在/routes/quotes.js文件的正上方添加POST引号路由module.exports = router
  2. /* POST quotes */
    router.post('/', async function(req, res, next) {
      try {
        res.json(await quotes.create(req.body));
      } catch (err) {
        console.error(`Error while posting quotes `, err.message);
        next(err);
      }
    });
    
    module.exports = router;
  3. 之后,我们将服务文件中添加validateCreatecreate函数,/services/quotes.jscreate在module.exports中公开如下所示:
  4. function validateCreate(quote) {
      let messages = [];
    
      console.log(quote);
    
      if (!quote) {
        messages.push('No object is provided');
      }
    
      if (!quote.quote) {
        messages.push('Quote is empty');
      }
    
      if (!quote.author) {
        messages.push('Quote is empty');
      }
    
      if (quote.quote && quote.quote.length > 255) {
        messages.push('Quote cannot be longer than 255 characters');
      }
    
      if (quote.author && quote.author.length > 255) {
        messages.push('Author name cannot be longer than 255 characters');
      }
    
      if (messages.length) {
        let error = new Error(messages.join());
        error.statusCode = 400;
    
        throw error;
      }
    }
    
    async function create(quote){
      validateCreate(quote);
    
      const result = await db.query(
        'INSERT INTO quote (quote, author) VALUES (?, ?)', 
        [quote.quote, quote.author]
      );
    
      let message = 'Error in creating quote';
    
      if (result.affectedRows) {
        message = 'Quote created successfully';
      }
    
      return {message};
    }
    
    module.exports = {
      getMultiple,
      create
    }
  5. 我知道验证是有点原始的,但现在可以完成工作。更好的方法是使用Joi或类似的验证库。接下来,让我们添加一个新的错误处理程序,以将我们的验证或其他错误显示为JSON响应,/app.js如下所示:
  6. app.use((err, req, res, next) => {
      const statusCode = err.statusCode || 500;
      console.error(err.message, err.stack);
      res.status(statusCode).json({'message': err.message});
      
      return;
    })
    
    module.exports = app;
  7. 确保将其放在module.exports = app行的上方,以便它们在路由之后执行。现在,您可以启动应用并尝试以下cURL命令:
  8. curl -i -X POST -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:3000/quotes --data '{"quote":"Controlling complexity is the essence of computer programming."}' ; #should give an error about missing author
    
    curl -i -X POST -H 'Accept: application/json' -H 'Content-type: application/json' http://localhost:3000/quotes --data '{"quote":"Controlling complexity is the essence of computer programming.","author":"Brian Kernighan"}'; #should work
  9. 以下是两个cURL命令的输出:
  10. 引用API cURL以使用Express js的Node.js MySQL教程的POST输出
  11. 根据配置和数据库记录,您可能会得到一个duplicate entry错误。只是将报价更改为其他内容然后尝试。POST报价API的代码更改在此请求中。
  12. 您可以在这里找到很多引号来尝试。
  13. 因此,您已经有了一个基本的RESTful API,用于报价,可以使用POST端点创建新的报价。有一个GET端点可通过分页获取报价。
  14. 如果您想尝试托管数据库,则可以查看此Node.js HarperDB教程。
  15. TLDR;我想快速运行

  16. 由于所有代码都在公共Github存储库中,因此您可以立即开始运行以下命令来开始使用:
  17. 克隆存储库: git clone [email protected]:geshan/nodejs-mysql.git
  18. 然后跑 cd nodejs-mysql
  19. 之后执行 npm install
  20. 结果运行: npm start
  21. 然后点击https://localhost:3000/quote您喜欢的浏览器
  22. 您应该看到类似以下的内容:
  23. 引用数据库表中使用Express js的Node.js MySQL教程的API动态输出
  24. 您可以查看代码,并尝试在带有Express Js的Node.js MySQL教程中将整个内容拼凑在一起,以获取报价REST API。该API可以作为Node.js微服务的良好基础。
  25. 您可以按照本分步指南Dockerdocker REST API app 。将该应用程序进行docker化之后,您可以轻松地将其托管在Google Cloud Run之类的东西上。如果您想在没有Docker的情况下快速开始测试,我建议使用Glitch。
  26. 结论

  27. 使用Node.js和MySQL创建REST API并不困难。
  28. 在Express的此Node.js MySQL教程中,一些事情没有得到解决,但这是一个很好的起点。DYp码友部落