Node.js 可以有效地与 PostgreSQL 等关系数据库一起使用。在这篇关于 Node.js PostgreSQL 教程的文章中,我们将使用 Express Js 逐步构建一个用于报价的 REST API。


目录

1.先决条件

  1. Node.js PostgreSQL教程步骤

1.Setup Express 与 express generator

1.删除公用文件夹

2.删除不必要的现有路由,然后为报价创建新路由

3.更改索引路由给出JSON

2.使用报价表设置PostgreSQL

3.链接 Node.js 与 Postgres

4.显示报价 - GET API 分页

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

1.为创建报价添加验证 POST API

3.TLDR;快速概要

4.结论

你可以阅读我之前的教程来尝试Node.js 和 MySQL。您应该阅读本指南以使用Docker 和 Node.js,它也是一个分步指南。当然,您可以继续学习这个 Node.js 与 PostgreSQL 教程 :)。

为了您的方便,每个步骤都被划分为一个独特的拉取请求,以便您可以轻松地按照教程进行操作。

先决条件

  1. 您已在您的机器上安装并运行 Node.js(最好是 Node 14.x)(或使用 docker 容器运行的 Node.js)。

  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-generator执行 Express js,请运行以下命令:

npx express-generator --no-view --git nodejs-postgresql

进入全屏模式 退出全屏模式

--no-view参数告诉生成器生成没有像 Pug 这样的任何视图的 Express 应用程序。另一个--git参数表示我们要在我们的 Express 应用程序中添加默认的.gitignore文件。

它将在nodesj-postgresql目录中创建所需的文件。当命令成功运行时,您的 express 将被设置。要快速检查 Express 是否设置正确,请运行以下命令:

cd nodejs-posgresql && npm install && DEBUG=nodejs-posgresql:* npm start

进入全屏模式 退出全屏模式

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

[Express running on local at port 3000](https://res.cloudinary.com/practicaldev/image/fetch/s--vfZ9UhDT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https:/ /dev-to-uploads.s3.amazonaws.com/i/o176php5pp0r3lmpwkzj.jpg)

生成的准系统 Express Js 应用可以在这个pull request中查看。

删除公用文件夹

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

要删除生成的公用文件夹,请在终端上执行以下操作:

rm -rf public

进入全屏模式 退出全屏模式

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

此时,我们将删除在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;

进入全屏模式 退出全屏模式

目前,它会给出一个只有 1 个报价的静态输出,如上所示。我们将链接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;

进入全屏模式 退出全屏模式

上述文件的更改仅在第 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;

进入全屏模式 退出全屏模式

您可以在此pull request中检查此步骤中所做的所有更改。

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

DEBUG=nodejs-postgresql:* npm start

进入全屏模式 退出全屏模式

然后在浏览器选项卡上点击http://localhost:3000/quotes,您将看到如下内容:

[报价 API 静态输出](https://res.cloudinary.com/practicaldev/image/fetch/s--5oK-lDDS--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https:// /dev-to-uploads.s3.amazonaws.com/i/aff532rodx0wmwcrtc2t.jpg)

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

使用报价表设置 PostgreSQL

我们希望让您免于在计算机上本地创建和维护数据库的麻烦。您可以在 Elephant SQL 上拥有一个免费的 PostgreSQL 数据库,其中包含 20 MB 数据和 5 个并发连接。对于本教程的目的来说,这已经绰绰有余了。

要在注册后在Elephant SQL上创建 PostgreSQL 数据库,请按照此文档操作。在您选择的数据中心创建免费(小乌龟)数据库。如果您想了解有关 PostgreSQL 的更多信息,请遵循此多部分教程on 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
);

进入全屏模式 退出全屏模式

这是它在 Elephant SQL 界面上的外观:

[在Elephant SQL接口上创建Quotes表](https://res.cloudinary.com/practicaldev/image/fetch/s--uIblFYH7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https:/ /dev-to-uploads.s3.amazonaws.com/i/42u3g8uhlyiy0a2toa2c.jpg)

这是一个非常简单的表格,有 5 列。第一个是id,它是一个序列和主键。然后有quoteauthor都是可变字符。

之后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');

进入全屏模式 退出全屏模式

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

SELECT * FROM quote;

进入全屏模式 退出全屏模式

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

[SELECT all from quote on Elephant SQL interface](https://res.cloudinary.com/practicaldev/image/fetch/s--77f5FjtZ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https: //dev-to-uploads.s3.amazonaws.com/i/b0ydg79cqy1gkg9cihyi.jpg)

您可以在这个pull request中找到初始化数据库 SQL 文件。当我们的数据库设置并准备就绪后,让我们继续将它与 Node.js Express 应用程序链接起来。

将 Node.js 与 Postgres 链接

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

npm install --save pg

进入全屏模式 退出全屏模式

我们通过安装这个 npm 包得到的改变在这个pull request中。是时候继续添加 GET 报价 API 路由了。

显示报价 - 使用分页获取 API

启动 Express Js 应用后进入http://localhost:3000/quotes时,可以看到如下内容:

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

进入全屏模式 退出全屏模式

现在我们将通过从 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;

进入全屏模式 退出全屏模式

随后,我们将需要添加一个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
}

进入全屏模式 退出全屏模式

之后,我们将在根级别添加一个helper.js文件,这将帮助我们格式化结果并计算分页偏移量。它将具有以下内容:

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

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

module.exports = {
  getOffset,
  emptyOrRows
}

进入全屏模式 退出全屏模式

此时,我们将添加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
}

进入全屏模式 退出全屏模式

所有这些都来自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;

进入全屏模式 退出全屏模式

routes/quotes.js文件中的主要变化是添加了报价服务。之后,/quotes使用添加的报价服务动态获取报价。

请注意,引号已经分页,这意味着https://localhost:3000/quotes?page=2将给出引号 11-20,因为它在 config.js 文件中每页有 10 个引号。此时第 2 页的输出应如下所示:

[第 2 页上的报价从数据库表动态获取](https://res.cloudinary.com/practicaldev/image/fetch/s--tzf5rMXQ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/ https://dev-to-uploads.s3.amazonaws.com/i/c61b9tm3yu8sedi15bvq.jpg)

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

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

为了创建一个新报价,我们将它保留为一个简单的 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;

进入全屏模式 退出全屏模式

暂时,我们不会添加任何代码级别的验证。数据库表quote具有必填字段quote和 255 个字符。因此,如果报价为空,则会出现数据库级错误。与 MySQL 不同,如果引号超过 255 个字符,PostgreSQL 将给出错误。

在更真实的场景中,我建议对这些情况使用验证库。现在,让我们在/services/quotes.js中添加create方法,如下所示:

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
}

进入全屏模式 退出全屏模式

运行服务器后,您可以尝试以下 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"}'

进入全屏模式 退出全屏模式

它应该返回 200 响应,说明报价已创建。您可以尝试此帖子中的其他报价。

随着创建报价 POST API 的工作。您可以轻松地在它的基础上使用相应的UPDATEDELETESQL 语句创建编辑和删除引用端点。请注意传递正确的 id 来执行这些操作。

与前面的步骤类似,此步骤的所有代码更改都可以在这个pull request中找到。

添加验证以创建报价 POST API

到目前为止,它应该可以正常工作,但我们不应该将验证推送到数据库层,因为它会消耗更多的资源。因此,在此步骤的下一部分中,我们将在代码级别添加验证。

我们将在/services/quotes.jscreate方法之上添加一个validateCreate方法来进行验证,如下所示:

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};
}

进入全屏模式 退出全屏模式

所以现在如果你在没有作者的情况下尝试下面的 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."}' 

进入全屏模式 退出全屏模式

它将显示如下内容:

[在没有作者的情况下创建报价将给出验证错误](https://res.cloudinary.com/practicaldev/image/fetch/s--gozBZhTo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880 /https://dev-to-uploads.s3.amazonaws.com/i/8z474m7quxx6md1v78gs.jpg)

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

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

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

DEBUG=nodejs-postgresql:* nodemon bin/www 

进入全屏模式 退出全屏模式

Nodemon 非常适合开发。

TLDR;快速纲要

上面显示的所有代码都在公共GitHub 存储库中,要快速开始已经构建的内容,您可以按照以下步骤操作:

1.克隆存储库:git clone git@github.com:geshan/nodejs-posgresql.git

2.然后运行cd nodejs-postgresql

  1. 随后,执行:npm install && npm start

  2. 之后,在您喜欢的浏览器上点击:https://localhost:3000/quotes

  3. 您应该在浏览器上看到以下内容:

[第 1 页引述](https://res.cloudinary.com/practicaldev/image/fetch/s--tO947pyv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev- to-uploads.s3.amazonaws.com/i/nuecxiaamiqewygfoana.jpg)

结论

到目前为止,使用 Node.js 和 PostgreSQL 设置 REST API 非常有趣。尽管如此,它更像是在表面上刮擦。您可以使用GitHub 存储库作为样板来创建简单的 REST API。

本教程是构建完整的真实 REST API 的绝佳起点。我希望你能以此作为激动人心的开始。祝你好运!

Logo

PostgreSQL社区为您提供最前沿的新闻资讯和知识内容

更多推荐