使用 Node, Sequelize, Postgres 和 Docker 搭建 CURD API【译】

在本文中,我们将使用 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

Node 是后端 JavaScript 运行时环境,这意味着可以在计算机 (例如,您的计算机或安装了 Node 的计算机) 上执行 JavaScript 代码。好消息是,通过使用 Docker,您实际上不需要安装它,因为我们将使用 Node 的 docker image,因此,我们也可以避免在我的机器上安装的我的 Node 版本和你的之间进行版本控制

POSTGRES

Postgres (PostgreSQL) 是一个免费的开源关系数据库,非常流行且稳定

SEQUELIZE

Sequelize 是 Node 的基于 Promise 的对象关系映射 (ORM)。ORM 是一种使用面向对象的编程语言从不兼容类型系统转换数据的技术。

它允许我们在数据库中创建和修改表,而无需执行 SQL 命令

它还适用于 MySQL、MariaDB、SQLite、Microsoft SQL Server。

DOCKER

Docker 是一个使用容器概念构建运行和共享应用程序的平台。如果你想简单介绍一下,这里有一个简短的视频

youtu.be/eN_O4zd4D9o

跟着步骤来

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 文件

我们的目录结构应该如下所示:

让我们编写 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

现在是 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” 命令验证两个容器是否都在运行

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

我们将使用 postman,但是你可以随时使用你想要的工具

让我们做一个像这样的 GET 请求

这意味着我们的服务器已启动并正在运行

让我们尝试获取所有用户

要添加用户,我们可以发出如下 POST 请求:

让我们再添加一个

现在,我们再看看用户列表,我们应该会看到这样的内容:

我们还可以通过在 URL 末尾添加用户的 id 来检查一个用户:

我们还可以通过 PUT 请求更新现有用户

如果我们尝试再次获得用户,我们会得到这个

现在,让我们尝试使用具有相对 id 的 DELETE 请求删除一个用户

服务器显示一个用户已被删除

如果我们尝试再次获得所有用户,只有 id 为 2 的记录被拒绝

结论

如果您尝试遵循本文,我想知道您是否遇到任何问题。谢谢

GitHub 仓库: github.com/FrancescoXX/docker-nsp

其他精彩文章