使用 Sequelize 快速构建 PostgreSQL 数据的 CRUD 操作

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核4GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 之前写过一个专栏《布道API》来介绍API的REST风格及推荐实践,今天开始来构建一个管理系统的API服务,首先需要处理的就是数据存储,本文将结合实际开发总结在 NodeJS 下使用 Sequelize 快速构建 PostgreSQL 数据的 CRUD 操作。

之前写过一个专栏《布道API》来介绍API的REST风格及推荐实践,今天开始来构建一个管理系统的API服务,首先需要处理的就是数据存储,本文将结合实际开发总结在 NodeJS 下使用 Sequelize 快速构建 PostgreSQL 数据的 CRUD 操作。

项目源代码:github.com/QuintionTan…

Sequelize

Sequelize 是一个基于 promise 的 Node.js ORM 工具,它具有强大的事务支持、关联关系、预读和延迟加载、读取复制等功能,支持的数据库包括:PostgreSQLMySQLMariaDBSQLiteMSSQL

Sequelize 类是引用 sequlize 模块后获取一个顶级对象,通过它来创建 sequlize 实例,也可以通过该对象来获取模内其它对象的引用,如:Utils工具类、Transaction 事务类等。创建实例后,可以通过实例来创建或定义 Model(模型)、执行查询、同步数据库结构等操作。

官方网站:docs.sequelizejs.com/

添加和配置

在安装模块之前,首先安装开发工具Sequelize-CLI

sudo npm install -g sequelize-cli

接下来在项目目录下安装数据存储相关的模块。

npm install  sequelize --save
npm install pg pg-hstore  --save

现在在项目根目录下创建文件.sequelizerc,代码如下:

const path = require('path');
module.exports = {
  "config": path.resolve('./config', 'db.json'),
  "models-path": path.resolve('./models'),
  'seeders-path': path.resolve('./seeders'),
  'migrations-path': path.resolve('./migrations')
};

该文件将告诉 Sequelize 初始化,以生成configmodels 到特定目录。接下来,输入命令初始化 Sequelize

sequelize init

该命令将创建 config/db.jsonmodels/index.jsmigrationsseeders 目录和文件。命令执行完毕之后打开并编辑 config/db.json 来配置数据库连接信息。

{
    "development": {
        "username": "dbusername",
        "password": "dbpassword",
        "database": "crayon-admin",
        "host": "127.0.0.1",
        "dialect": "postgres",
        "options": {
            "operatorsAliases": false
        },
        "logging": false
    },
    "test": {
        "username": "dbusername",
        "password": "dbpassword",
        "database": "crayon-admin",
        "host": "127.0.0.1",
        "dialect": "postgres"
    },
    "production": {
        "username": "dbusername",
        "password": "dbpassword",
        "database": "crayon-admin",
        "host": "127.0.0.1",
        "dialect": "postgres"
    }
}

目录说明:

  • migrations:所有迁移文件,通过sequelize db:migrate 创建相应数据表
  • seeders:种子文件,即初始化需要插入到数据库中的数据,运行sequelize db:seed:all

创建 Models 和 Migrations

使用CLI工具Sequelize-CLI 创建 administrators

sequelize model:create --name administrators --attributes id:integer,add_time:integer,last_login:integer,username:string,email:string,login_ip:string

执行后会生成两个文件

  • /src/migrations/20210803095520-create-administrators.js :创建数据表脚本,用于数据库初始化。
"use strict";
module.exports = {
    up: async (queryInterface, Sequelize) => {
        await queryInterface.createTable("administrators", {
            id: {
                allowNull: false,
                autoIncrement: true,
                primaryKey: true,
                type: Sequelize.INTEGER,
            },
            add_time: {
                type: Sequelize.INTEGER,
            },
            last_login: {
                type: Sequelize.INTEGER,
            },
            username: {
                type: Sequelize.STRING,
            },
            password: {
                type: Sequelize.STRING,
            },
            email: {
                type: Sequelize.STRING,
            },
            login_ip: {
                type: Sequelize.STRING,
            },
        });
    },
    down: async (queryInterface, Sequelize) => {
        await queryInterface.dropTable("administrators");
    },
};
  • /src/models/administrators.js :生成的model文件
"use strict";
const { Model } = require("sequelize");
module.exports = (sequelize, DataTypes) => {
    class administrators extends Model {}
    administrators.init(
        {
            id: {
                type: DataTypes.INTEGER,
                autoIncrement: true,
                primaryKey: true,
            },
            add_time: DataTypes.INTEGER,
            last_login: DataTypes.INTEGER,
            username: DataTypes.STRING,
            password: DataTypes.STRING,
            email: DataTypes.STRING,
            login_ip: DataTypes.STRING,
        },
        {
            sequelize,
            indexes: [
                {
                    unique: true,
                    fields: ["id"],
                },
            ],
            freezeTableName: true,
            timestamps: false, // 是否自动添加时间戳createAt,updateAt
            modelName: "administrators",
        }
    );
    return administrators;
};

现在执行命令:

sequelize db:migrate

执行成功后将在连接的数据库中创建数据表:administrators

创建 seed

seed 用于初始化插入数据,如管理员,在系统运行前需要创建一个默认账号,这些默认账号信息就写在 seed 文件中。创建 seed 命令如下:

sequelize seed:create --name administrator

执行成功后将会在 seeders 文件夹中创建文件,修改代码如下:

"use strict";
module.exports = {
    up: async (queryInterface, Sequelize) => {
        /**
         * Add seed commands here.
         *
         * Example:
         * await queryInterface.bulkInsert('People', [{
         *   name: 'John Doe',
         *   isBetaMember: false
         * }], {});
         */
        await queryInterface.bulkInsert(
            "administrators",
            [
                {
                    id:1,
                    username: "administrators",
                    password: "devpoint",
                    email: "QuintionTang@gmail.com",
                    add_time:1627828617,
                    last_time:1627828617
                },
            ],
            {}
        );
    },
    down: async (queryInterface, Sequelize) => {
        /**
         * Add commands to revert seed here.
         *
         * Example:
         * await queryInterface.bulkDelete('People', null, {});
         */
    },
};

现在将 seed 中的数据插入到数据库中,执行一下命令:

sequelize db:seed:all

创建 Services

创建文件夹 services , 文件夹中代码封装与 model 交互的方法,包括所有CRUD(创建,读取,更新和删除)操作,创建 administrators.js ,实现的逻辑为获取账号信息、更新账号信息,代码如下:

const AdministratorsModel = require("../models").administrators;
class AdministratorsService {
    constructor() {}
    async get(username) {
        try {
            const userinfo = await AdministratorsModel.findOne({
                where: { username },
            });
            return userinfo;
        } catch (error) {
            throw error;
        }
    }
    async add(newData) {
        try {
            return await AdministratorsModel.create(newData);
        } catch (error) {
            throw error;
        }
    }
    async del(id) {
        try {
            const isExist = await AdministratorsModel.findOne({
                where: { id: Number(id) },
            });
            if (isExist) {
                const deleted = await AdministratorsModel.destroy({
                    where: { id: Number(id) },
                });
                return deleted;
            }
            return null;
        } catch (error) {
            throw error;
        }
    }
    async update(id, updateData) {
        try {
            const isExist = await AdministratorsModel.findOne({
                where: { id: Number(id) },
            });
            if (isExist) {
                await AdministratorsModel.update(updateData, {
                    where: { id: Number(id) },
                });
                return updateData;
            }
            return null;
        } catch (error) {
            throw error;
        }
    }
}
module.exports = new AdministratorsService();

创建 Controllers

上面创建的 services 文件用于控制器,在控制器文件夹中创建一个名为 administrators.js 的文件, 代码如下:

const administratorsService = require("../services/administrators");
const util = require("../utils");
class AdministratorsController {
    constructor() {}
    async login(req, res) {
        const { username, passowrd } = req.body;
        try {
            const userinfo = await administratorsService.get(username);
            console.log(userinfo);
            if (!userinfo) {
                util.setError(200, 30004, `用户名不存在: ${username}`);
            } else {
                util.setSuccess(200, "登录成功", userinfo);
            }
            return util.send(res);
        } catch (error) {
            util.setError(404, error);
            return util.send(res);
        }
    }
}
module.exports = new AdministratorsController();

创建 Routers

在文件夹 routers 中创建文件 administrators.js 文件,代码如下:

const Router = require("express");
const administratorController = require("../controllers/administrators");
const administratorsRouter = Router();
administratorsRouter.post("/login", administratorController.login);
module.exports = administratorsRouter;

创建入口

现在来为服务创建接口,项目根目录下创建文件 app.js ,代码如下:

"use strict";
const administratorsRouter = require("./src/routers/administrators");
require("./src/utils/logger.js")(2);
const pjson = require("./package.json");
const os = require("os");
const express = require("express");
const app = express();
const bodyParser = require("body-parser");
const CONFIG = require("./config");
const cookieParser = require("cookie-parser");
function _version(serviceUrl) {
    const serviceInfo = {
        name: os.hostname(),
        os: os.platform(),
        os_v: os.release(),
        version: "v" + pjson.version,
    };
    console.info("   ");
    console.info("   ", serviceInfo.name);
    console.success("   ", serviceInfo.version);
    console.success("   ", serviceUrl);
    console.info("   ");
    console.info("   ");
}
function _isAuth(req) {
    if (req.cookies) {
        return req.cookies.auth;
    } else {
        return false;
    }
}
function _setAuth(res, userinfo) {
    res.cookie("auth", userinfo);
}
function _formatResponse(code, message, data) {
    return Object.assign(
        {
            code: code,
            message: message,
        },
        data
    );
}
const allowDomains = "*"; //如发布需改成:127.0.0.1
app.all(allowDomains, (req, res, next) => {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("origin", "127.0.0.1:4200");
    res.header(
        "Access-Control-Allow-Headers",
        "Origin, X-Requested-With, Content-Type, Accept,application/x-www-form-urlencoded"
    );
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    res.header("Content-Type", "application/json;charset=utf-8");
    const noCheckPaths = ["/api/v1/auth/login"];
    if (req.method == "OPTIONS") {
        res.send(200);
    } else {
        if (noCheckPaths.includes(req.path)) {
            next();
        } else {
            const authInfo = _isAuth(req);
            if (authInfo && authInfo.name) {
                next();
            } else {
                res.send(401);
            }
        }
    }
});
app.use(cookieParser());
app.use(bodyParser.json());
app.use(
    bodyParser.urlencoded({
        extended: true,
    })
);
app.use("/api/v1/auth", administratorsRouter);
// 开始运行
const port = process.env.PORT || CONFIG.port;
_version(`running at http://127.0.0.1:${port}`);
app.listen(port);

现在执行命令 node app.js 启动服务,将看到终端效果如下:

image.png

至此,完成一个基本的 API 登录服务,还有待完善,后续在迭代中完善。文章涉及的代码在 GitHub 上。


相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
相关文章
|
6月前
|
SQL Oracle 关系型数据库
实时计算 Flink版操作报错之往GREENPLUM 6 写数据,用postgresql-42.2.9.jar 报 ON CONFLICT (uuid) DO UPDATE SET 语法有问题。怎么解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
6月前
|
SQL 关系型数据库 数据库
实时计算 Flink版操作报错之使用SQL 将 PostgreSQL 的 date 类型字段转换为 TIMESTAMP 类型时遇到报错,该如何处理
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
5月前
|
消息中间件 Java 关系型数据库
实时计算 Flink版操作报错合集之从 PostgreSQL 读取数据并写入 Kafka 时,遇到 "initial slot snapshot too large" 的错误,该怎么办
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
1010 0
|
5月前
|
DataWorks 安全 关系型数据库
DataWorks产品使用合集之使用Flink CDC读取PostgreSQL数据时如何指定编码格式
DataWorks作为一站式的数据开发与治理平台,提供了从数据采集、清洗、开发、调度、服务化、质量监控到安全管理的全套解决方案,帮助企业构建高效、规范、安全的大数据处理体系。以下是对DataWorks产品使用合集的概述,涵盖数据处理的各个环节。
|
3月前
|
SQL 关系型数据库 MySQL
SQL Server、MySQL、PostgreSQL:主流数据库SQL语法异同比较——深入探讨数据类型、分页查询、表创建与数据插入、函数和索引等关键语法差异,为跨数据库开发提供实用指导
【8月更文挑战第31天】SQL Server、MySQL和PostgreSQL是当今最流行的关系型数据库管理系统,均使用SQL作为查询语言,但在语法和功能实现上存在差异。本文将比较它们在数据类型、分页查询、创建和插入数据以及函数和索引等方面的异同,帮助开发者更好地理解和使用这些数据库。尽管它们共用SQL语言,但每个系统都有独特的语法规则,了解这些差异有助于提升开发效率和项目成功率。
360 0
|
3月前
|
SQL 关系型数据库 HIVE
实时计算 Flink版产品使用问题之如何将PostgreSQL数据实时入库Hive并实现断点续传
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
4月前
|
分布式计算 DataWorks 关系型数据库
DataWorks操作报错合集之使用连接串模式新增PostgreSQL数据源时遇到了报错"not support data sync channel, error code: 0001",该怎么办
DataWorks是阿里云提供的一站式大数据开发与治理平台,支持数据集成、数据开发、数据服务、数据质量管理、数据安全管理等全流程数据处理。在使用DataWorks过程中,可能会遇到各种操作报错。以下是一些常见的报错情况及其可能的原因和解决方法。
|
4月前
|
SQL 监控 关系型数据库
实时计算 Flink版操作报错合集之在设置监控PostgreSQL数据库时,将wal_level设置为logical,出现一些表更新和删除操作报错,怎么办
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
4月前
|
关系型数据库 MySQL 数据库
实时计算 Flink版操作报错合集之在处理PostgreSQL数据库遇到报错。该如何解决
在使用实时计算Flink版过程中,可能会遇到各种错误,了解这些错误的原因及解决方法对于高效排错至关重要。针对具体问题,查看Flink的日志是关键,它们通常会提供更详细的错误信息和堆栈跟踪,有助于定位问题。此外,Flink社区文档和官方论坛也是寻求帮助的好去处。以下是一些常见的操作报错及其可能的原因与解决策略。
|
4月前
|
Java 关系型数据库 API
使用Spring Boot和PostgreSQL构建高级查询
使用Spring Boot和PostgreSQL构建高级查询