发布于 2026-01-06 1 阅读
0

Node.js Postgresql 教程:使用 Express 逐步构建简单的 REST API

Node.js Postgresql 教程:使用 Express 逐步构建简单的 REST API

Node.js 可以高效地与 PostgreSQL 等关系型数据库配合使用。在本篇 Node.js PostgreSQL 教程中,我们将使用 Express.js 逐步构建一个用于报价的 REST API。


目录

  1. 先决条件
  2. Node.js PostgreSQL教程步骤
    1. 使用 Express 生成器设置 Express
      1. 删除公共文件夹
      2. 删除不必要的现有路由,然后创建一个新的报价路由。
      3. 更改索引路由以提供 JSON
    2. 使用报价表配置 PostgreSQL
    3. 将 Node.js 与 Postgres 连接起来。
    4. 显示报价 - 带分页的 GET API
    5. 保存新报价 - Node.js PostgreSQL POST API 教程
      1. 为创建报价 POST API 添加验证
  3. TLDR;简要概述
  4. 结论

您可以阅读我之前的教程,了解如何使用Node.js 和 MySQL。您也可以阅读这篇指南,了解如何将Docker 与 Node.js结合使用,它也是一篇循序渐进的教程。当然,您也可以继续学习这篇关于 Node.js 和 PostgreSQL 的教程 :)。

为了方便起见,每个步骤都已单独列出为一个拉取请求,以便您可以轻松地按照教程进行操作。

先决条件

  1. 您的机器上已安装并运行了 Node.js(最好是 Node 14.x)(或者 Node.js 已在 Docker 容器中运行)。
  2. 您了解Node.js的基本工作原理,并且有一定的Express Js经验。
  3. 了解一些 Git 和 GitHub 的知识会很有帮助。
  4. 数据库方面,我们将使用ElephantSQL上的免费数据库,请先注册并创建一个免费的 PostgreSQL 数据库。当然,您需要了解关系型数据库的工作原理。
  5. 您可以使用集成开发环境 (IDE) 进行编码。我将使用 VS Code 作为编辑器,但您可以自由选择任何您喜欢的代码编辑器来完成本 Node.js PostgreSQL 教程。

Node.js PostgreSQL教程步骤

我们将使用 Express.js 构建一个非常简单的 REST API,用于发送报价。此时,快速回顾一下REST API 的概念将非常有帮助。

学习一些 HTTP 动词并复习一下 cURL 命令会很有帮助。我们将使用 cURL 来运行示例。

目前,我们认为您的 Node.js 运行正常。那么,让我们开始配置 Express.js 吧:

使用 Express 生成器设置 Express

要使用express-generator构建 Express js,请运行以下命令:

npx express-generator --no-view --git nodejs-postgresql
Enter fullscreen mode Exit fullscreen mode

--no-view参数指示生成器生成一个不包含任何视图的 Express 应用,就像 Pug 那样。另一个--git参数表明我们希望.gitignore在 Express 应用中添加默认文件。
它会在指定目录中创建所需的文件nodesj-postgresql。命令成功运行后,您的 Express 应用就设置完成了。要快速检查 Express 是否设置正确,请运行以下命令:

cd nodejs-posgresql && npm install && DEBUG=nodejs-posgresql:* npm start
Enter fullscreen mode Exit fullscreen mode

点击后,您应该会在浏览器中看到类似的内容http://localhost:3000

Express 正在本地端口 3000 上运行

您可以在此拉取请求中查看生成的简易 Express Js 应用程序

删除公共文件夹

由于本 Node.js PostgreSQL 教程旨在构建一个用于报价的 REST API,因此我们不需要任何 CSS 或 JS。所以,我们将删除生成的public文件夹,因为我们将处理 JSON 数据。

要删除生成的公共文件夹,请在终端上执行以下命令:

rm -rf public
Enter fullscreen mode Exit fullscreen mode

删除不必要的现有路由,然后创建一个新的报价路由。

此时,我们将删除目录中不必要的用户路由routes/users.js。随后,我们将添加routes/quotes.js一个包含以下内容的文件:

const express = require('express');
const router = express.Router();

/* GET quotes listing. */
router.get('/', function(req, res, next) {
  res.json({
    data: [
      {
        quote: 'First, solve the problem. Then, write the code.',
        author: 'John Johnson'
      }
    ],
    meta: {
      page: 1
    }
  });
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

目前,它只会输出如上所示的一条静态引用。我们将在文件中app.js像这样连接引用路由:

var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');

var indexRouter = require('./routes/index');
var quotesRouter = require('./routes/quotes');

var app = express();

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/quotes', quotesRouter);

module.exports = app;
Enter fullscreen mode Exit fullscreen mode

上述文件中的更改仅在第 7 行和第 18 行,其中用户的路由器已替换为带引号的路由器。

更改索引路由以提供 JSON

此步骤的最后一步修改位于index.js项目根目录下的文件第 6 行。我们将修改该行,使其发送 JSON 数据而不是渲染视图。修改后的文件内容将与修改前相同:

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.json({message: 'alive'});
});

module.exports = router;
Enter fullscreen mode Exit fullscreen mode

您可以在此拉取请求中查看此步骤中所做的所有更改

要快速查看上述更改的输出结果,请运行以下命令:

DEBUG=nodejs-postgresql:* npm start
Enter fullscreen mode Exit fullscreen mode

然后点击http://localhost:3000/quotes浏览器标签页,你会看到类似下面的内容:

带有静态输出的报价 API

下一步我们将从PostgreSQL数据库中获取报价。

使用报价表配置 PostgreSQL

我们希望帮您免去在本地机器上创建和维护数据库的麻烦。您可以在 Elephant SQL 上获得一个免费的 PostgreSQL 数据库,包含 20 MB 的数据和 5 个并发连接。这对于本教程来说绰绰有余。

注册Elephant SQL后,请按照此文档创建 PostgreSQL 数据库。在您选择的数据中心创建免费的(小型)数据库。如果您想了解更多关于 PostgreSQL 的信息,请阅读Elephant SQL 上的这篇系列教程。

之后,要在已创建数据库的“浏览器”部分运行以下 SQL 语句来创建报价表:

CREATE SEQUENCE quote_id_seq START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1;

CREATE TABLE quote (
    id bigint DEFAULT nextval('quote_id_seq'::regclass) NOT NULL PRIMARY KEY,
    quote character varying(255) NOT NULL UNIQUE,
    author character varying(255) NOT NULL,
    created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
    updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

以下是它在 Elephant SQL 界面上的显示效果:

在 Elephant SQL 接口上创建报价表

这是一个非常简单的五列表格。第一列是id序列号,也是主键。接下来是两个quote变量,author它们都是可变字符型。

之后,created_atupdated_at两个字段都变成了时间戳。该quote列添加了唯一索引,以避免重复出现相同的引言。创建表后,我们将quote通过执行以下插入 SQL 语句向表中填充一些引言:

INSERT INTO quote (quote, author) VALUES 
('There are only two kinds of languages: the ones people complain about and the ones nobody uses.', 'Bjarne Stroustrup'), 
('Any fool can write code that a computer can understand. Good programmers write code that humans can understand.', 'Martin Fowler'), 
('First, solve the problem. Then, write the code.', 'John Johnson'), 
('Java is to JavaScript what car is to Carpet.', 'Chris Heilmann'), 
('Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.', 'John Woods'), 
('I''m not a great programmer; I''m just a good programmer with great habits.', 'Kent Beck'), 
('Truth can only be found in one place: the code.', 'Robert C. Martin'), 
('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'), 
('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'), 
('SQL, Lisp, and Haskell are the only programming languages that I’ve seen where one spends more time thinking than typing.', 'Philip Greenspun'), 
('Deleted code is debugged code.', 'Jeff Sickel'), 
('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'), 
('Simplicity is prerequisite for reliability.', 'Edsger W. Dijkstra'), 
('There are only two hard things in Computer Science: cache invalidation and naming things.', 'Phil Karlton'), 
('Measuring programming progress by lines of code is like measuring aircraft building progress by weight.', 'Bill Gates'), 
('Controlling complexity is the essence of computer programming.', 'Brian Kernighan'),
('The only way to learn a new programming language is by writing programs in it.', 'Dennis Ritchie');
Enter fullscreen mode Exit fullscreen mode

插入 17 行数据后,如果您在 Elephant SQL 浏览器界面上运行以下命令:

SELECT * FROM quote;
Enter fullscreen mode Exit fullscreen mode

您应该会看到类似下面的内容:

SELECT all from quote on Elephant SQL interface

您可以在此拉取请求中找到初始化数据库的 SQL 文件。数据库已设置完毕,接下来我们将把它与 Node.js Express 应用程序连接起来。

将 Node.js 与 Postgres 连接起来。

要将 Node.js Express Js 应用程序与我们已设置的数据库连接起来,我们需要安装 Postgres npm 库。要获取这个有用的库,请运行以下命令:

npm install --save pg
Enter fullscreen mode Exit fullscreen mode

安装此 npm 包后所做的更改已包含在此拉取请求中。现在可以开始添加 GET 报价 API 路由了。

显示报价 - 带分页的 GET API

http://localhost:3000/quotes启动 Express Js 应用后,您会看到类似以下内容

{
  "data":[
    {
      "quote":"First, solve the problem. Then, write the code.",
      "author":"John Johnson"
    }
  ],
  "meta":{
    "page":1
  }
}
Enter fullscreen mode Exit fullscreen mode

现在我们将用 Elephant SQL 从 PostgreSQL 数据库中获取数据来替换它。为此,我们需要连接到数据库。

让我们config.js在根目录下创建一个文件。这个配置文件包含数据库凭据和其他配置信息,如下所示:

const env = process.env;

const config = {
  db: { /* do not put password or any sensitive info here, done only for demo */
    host: env.DB_HOST || 'otto.db.elephantsql.com',
    port: env.DB_PORT || '5432',
    user: env.DB_USER || 'cklijfef',
    password: env.DB_PASSWORD || 'V1qidES5k3DSJICDRgXtyT8qeu2SPCZp',
    database: env.DB_NAME || 'cklijfef',
  },
  listPerPage: env.LIST_PER_PAGE || 10,
};

module.exports = config;
Enter fullscreen mode Exit fullscreen mode

接下来,我们需要添加一个services/db.js文件,该文件将使用一个连接池来运行我们的 SQL 查询。它看起来会像这样:

const { Pool } = require('pg');
const config = require('../config');
const pool = new Pool(config.db);

/**
 * Query the database using the pool
 * @param {*} query 
 * @param {*} params 
 * 
 * @see https://node-postgres.com/features/pooling#single-query
 */
async function query(query, params) {
    const {rows, fields} = await pool.query(query, params);

    return rows;
}

module.exports = {
  query
}
Enter fullscreen mode Exit fullscreen mode

之后,我们将helper.js在根目录下添加一个文件,用于格式化结果并计算分页偏移量。该文件内容如下:

function getOffset(currentPage = 1, listPerPage) {
  return (currentPage - 1) * [listPerPage];
}

function emptyOrRows(rows) {
  if (!rows) {
    return [];
  }
  return rows;
}

module.exports = {
  getOffset,
  emptyOrRows
}
Enter fullscreen mode Exit fullscreen mode

此时,我们将添加services/quotes.js一个文件,其内容如下:

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 OFFSET $1 LIMIT $2', 
    [offset, config.listPerPage]
  );
  const data = helper.emptyOrRows(rows);
  const meta = {page};

  return {
    data,
    meta
  }
}

module.exports = {
  getMultiple
}
Enter fullscreen mode Exit fullscreen mode

所有这些都是从路由文件中复制粘贴过来routes/quotes.js的,修改后的文件内容如下:

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;
Enter fullscreen mode Exit fullscreen mode

此文件中的主要更改routes/quotes.js是添加了报价服务。之后,程序/quotes将使用该报价服务动态获取报价。

请注意,引用内容已分页,也就是说,https://localhost:3000/quotes?page=2由于 config.js 文件中每页设置了 10 条引用,因此将显示第 11-20 条引用。此时,第 2 页的输出应如下所示:

第 2 页上的报价是从数据库表中动态获取的。

现在我们来添加 POST 报价 API,它会将新的报价插入数据库。如上所述,您可以在这个组织有序的拉取请求中查看此步骤的所有文件更改。

保存新报价 - Node.js PostgreSQL POST API 教程

为了创建新报价,我们将采用简单的 POST API。我们不会使用任何验证库,并且会尽可能简化响应代码。

首先,我们要添加保存新报价的端点,就是将其添加到/routes/quotes.js文件中,就module.exports = router在如下所示的上一行:

/* 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;
Enter fullscreen mode Exit fullscreen mode

目前,我们暂不添加任何代码级验证。数据库表中quote有一个quote必填字段,且长度限制为 255 个字符。因此,如果引号为空,则会抛出数据库级错误。与 MySQL 不同,PostgreSQL 在引号长度超过 255 个字符时会报错。

在更实际的应用场景中,我建议使用验证库来处理这类情况。现在,我们先像下面create这样添加方法/services/quotes.js

async function create(quote) {
  const result = await db.query(
    'INSERT INTO quote(quote, author) VALUES ($1, $2) RETURNING *',
    [quote.quote, quote.author]
  );
  let message = 'Error in creating quote';

  if (result.length) {
    message = 'Quote created successfully';
  }

  return {message};
}

module.exports = {
  getMultiple,
  create
}
Enter fullscreen mode Exit fullscreen mode

服务器运行后,您可以尝试以下 curl 命令,看看它是否能创建新的报价单:

curl -i -X POST -H 'Accept: application/json' \
    -H 'Content-type: application/json' http://localhost:3000/quotes \
    --data '{"quote":"Before software can be reusable it first has to be usable2.","author":"Ralph Johnson"}'
Enter fullscreen mode Exit fullscreen mode

应该会返回 200 响应,表示引用已创建。您可以尝试此帖子中的其他引用。

创建报价的 POST API 功能已正常运行。您可以轻松地在此基础上创建编辑和删除报价的端点,并分别使用相应的UPDATESQLDELETE语句。只需注意传递正确的 ID 即可执行这些操作。

与之前的步骤类似,此步骤的所有代码更改都可以在此拉取请求中找到。

为创建报价 POST API 添加验证

目前功能应该已经实现,但我们不应该将验证逻辑下放到数据库层,因为这会消耗更多资源。因此,在本步骤的下一部分,我们将在代码层添加验证逻辑。

我们将在validateCreate现有create方法之上添加一个/services/quotes.js验证方法,如下所示:

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 ($1, $2) RETURNING *',
    [quote.quote, quote.author]
  );
  let message = 'Error in creating quote';

  if (result.length) {
    message = 'Quote created successfully';
  }

  return {message};
}
Enter fullscreen mode Exit fullscreen mode

所以现在,如果您尝试使用以下不带作者信息的 cURL 命令,服务器运行时将显示错误:

curl -i -X POST -H 'Accept: application/json' \
    -H 'Content-type: application/json' http://localhost:3000/quotes \
    --data '{"quote":"Before software can be reusable it first has to be usable."}' 
Enter fullscreen mode Exit fullscreen mode

它会显示类似下面的内容:

创建未注明作者的引用会引发验证错误。

这些更改也反映在拉取请求中。

在开发环境中,我强烈建议使用Nodemon,因为它会在每次文件更改时重启服务器。

在全局安装 nodemon 后,您可以使用以下命令运行该应用程序:

DEBUG=nodejs-postgresql:* nodemon bin/www 
Enter fullscreen mode Exit fullscreen mode

Nodemon非常适合开发。

TLDR;简要概述

以上所有代码都位于公开的GitHub 代码库中,要快速上手使用已构建的功能,您可以按照以下步骤操作:

  1. 使用以下命令克隆存储库:git clone git@github.com:geshan/nodejs-posgresql.git
  2. 然后运行cd nodejs-postgresql
  3. 然后,执行:npm install && npm start
  4. 之后,点击:https://localhost:3000/quotes在您常用的浏览器中
  5. 您的浏览器上应该显示以下内容:

第 1 页的引言

结论

到目前为止,使用 Node.js 和 PostgreSQL 搭建 REST API 的过程相当有趣。不过,这仍然只是冰山一角。你可以使用GitHub 代码库作为模板来创建简单的 REST API。

本教程是构建完整、可实际应用的 REST API 的绝佳起点。希望它能成为你开启精彩项目的开端。祝你好运!

文章来源:https://dev.to/geshan/node-js-postgresql-tutorial-build-a-simple-rest-api-with-express-step-by-step-3phi