使用PostgresSQL和Express在Node.js中构建一个RESTful Web API
REST是REpresentational State Transfer的缩写。它允许你创建一个数据对象,将该对象的状态发送到服务器上,并返回该对象的值。REST是一套设计标准,而不是物理结构(架构)。它使用资源(数据)的 "表征",将资源状态从服务器转移到客户端的应用状态。
API是应用 编程 接口的缩写。它是两个软件程序之间的通信语言。API使用一种商定的数据格式在程序之间来回发送请求和响应。它定义了两个程序之间的通信规则和程序。这有助于在这些程序之间形成一个接触点(一个端点)。
一个应用REST 风格的API被称为RESTful 。一个RESTful API的工作方式几乎与网络一样。通常情况下,你向服务器提出API请求,并通过HTTP协议得到一个响应。
下图描述了这个概念。

向服务器发出的请求使用HTTP方法,如。
- GET - 从服务器上检索数据。
- POST--用于提交特定的数据,由服务器进行处理。
- PUT--允许向服务器发送一个更新请求。PUT方法允许修改指定的数据值。
- DELETE--这使你能够提出请求并通知服务器你想删除一些指定的数据值。
RESTful API可以用几乎所有的编程语言来开发。在本指南中,你将通过使用Node.js构建一个RESTful API来学习REST概念。
我们将使用Express 来管理服务器的HTTP协议。由于我们将建立一个交互式的API,我们需要一种方法来存储我们的数据。本指南将使用PostgreSQL (一种关系数据库管理系统)来管理我们的数据。
RESTful API流行的一些原因包括。
- 它们是无状态和可缓存的。
- 由于其可缓存的架构,提供高性能。
- 它们具有统一的客户端-服务器架构。这样就把客户端和服务器分离开来,从而形成了可扩展的服务器组件和资源。
- 它们有一个统一的接口。每个HTTP方法(URL)都是唯一的。这使得使用表示法来识别和操作自描述的资源更加容易。
- RESTful API允许用各种编程语言编写的软件应用程序在不同的环境中相互通信。
目标
我们将创建一个todo-list RESTful API。
这个应用程序符合CRUD 操作,例如。
- CREATE - 添加一个新的todo项目。
- 阅读--查看todo列表中的项目。
- 更新todo列表。为了更新todo列表,我们将使用一个切换器来区分已完成和未完成的todo。这将捕捉到UPDATE的方面。
- DELETE一个todo项目。

这些CRUD操作依赖于HTTP方法。

前提条件
本指南假定你已经具备以下关键领域的知识。
- Node.js的基本知识。
- 能够编写SQL查询。我们将使用SQL来与我们的数据库进行通信。因此,一些关于如何编写这些查询的预先知识将是非常重要的。
- 关于如何使用Express的基本知识。你需要熟悉Express,一个Node.js框架。能够用Express创建路由并管理一个简单的服务器。
- 要熟悉PostgreSQL。PostgreSQL是一个关系型数据库,它使用SQL查询来与存储在数据库表中的数据进行交互。
应用程序包
下面的包将帮助我们建立todo应用程序。
- Express -Express将帮助我们制作与数据库服务器通信的API端点。这使我们能够访问我们想要的资源(数据)。数据的访问将基于HTTP标准方法,即GET、POST、UPDATE和DELETE。
- CORS -CORS代表
Cross Origin Resource Sharing。它允许我们绕过应用于RESTful API的安全性。 - EJS -EJS代表嵌入式 JavaScript。它是一种模板引擎语言,让你用普通的JavaScript生成HTML标记。我们可以使用EJS为静态内容提供服务,而不是提供更多的动态内容。EJS模板在服务器端进行渲染,产生一个HTML文档,然后客户端可以接收。我们将使用EJS模板来为我们的RESTful API创建一个客户端页面。

- PG- PG使得Node.js可以与PostgreSQL数据库连接和通信。
- Nodemon- 这是一个
dev包(应用程序的功能不需要)。Nodemon确保每当你进行修改时,服务器都在运行。当你保存更改时,你不需要重新启动服务器。Nodemon将为你处理这个问题。
应用程序的结构
下面是todo应用程序的项目结构。

设置项目
确保你的电脑上安装了Node.js运行时间。你可以通过运行node –v 命令检查Node.js的版本。
在你想要的文件夹中,运行以下命令来初始化你的Node.js项目。
npm init
回答相关问题,然后进行下一步的操作。
或者,你可以运行`npm init -y',用NPM默认值自动初始化你的项目。
安装必要的依赖项
你可以安装我们上面讨论的所有Node.js包,如下所示。
npm install cors ejs express pg
和
npm install --save-dev nodemon
设置PostgreSQL数据库
安装以下PostgreSQL环境。
- PostgreSQL- 一个开源的关系型数据库管理系统。
- pgAdmin- 一个独立的用于管理PostgreSQL数据库的destop应用程序。
一旦安装并配置好,创建一个my_todos_db 数据库和一个表来工作,如下所示。
- 创建一个数据库,
my_todos_db。
CREATE DATABASE test
CREATE TABLE todos (
id SERIAL PRIMARY KEY,
title VARCHAR(100) NOT NULL,
checked Boolean NOT NULL)
在src 文件夹中,创建一个config 文件夹,然后创建一个db.js 文件 (src/config/db.js)。我们将对数据库进行如下配置。
const Pool = require("pg").Pool;
const pool = new Pool({
user:'postgres', // default postgres
host:'localhost',
database:'name_of_your_database', // `my_todos_db`
password:'your_password', //added during PostgreSQL and pgAdmin installation
port:'5432' //default port
});
module.exports = pool;
设置服务器
在src 文件夹中,创建一个index.js 文件(src/index.js),并按以下方式配置应用服务器。
const express = require("express");
const cors = require("cors");
const app = express();
app.use(express.json());
app.use(cors());
app.get("/", (req, res) =>{
res.send("hello world!");
});
const PORT = process.env.PORT || 4000;
app.listen(PORT, () => {
console.log(`app started on port ${PORT}`)
});
测试服务器是否工作
为了启动服务器,在package.json 中配置scripts 对象,如下所示。
"dev": "nodemon ./src/index.js"
然后,运行npm run dev 来启动服务器。

在浏览器中打开http://localhost/4000 。这应该给你一个响应hello world! 。
如果连接到服务器时有错误,你将在控制台中被提示。在进行下一步之前,请确保你解决了这个错误。
由于服务器已经启动并运行,我们可以不使用app.get("/", (req, res) =>{res.send("hello world!");
如果有任何变化,服务器应用程序将由Nodemon重新启动。没有必要重新运行服务器。

设置路由
创建一个routes 文件夹并添加一个todos.js 文件(src/routes/todos.js )。
在这里,我们将配置我们的路由,如下所示。
const express = require("express");
const router = express.Router();
//Get all todos.
router.get('/', async (req,res) => {
});
//Create a todo.
router.post('/todo', async (req,res) => {
});
//Update a todo.
router.put('/todos/:todoId', async (req,res) => {
});
//Delete a todo.
router.delete('/todos/:todoId', async (req,res) => {
});
module.exports = router;
为了使路由正常工作,我们需要在index.js 文件中配置它们。要做到这一点,我们在src/index.js 文件中添加以下修改。
//import the routes
const todoRoutes = require('./routes/todos');
//configure the app.
app.use(todoRoutes);
设置控制器
控制器负责处理路由所暴露的功能。为了设置控制器,创建一个文件夹,并将其命名为controllers 。接下来,创建一个文件Todo.js ,(src/controllers/Todo.js)。在这个文件中,我们将添加所有我们需要的SQL查询,如SELECT,INSERT,UPDATE 和DELETE 功能,如下所示。
const db = require("../config/db");
class Todo {
//get all todos.
async getTodos() {
let results = await db.query(`SELECT * FROM todos`).catch(console.log);
return results.rows;
}
//create a todo.
async createTodo(todo) {
await db
.query("INSERT INTO todos (title, checked) VALUES ($1, $2)", [todo.title,false,])
.catch(console.log);
return;
}
//update a todo.
async updateTodo(todoId) {
//get the previous todo.
let original_todo = await db
.query(`SELECT * FROM todos WHERE id=$1`, [parseInt(todoId)])
.catch(console.log);
let new_checked_value = !original_todo.rows[0].checked;
//update the checked todo
await db
.query(`UPDATE todos SET checked=$1 WHERE id=$2`, [new_checked_value,parseInt(todoId),])
.catch(console.log);
return;
}
//delete a todo.
async deleteTodo(todoId) {
await db.query(`DELETE FROM todos WHERE id=$1`, [parseInt(todoId)]).catch(console.log);
return;
}
}
module.exports = Todo;
将控制器链接到路由上
为了使路由真正发挥作用,我们需要将它们与各自的控制器联系起来,正如在Todo.js 。在src/routes/todos.js 文件中,添加以下修改。
//import the controller
const Todo = require('../controllers/Todo');
//Get all todos.
router.get('/', async (req,res) => {
let todos = await new Todo().getTodos();
});
//Create a todo.
router.post('/todo', async (req,res) => {
let {title} = req.body;
await new Todo().createTodo({title},res);
});
//Update a todo.
router.put('/todos/:todoId', async (req,res) => {
let {todoId} = req.params;
await new Todo().updateTodo(todoId,res);
let todos = await new Todo().getTodos();
});
//Delete a todo.
router.delete('/todos/:todoId', async (req,res) => {
let {todoId} = req.params;
await new Todo().deleteTodo(todoId);
let todos = await new Todo().getTodos();
});
设置视图
我们将设置将被渲染到客户端的EJS视图。EJS视图的工作原理与HTML元素(如按钮和表单)相同。视图将帮助我们从客户端触发必要的动作,如添加、删除或更新一个todo项目。
设置CSS和视图文件夹,如application structure 。


我们将包括以下视图。
- 一个主页(
src/views/pages/home.ejs),以包括我们添加到我们的todo应用程序的任何EJS模板。
<%- include('../partials/header.ejs') %>
<section class="home-page">
<div class="container">
<div class="row">
<div class="col-12 col-md-12 col-sm-12">
<div class="todo-content">
<h4 class="todo-heading">My todos.</h4>
<%- include('../partials/todos.ejs') %>
<%- include('../partials/add-todo.ejs') %>
</div>
</div>
</div>
</div>
</section>
在上面的代码中,我们正在做以下工作。
- 导入主页的标题。
- 设置主页的布局。
- 导入
todos.ejs文件。它包含了取来的todos。 - 导入
add-todo.ejs文件。它包含了添加tododo的表单。
- 一个标题 (
header.ejs) - 这将包括以下内容。
-
一个todo头。
-
链接
src/public/css/custom.css、src/public/css/bootstrap.min.css和、src/public/js/main.js。我们将添加更新和删除功能,链接到这个main.js的视图,如下图所示。
//updating a todo.
function updateTodo(todoId) {
//contact server
return $.ajax({
method: "put",
url: `/todos/${todoId}`,
contentType: "application/json",
cache: false,
error: (error) => {
console.error(error);
},
});
}
//deleting a todo.
function deleteTodo(todoId) {
//contact server
return $.ajax({
method: "delete",
url: `/todos/${todoId}`,
contentType: "application/json",
cache: false,
success: () => {
location.reload();
},
error: (error) => {
console.error(error);
},
});
}
在上面的代码中,我们是。
- 使用AJAX与服务器进行通信,基于
PUT和DELETE方法。这是因为HTML表单默认不支持这些方法。
在添加头、CSS文件和main.js 之后,src/views/partials/header.ejs 应该是这个样子的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todolist app</title>
<link href="/static/css/bootstrap.min.css" rel="stylesheet" />
<link href="/static/css/custom.css" rel="stylesheet" />
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://kit.fontawesome.com/a076d05399.js" crossorigin="anonymous"></script>
<script src="/static/js/main.js"></script>
</head>
- 添加新的todo (
src/views/partials/add-todo.ejs) - 一个表单,在用户输入数据的同时,向服务器发送一个POST请求。
<div class="add-todo">
<h5 class="add-todo-heading">Add a todo.</h5>
<form class="add-todo-form" method="POST" action="/todo">
<div class="form-group form-title">
<label for="title"> Title </label>
<input id="title" type="text" class="form-control" name="title" placeholder="What do you want to do?" />
</div>
<div class="form-group form-submit">
<button type="submit" class="btn btn-primary">add todo</button>
</div>
</form>
</div>
- Todo列表(
src/views/partials/todos.ejs)。一个GET形式的方法获取所有的todo。每个待办事项都有一个删除按钮和一个切换按钮来检查已完成的待办事项。
<ul class="list-group">
<% for(let i=0; i < todos.length; i++) { %>
<li class="list-group-item d-flex justify-content-between align-items-center">
<%= todos[i].title %>
<div class="list-group-item-actions">
<div class="form-check">
<input type="checkbox" class="form-check-input" onclick="updateTodo(<%=todos[i].id %>)" <%=todos[i].checked ? "checked" : "" %>/>
</div>
<button class="delete-todo-form-btn" onclick="deleteTodo(<%= todos[i].id %>)">
<i class="far fa-trash-alt"></i>
</button>
</div>
</li>
<% } %>
</ul>
一些CSS来设计视图 (custom.css)。
.home-page {
width:100%;
height: 100;
margin-top: 10px;
padding:20px;
}
.todo-content {
width:100%;
}
.todo-heading {
width: 100%;
text-align: center;
margin: 10px 0px;
}
.list-group-item-actions{
display: flex;
justify-content: space-between;
}
.update-todo-form {
margin-right: 5px;
}
.add-todo{
width:80%;
margin: 20px auto;
padding: 10px 0px;
}
.add-todo-heading {
width: 100%;
text-align: center;
margin: 10px 0px;
}
.add-todo-form{
width:50%;
margin:0px auto;
display: flex;
justify-content: space-between;
}
.form-title {
width:80%;
margin-right: 10px;
}
.form-submit {
margin-top: 24px;
}
.delete-todo-form-btn{
border: none;
background: transparent;
cursor: pointer;
}
在GitHub上查看这个项目,并抓取用于样式bootstrap元素的bootstrap,如按钮。Bootstrap是在
bootstrap.min.css文件中指定的。
为了将应用程序与这些视图集成,我们需要在我们的服务器上添加这几行代码(src/index.js)。
app.use(express.urlencoded({extended:true}));
app.set("view engine","ejs");
app.set("views","src/views/pages");
app.use('/static',express.static(`${__dirname}/public`));
这将。
- 服务EJS引擎模板。
- 服务静态文件,如
.css文件。
将视图链接到路由
我们需要将我们的视图与后端连接起来。
在src/routes/todos.js 文件中添加以下配置。
//Get all todos.
router.get('/', async (req,res) => {
let todos = await new Todo().getTodos();
return res.render('home',{todos});
});
//Create a todo.
router.post('/todo', async (req,res) => {
let {title} = req.body;
await new Todo().createTodo({title},res);
return res.redirect('/')
});
//Update a todo.
router.put('/todos/:todoId', async (req,res) => {
let {todoId} = req.params;
await new Todo().updateTodo(todoId,res);
let todos = await new Todo().getTodos();
return res.render('home',{todos});
});
//Delete a todo.
router.delete('/todos/:todoId', async (req,res) => {
let {todoId} = req.params;
await new Todo().deleteTodo(todoId);
let todos = await new Todo().getTodos();
return res.render('home',{todos});
});
测试应用程序
确保开发服务器正在运行。如果没有,使用下面的命令重新启动它。
npm run dev
在你的浏览器中导航到http://localhost/4000 ,访问该应用程序。

或者,从这个GitHub仓库克隆这个项目。
克隆仓库后,在终端打开该项目,运行npm install ,安装项目的依赖性。
确保你的数据库按照我们在本指南中的描述设置好了。
使用npm run dev ,运行应用程序,并在浏览器中打开http://localhost/4000 ,与todo应用程序交互。然后,检查数据库,确认添加或更新todo是否反映出这样的情况。

总结
RESTful APIs允许客户端和服务器端是独立的。这是一个很大的优势,特别是在作为一个团队工作或当你想建立可扩展的应用程序时。