在本文中,我们将使用 Node, Sequelize, Postgres 和 Docker 搭建 CURD API。
原文地址:https://francescociulla.com/crud-api-using-node-sequelize-postgres-and-docker
GitHub Repository: github.com/FrancescoXX/docker-nsp
NODE
image.pngNode是后端JavaScript运行时环境,这意味着可以在计算机 (例如,您的计算机或安装了Node的计算机) 上执行JavaScript代码。好消息是,通过使用Docker,您实际上不需要安装它,因为我们将使用 Node 的 docker image,因此,我们也可以避免在我的机器上安装的我的 Node 版本和你的之间进行版本控制
POSTGRES
image.pngPostgres (PostgreSQL) 是一个免费的开源关系数据库,非常流行且稳定
SEQUELIZE
[图片上传失败...(image-3b0a5f-1612512236595)]
Sequelize是 Node 的基于 Promise 的对象关系映射 (ORM)。ORM是一种使用面向对象的编程语言从不兼容类型系统转换数据的技术。
它允许我们在数据库中创建和修改表,而无需执行SQL命令
它还适用于MySQL、MariaDB、SQLite、Microsoft SQL Server。
DOCKER
image.pngDocker是一个使用容器概念构建运行和共享应用程序的平台。如果你想简单介绍一下,这里有一个简短的视频
image.png跟着步骤来
1. 创建一个名为docker-nsp的文件夹 (代表node、sequelize、postgres) 并进入其中
mkdir docker-nsp && cd docker-nsp
2. 使用npm初始化 Node 应用
npm init -y
3. 安装依赖
npm install express pg sequelize
4. 创建结构
然后创建一个index.js文件
我们的目录结构应该如下所示:
image.png让我们编写index.js文件
const express = require('express');
const bodyParser = require('body-parser');
const sequelize = require('./util/database'); //database initializations
const User = require('./models/users'); //REQUIRED even if IDE says not used!
//INITIALIZE APP WITH EXPRESS
const app = express();
//BODYPARSER
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
//Set proper Headers on Backend
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
//ROUTES
app.use('/dev', require('./routes/dev')); //All test routes are placed here
app.use('/users', require('./routes/users')); //users crud
(async () => {
try {
await sequelize.sync(
{ force: false} //Reset db every time
);
app.listen(process.env.EXTERNAL_PORT); //DEF in docker.compose.yml
} catch (error) {
console.log(error);
}
})();
在util文件夹中,让我们创建与postgres db的连接
创建database.js文件
const Sequelize = require('sequelize');
//GET ENV VARIABLES FROM
const sequelize = new Sequelize(
process.env.PGDATABASE,
process.env.PGUSER,
process.env.PGPASSWORD,
{
host: process.env.PGHOST,
dialect: 'postgres'
});
module.exports = sequelize;
在路由文件夹中,让我们创建几个文件来重定向HTTP请求: dev.js和users.js
路由文件夹中的dev.js文件:
const controller = require('../controllers/dev');
const router = require('express').Router();
router.get('/config', controller.getConfig);
router.get('/version', controller.getVersion);
router.get('/seq', controller.seq); //test sequelize connection
module.exports = router;
路由文件夹中的users.js文件:
const controller = require('../controllers/' + 'users');
const router = require('express').Router();
//CRUD Model-Agnostic.
//Keep them at the end of the route file for url parsing requests
router
.get('/', controller.getAll)
.get('/:id', controller.getOne)
.post('/', controller.createOne)
.put('/:id', controller.updateOne)
.delete('/:id', controller.deleteOne);
module.exports = router;
在 “模型” 文件夹中,让我们创建users.js文件以用作用户的模型:
模型文件夹中的users.js文件:
const Sequelize = require('sequelize');
const db = require('../util/database');
const User = db.define('users', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
username: {
type: Sequelize.STRING,
allowNull: false,
unique: true
},
email: {
type: Sequelize.STRING,
allowNull: false,
},
password: {
type: Sequelize.STRING,
allowNull: false
}
});
module.exports = User;
模型(model)文件夹中的users.js文件:
控制器(controllers)文件夹中的dev.js文件:
const packJson = require('../../package.json');
const sequelize = require('../util/database');
// [GET] ../dev/config
exports.getConfig = (req, res, next) => {
return res.status(200).json({ packJson });
};
// [GET] ../dev/version
exports.getVersion = (req, res, next) => {
return res.status(200).json({ 'nps Backend': packJson.version });
};
// [GET] ../dev/seq
exports.seq = async (req, res, next) => {
try {
await sequelize.authenticate();
console.log('Sequelize Connection established');
res.status(200).json('Sequelize Connection established');
next();
} catch (error) {
next(error);
}
};
控制器(controllers)文件夹中的users.js文件:
const User = require("../models/users");
/**
* CRUD CONTROLLERS
*/
//CREATE-ONE
exports.createOne = async (req, res, next) => {
console.log("createOne: [POST] /users/");
try {
const USER_MODEL = {
username: req.body.username,
email: req.body.email,
password: req.body.password,
role: req.body.role,
}
try {
const user = await User.create(USER_MODEL);
console.log("OK createOne USER: ", user);
return res.status(201).json(user);
} catch (error) {
console.log('ERROR in createOne ' + "USER:", error);
return res.status(500).json(error);
}
} catch (error) {
return res.status(400).json("Bad Request");
}
};
//GET-ALL
exports.getAll = async (req, res, next) => {
console.log("getAll: [GET] /users/");
try {
const ALL = await User.findAll();
console.log("OK getAll USER: ", ALL.map(el => el.dataValues));
return res.status(200).json(ALL);
} catch (error) {
console.log('ERROR in getAll ' + "USER:", error);
return res.status(500).json(error);
}
};
//GET-ONE
exports.getOne = async (req, res, next) => {
console.log("getOne: [GET] /users/:id");
try {
const u = await User.findByPk(req.params.id);
console.log("OK getOne USER: ", u.dataValues);
return res.status(200).json(u);
} catch (error) {
console.log('ERROR in getOne ' + "USER:", error);
return res.status(500).json(error);
}
};
//UPDATE-ONE.
exports.updateOne = async (req, res, next) => {
console.log("updateOne: [PUT] /users/:id");
try {
const USER_MODEL = {
username: req.body.username,
email: req.body.email,
password: req.body.password,
role: req.body.role
}
try {
const u = await User.update(USER_MODEL, { where: { id: req.params.id } });
console.log("OK updateOne USER: ", u);
return res.status(200).json(u);
} catch (error) {
console.log('ERROR in updateOne ' + "USER:", error);
return res.status(500).json(error);
}
} catch (error) {
return res.status(400).json("Bad Request");
}
};
//DELETE-ONE
exports.deleteOne = async (req, res, next) => {
console.log("[DELETE] /users/:id");
try {
const u = await User.destroy({ where: { id: req.params.id } });
console.log("OK deleteOne USER: ", );
return res.status(200).json(u);
} catch (error) {
console.log('ERROR in deleteOne ' + "USER:", error);
return res.status(500).json(error);
}
};
DOCKER
image.png现在是Docker部分!
在主文件夹中,创建3个文件:
- Dockerfile
- docker-compose.yml
- .dockerignore (it starts with a dot)
.dockerignore 文件:
.git
node_modules
npm-debug.log
然后 Dockerfile:
FROM node:14
EXPOSE 3001
# Use latest version of npm
RUN npm i npm@latest -g
COPY package.json package-lock.json* ./
RUN npm install --no-optional && npm cache clean --force
# copy in our source code last, as it changes the most
WORKDIR /opt
COPY . .
CMD [ "node", "app/index.js" ]
docker-compose.yml 文件:
version: "3.8"
services:
nsp_backend:
container_name: nsp_backend
image: francescoxx/nsp-template:0.0.2
build:
context: .
ports:
- "3001:3001"
environment:
- EXTERNAL_PORT=3001
- PGUSER=francesco
- PGPASSWORD=12345
- PGDATABASE=nps_database
- PGHOST=nsp_db # NAME OF THE SERVICE
depends_on:
- nsp_db
nsp_db:
container_name: nsp_db
image: "postgres:12"
ports:
- "5432:5432"
environment:
- POSTGRES_USER=francesco
- POSTGRES_PASSWORD=12345
- POSTGRES_DB=nps_database
volumes:
- nps_data:/var/lib/postgresql/data
volumes:
nps_data: {}
将docker 镜像 “francescoxx/nsp-模板: 0.0.2” 替换为您选择的 docker 镜像名称!
让我们启动nsp_db服务:
docker-compose up -d nsp_db
我们应该有一个Postgres数据库启动并运行!
让我们检查一下数据库中的内容:
docker exec -it nsp_db psql -U francesco postgres
一旦我们进入容器 (您可以通过检查postgres = # 终端来验证这一点)
连接到nsp_database
这意味着postgres使用我们在开始时传递的环境变量创建了一个名为 “nsp_database” 的数据库
然后:
您应该会收到以下消息:
"没有发现任何表(relations)"
这是因为我们已经使用环境变量创建了数据库,但是我们还没有创建任何表或关系
退出
然后重新进入终端
是时候去构建我们的Docker 镜像的
定位到 docker-compose.yml 文件所在的目录,然后运行:
现在是时候运行我们的 Node 应用程序了
docker-compose up -d nsp_backend
我们可以使用 “docker ps -a” 命令验证两个容器是否都在运行
[图片上传失败...(image-71b001-1612512236595)]
Sequelize将为我们创建一个数据库
尝试再次启动命令
docker exec -it nsp_db psql -U francesco postgres
尝试再次启动命令
现在
您应该看到表 “users”。这是在我们启动节点应用程序时通过Sequelize创建的。
无论如何,如果我们执行
我们应该得到一张空表
nsp_database=# select * from users; id | username | email | password | createdAt | updatedAt ----+----------+-------+----------+-----------+----------- (0 rows)
现在有趣的是,让我们测试一下API!
POSTMAN
image.png我们将使用 postman,但是你可以随时使用你想要的工具
让我们做一个像这样的GET请求
image.png这意味着我们的服务器已启动并正在运行
让我们尝试获取所有用户
image.png要添加用户,我们可以发出如下POST请求:
image.png让我们再添加一个
image.png现在,我们再看看用户列表,我们应该会看到这样的内容:
image.png我们还可以通过在URL末尾添加用户的id来检查一个用户:
image.png我们还可以通过PUT请求更新现有用户
image.png如果我们尝试再次获得用户,我们会得到这个
image.png现在,让我们尝试使用具有相对id的DELETE请求删除一个用户
image.png服务器显示一个用户已被删除
image.png如果我们尝试再次获得所有用户,只有id 为 2 的记录被拒绝
image.png结论
如果您尝试遵循本文,我想知道您是否遇到任何问题。谢谢
GitHub 仓库: github.com/FrancescoXX/docker-nsp
网友评论