应用双节点,数据库是dg_如何使用节点构建轻量级发票应用程序:数据库和API

news/2024/7/7 4:37:33

应用双节点,数据库是dg

介绍 (Introduction)

To get paid for goods and services provided, businesses need to send invoices to their customers informing them of the services that they will be charged for. Back then, people had paper invoices which they gave to the customers when they contacted them for their services. With the advent and advancement of technology, people are now able to send electronic invoices to their customers.

为了从提供的商品和服务中获得报酬,企业需要向客户发送发票,以告知他们将向其收取费用的服务。 那时,人们有纸发票,当他们联系他们以获取服务时便给了他们。 随着技术的出现和进步,人们现在可以将电子发票发送给他们的客户。

In this tutorial you will build an invoicing application using Vue and NodeJS. This application will perform functions such as creating, sending, editing, and deleting an invoice.

在本教程中,您将使用Vue和NodeJS构建发票应用程序。 该应用程序将执行诸如创建,发送,编辑和删除发票之类的功能。

要求 (Requirements)

To follow through the article adequately, you’ll need the following:

为了充分阅读本文,您需要满足以下条件:

  • Node installed on your machine

    安装在计算机上的节点
  • Node Package Manager (NPM) installed on your machine

    您计算机上安装的节点软件包管理器(NPM)

Run the following to verify that Node is installed on your machine:

运行以下命令以验证您的计算机上是否安装了Node:

  • node --version

    节点-版本

Run the following to verify that NPM is installed on your machine:

运行以下命令以验证计算机上是否已安装NPM:

  • npm --version

    npm --version

If you get version numbers as results then you’re ready to proceed.

如果得到版本号作为结果,则可以继续进行。

步骤1 —设置服务器 (Step 1 — Setting Up Server)

Now that we have the requirements all set, the next thing to do is to create the backend server for the application. The backend server will maintain the database connection.

现在我们已经设置了所有要求,接下来要做的就是为应用程序创建后端服务器。 后端服务器将维护数据库连接。

Start by creating a folder to house the new project:

首先创建一个文件夹来容纳新项目:

  • mkdir invoicing-app

    mkdir发票应用

Initialize it as a Node project:

将其初始化为Node项目:

  • cd invoicing-app && npm init

    cd invoicing-app && npm init

For the server to function appropriately, there are some Node packages that need to be installed. You can install them by running this command:

为了使服务器正常运行,需要安装一些Node软件包。 您可以通过运行以下命令来安装它们:

  • npm install --save express body-parser connect-multiparty sqlite3 bluebird path umzug bcrypt

    npm install --save express body-parser connect-multiparty sqlite3 bluebird路径umzug bcrypt

That command installs the following packages:

该命令将安装以下软件包:

  • bcrypt to hash user passwords

    bcrypt哈希用户密码

  • express to power our web application

    express为我们的Web应用程序提供动力

  • sqlite3 to create and maintain the database

    sqlite3创建和维护数据库

  • path to resolve file paths within our application

    path到我们的应用程序内解决文件路径

  • bluebird to use Promises when writing migrations

    bluebird在编写迁移时使用Promises

  • umzug as a task runner to run our database migrations

    umzug作为任务运行器来运行我们的数据库迁移

  • body-parser and connect-multiparty to handle incoming form requests

    body-parserconnect-multiparty处理传入的表单请求

Create a server.js file that will house the application logic:

创建一个将包含应用程序逻辑的server.js文件:

  • touch server.js

    触摸server.js

In the server.js file, import the necessary modules and create an express app:

server.js文件中,导入必要的模块并创建一个快速应用程序:

server.js
server.js
const express = require('express')
    const bodyParser = require('body-parser');
    const sqlite3 = require('sqlite3').verbose();
    const PORT = process.env.PORT || 3128;

    const app = express();
    app.use(bodyParser.urlencoded({extended: false}));
    app.use(bodyParser.json());

    [...]

Create a / route to test that the server works:

创建一个/路由以测试服务器是否正常工作:

server.js
server.js
[...]

    app.get('/', function(req,res){
        res.send("Welcome to Invoicing App");
    });

    app.listen(PORT, function(){
        console.log(`App running on localhost:${PORT}`);
    });

app.listen() tells the server the port to listen to for incoming routes. To start the server, run the following in your project directory:

app.listen()告诉服务器端口监听入站路由。 要启动服务器,请在项目目录中运行以下命令:

  • node server

    节点服务器

Your application will now begin to listen to incoming requests.

您的应用程序现在将开始侦听传入的请求。

第2步-使用SQLite创建并连接到数据库 (Step 2 — Creating and Connecting To Database Using SQLite)

For an invoicing application, a database is needed to store the existing invoices. SQLite is going to be the database client of choice for this application.

对于发票应用程序,需要一个数据库来存储现有发票。 SQLite将成为该应用程序的首选数据库客户端。

Start by creating a database folder:

首先创建一个database文件夹:

  • mkdir database

    mkdir数据库

Next, move into the new directory and create a file for your database:

接下来,进入新目录并为您的数据库创建一个文件:

  • cd database && touch InvoicingApp.db

    cd数据库&&触摸InvoicingApp.db

In the database directory, run the sqlite3 client:

在数据库目录中,运行sqlite3客户端:

  • invoicing-app/database/ sqlite3

    发票应用程序/数据库/ sqlite3

Open the InvoicingApp.db database:

打开InvoicingApp.db数据库:

  • .open InvoicingApp.db

    .open InvoicingApp.db

Now that the database has been selected, next thing is to create the needed tables.

现在已经选择了数据库,下一步是创建所需的表。

This application will use three tables:

该应用程序将使用三个表:

  • Users - This will contain the user data (id, name, email, company_name, password)

    用户 -这将包含用户数据(ID,名称,电子邮件,公司名称,密码)

  • Invoices - Store data for an invoice (id, name, paid, user_id)

    发票 -存储发票数据(ID,名称,已付款,user_id)

  • Transactions - Singular transactions that come together to make an invoice (name, price, invoice_id)

    交易 -一起构成发票的单一交易(名称,价格,invoice_id)

Since the necessary tables have been identified, the next step is to run the queries to create the tables.

由于已经确定了必要的表,因此下一步是运行查询以创建表。

Migrations are used to keep track of changes in a database as the application grows. To do this, create a migrations folder in the database directory.

迁移用于随着应用程序的增长跟踪数据库中的更改。 为此,请在database目录中创建一个migrations文件夹。

  • mkdir migrations

    mkdir迁移

This will house all the migration files.

这将容纳所有迁移文件。

Now, create a 1.0.js file in the migrations folder. This naming convention is to keep track of the newest changes.

现在,在migrations文件夹中创建一个1.0.js文件。 此命名约定是为了跟踪最新的更改。

  • cd migrations && touch 1.0.js

    cd迁移&&触摸1.0.js

In the 1.0.js file, you first import the node modules:

1.0.js文件中,首先导入节点模块:

database/migrations 1.0.js
数据库/迁移1.0.js
"use strict";
    const Promise = require("bluebird");
    const sqlite3 = require("sqlite3");
    const path = require('path');

    [...]

Then, the idea now is to export an up function that will be executed when the migration file is run and a down function to reverse the changes to the database.

然后,现在的想法是导出运行迁移文件时将要执行的up函数和一个down函数以将更改撤消到数据库。

database/migrations/1.0.js
数据库/迁移/ 1.0.js
[...]
    module.exports = {
      up: function() {
        return new Promise(function(resolve, reject) {
          /* Here we write our migration function */
          let db = new sqlite3.Database('./database/InvoicingApp.db');
          //   enabling foreign key constraints on sqlite db
          db.run(`PRAGMA foreign_keys = ON`);

          [...]

In the up function, connection is first made to the database. Then the foreign keys are enabled on the sqlite database. In SQLite, foreign keys are disabled by default to allow for backwards compatibility, so the foreign keys have to be enabled on every connection.

up功能中,首先建立与数据库的连接。 然后在sqlite数据库上启用外键。 在SQLite中,默认情况下禁用外键以实现向后兼容性,因此必须在每个连接上启用外键。

Next, specify the queries to create the tables:

接下来,指定查询以创建表:

database/migrations/1.0.js
数据库/迁移/ 1.0.js
[...]
          db.serialize(function() {
            db.run(`CREATE TABLE users (
              id INTEGER PRIMARY KEY,
              name TEXT,
              email TEXT,
              company_name TEXT,
              password TEXT
            )`);

            db.run(`CREATE TABLE invoices (
              id INTEGER PRIMARY KEY,
              name TEXT,
              user_id INTEGER,
              paid NUMERIC,
              FOREIGN KEY(user_id) REFERENCES users(id)
            )`);

            db.run(`CREATE TABLE transactions (
              id INTEGER PRIMARY KEY,
              name TEXT,
              price INTEGER,
              invoice_id INTEGER,
              FOREIGN KEY(invoice_id) REFERENCES invoices(id)
            )`);
          });
          db.close();
        });
      },
      [...]

The serialize() function is used to specify that the queries will be ran sequentially and not simultaneously.

serialize()函数用于指定查询将按顺序而不是同时运行。

Afterwards, the queries to reverse the changes are also specified in the down() function:

之后,还可以在down()函数中指定用于撤消更改的查询:

database/migrations/1.0.js
数据库/迁移/ 1.0.js
[...]

      down: function() {
        return new Promise(function(resolve, reject) {
          /* This runs if we decide to rollback. In that case we must revert the `up` function and bring our database to it's initial state */
          let db = new sqlite3.Database("./database/InvoicingApp.db");
          db.serialize(function() {
            db.run(`DROP TABLE transactions`);
            db.run(`DROP TABLE invoices`);
            db.run(`DROP TABLE users`);
          });
          db.close();
        });
      }
    };

Once the migration files have been created, the next step is running them to make the changes in the database. To do this, create a scripts folder from the root of your application:

创建迁移文件后,下一步就是运行它们以在数据库中进行更改。 为此,请从应用程序的根目录创建一个scripts文件夹:

  • mkdir scripts

    mkdir脚本

Then create a file called migrate.js:

然后创建一个名为migrate.js的文件:

  • cd scripts && touch migrate.js

    cd脚本&&触摸migration.js

Add the following to the migrate.js file:

将以下内容添加到migrate.js文件中:

scripts/migrate.js
脚本/migrate.js
const path = require("path");
    const Umzug = require("umzug");

    let umzug = new Umzug({
      logging: function() {
        console.log.apply(null, arguments);
      },
      migrations: {
        path: "./database/migrations",
        pattern: /\.js$/
      },
      upName: "up",
      downName: "down"
    });

    [...]

First, the needed node modules are imported. Then a new umzug object is created with the configurations. The path and pattern of the migrations scripts are also specified. To learn more about the configurations, head over to the umzug GitHub page.

首先,导入所需的节点模块。 然后使用配置创建一个新的umzug对象。 还指定了迁移脚本的路径和模式。 要了解有关配置的更多信息,请转到umzug GitHub页面 。

To also give some verbose feedback, create a function to log events as shown below and then finally execute the up function to run the database queries specified in the migrations folder:

为了提供一些详细的反馈,请创建一个函数来记录事件,如下所示,然后最终执行up函数来运行migrations文件夹中指定的数据库查询:

scripts/migrate.js
脚本/migrate.js
[...]

    function logUmzugEvent(eventName) {
      return function(name, migration) {
        console.log(`${name} ${eventName}`);
      };
    }

    // using event listeners to log events
    umzug.on("migrating", logUmzugEvent("migrating"));
    umzug.on("migrated", logUmzugEvent("migrated"));
    umzug.on("reverting", logUmzugEvent("reverting"));
    umzug.on("reverted", logUmzugEvent("reverted"));

    // this will run your migrations
    umzug.up().then(console.log("all migrations done"));

Now, to execute the script, go to your terminal and in the root directory of your application, run:

现在,要执行脚本,请转到终端并在应用程序的根目录中运行:

  • ~/invoicing-app node scripts/migrate.js up

    〜/ invoicing-app节点脚本/migrate.js向上

You will see output similar to the following:

您将看到类似于以下内容的输出:


   
Output
all migrations done == 1.0: migrating ======= 1.0 migrating

步骤3 —创建应用程序路由 (Step 3 — Creating Application Routes)

Now that the database is adequately set up, the next thing is to go back to the server.js file and create the application routes. For this application, the following routes will be made available:

现在已经正确设置了数据库,下一步是回到server.js文件并创建应用程序路由。 对于此应用程序,将提供以下路由:

URLMETHODFUNCTION
/registerPOSTTo register a new user
/loginPOSTTo log in an existing user
/invoicePOSTTo create a new invoice
/invoice/user/{user_id}GETTo fetch all the invoices for a user
/invoice/user/{user_id}/{invoice_id}GETTo fetch a certain invoice
/invoice/sendPOSTTo send invoice to client
网址 方法 功能
/register POST 注册新用户
/login POST 登录现有用户
/invoice POST 创建新发票
/invoice/user/{user_id} GET 提取用户的所有发票
/invoice/user/{user_id}/{invoice_id} GET 提取特定发票
/invoice/send POST 向客户发送发票

To register a new user, a post request will be made to the /register route of your server. This route will look like this:

要注册新用户,将向服务器的/register路由发出发布请求。 该路线如下所示:

server.js
server.js
[...]
    const bcrypt = require('bcrypt')
    const saltRounds = 10;
    [...]

    app.post('/register', function(req, res){
        // check to make sure none of the fields are empty
        if( isEmpty(req.body.name)  || isEmpty(req.body.email) || isEmpty(req.body.company_name) || isEmpty(req.body.password) ){
            return res.json({
                'status' : false,
                'message' : 'All fields are required'
            });
        }
        // any other intendend checks

        [...]

A check is made to see if any of the fields are empty and if the data sent matches all the specifications. If an error occurs, an error message is sent to the user as a response. If not, the password is hashed and the data is then stored in the database and a response is sent to the user informing them that they are registered.

检查是否有任何字段为空,以及发送的数据是否符合所有规范。 如果发生错误,则会将错误消息发送给用户作为响应。 如果不是,则对密码进行哈希处理,然后将数据存储在数据库中,并将响应发送给用户,通知他们已注册。

server.js
server.js
bcrypt.hash(req.body.password, saltRounds, function(err, hash) {
        let db = new sqlite3.Database("./database/InvoicingApp.db");
        let sql = `INSERT INTO users(name,email,company_name,password) VALUES('${
          req.body.name
        }','${req.body.email}','${req.body.company_name}','${hash}')`;
        db.run(sql, function(err) {
          if (err) {
            throw err;
          } else {
            return res.json({
              status: true,
              message: "User Created"
            });
          }
        });
        db.close();
      });
    });

If an existing user tries to log in to the system using the /login route, they need to provide their email address and password. Once they do that, the route handles the request as follows:

如果现有用户尝试使用/login路由/login系统,则需要提供其电子邮件地址和密码。 一旦他们这样做,路由将按以下方式处理请求:

server.js
server.js
[...]

    app.post("/login", function(req, res) {
      let db = new sqlite3.Database("./database/InvoicingApp.db");
      let sql = `SELECT * from users where email='${req.body.email}'`;
      db.all(sql, [], (err, rows) => {
        if (err) {
          throw err;
        }
        db.close();
        if (rows.length == 0) {
          return res.json({
            status: false,
            message: "Sorry, wrong email"
          });
        }

      [...]

A query is made to the database to fetch the record of the user with a particular email. If the result returns an empty array, then it means that the user doesn’t exist and a response is sent informing the user of the error.

对数据库进行查询,以获取具有特定电子邮件的用户记录。 如果结果返回一个空数组,则表示该用户不存在,并且发送了一个响应,通知用户该错误。

If the database query returns user data, a further check is made to see if the password entered matches that password in the database. If it does, then a response is sent with the user data.

如果数据库查询返回用户数据,则进一步检查以查看输入的密码是否与数据库中的密码匹配。 如果是这样,则将与用户数据一起发送响应。

server.js
server.js
[...]
        let user = rows[0];
        let authenticated = bcrypt.compareSync(req.body.password, user.password);
        delete user.password;
        if (authenticated) {
          return res.json({
            status: true,
            user: user
          });
        }
        return res.json({
          status: false,
          message: "Wrong Password, please retry"
        });
      });
    });

    [...]

When the route is tested, you will receive either a successful or failed result.

测试路由后,您将收到成功或失败的结果。

The /invoice route handles the creation of an invoice. Data passed to the route will include the user ID, name of the invoice, and invoice status. It will also include the singular transactions to make up the invoice.

/invoice路由处理/invoice的创建。 传递到路线的数据将包括用户ID,发票名称和发票状态。 它还将包括单个交易以构成发票。

The server handles the request as follows:

服务器按以下方式处理请求:

server.js
server.js
[...]
    app.post("/invoice", multipartMiddleware, function(req, res) {
      // validate data
      if (isEmpty(req.body.name)) {
        return res.json({
          status: false,
          message: "Invoice needs a name"
        });
      }
      // perform other checks

      [...]

First, the data sent to the server is validated. Then a connection is made to the database for the subsequent queries.

首先,验证发送到服务器的数据。 然后,连接到数据库以进行后续查询。

server.js
server.js
[...]
      // create invoice
      let db = new sqlite3.Database("./database/InvoicingApp.db");
      let sql = `INSERT INTO invoices(name,user_id,paid) VALUES(
        '${req.body.name}',
        '${req.body.user_id}',
        0
      )`;
      [...]

The INSERT query needed to create the invoice is written and then executed. Afterwards, the singular transactions are inserted into the transactions table with the invoice_id as foreign key to reference them.

写入并创建发票所需的INSERT查询。 之后,将单个交易插入到具有invoice_id作为外键的transactions表中以引用它们。

server.js
server.js
[...]
      db.serialize(function() {
        db.run(sql, function(err) {
          if (err) {
            throw err;
          }
          let invoice_id = this.lastID;
          for (let i = 0; i < req.body.txn_names.length; i++) {
            let query = `INSERT INTO transactions(name,price,invoice_id) VALUES(
                '${req.body.txn_names[i]}',
                '${req.body.txn_prices[i]}',
                '${invoice_id}'
            )`;
            db.run(query);
          }
          return res.json({
            status: true,
            message: "Invoice created"
          });
        });
      });
    [...]

Once this is executed, the invoice is successfully created.

执行此操作后,将成功创建发票。

On checking the SQLite database, the following result is obtained:

在检查SQLite数据库时,将获得以下结果:

sqlite> select * from invoices;
    1|Test Invoice New|2|0
    sqlite> select * from transactions;
    1|iPhone|600|1
    2|Macbook|1700|1

Now, when a user wants to see all the created invoices, the client will make a GET request to the /invoice/user/:id route. The user_id is passed as a route parameter. The request is handled as follows:

现在,当用户希望查看所有创建的发票时,客户端将向/invoice/user/:id路由发出GET请求。 user_id作为路由参数传递。 该请求的处理方式如下:

index.js
index.js
[...]
    app.get("/invoice/user/:user_id", multipartMiddleware, function(req, res) {
      let db = new sqlite3.Database("./database/InvoicingApp.db");
      let sql = `SELECT * FROM invoices LEFT JOIN transactions ON invoices.id=transactions.invoice_id WHERE user_id='${req.params.user_id}'`;
      db.all(sql, [], (err, rows) => {
        if (err) {
          throw err;
        }
        return res.json({
          status: true,
          transactions: rows
        });
      });
    });

    [...]

A query is run to fetch all the invoices and the transactions related to the invoice belonging to a particular user.

运行查询以获取所有发票和与属于特定用户的发票相关的交易。

To fetch a specific invoice, a GET request is made with the user_id and invoice_id to the /invoice/user/{user_id}/{invoice_id} route. The request is handled as follows:

要获取特定的发票,请向/invoice/user/{user_id}/{invoice_id}路由发出带有user_idinvoice_idGET请求。 该请求的处理方式如下:

index.js
index.js
[...] 

    app.get("/invoice/user/:user_id/:invoice_id", multipartMiddleware, function(req, res) {
      let db = new sqlite3.Database("./database/InvoicingApp.db");
      let sql = `SELECT * FROM invoices LEFT JOIN transactions ON invoices.id=transactions.invoice_id WHERE user_id='${
        req.params.user_id
      }' AND invoice_id='${req.params.invoice_id}'`;
      db.all(sql, [], (err, rows) => {
        if (err) {
          throw err;
        }
        return res.json({
          status: true,
          transactions: rows
        });
      });
    });

    // set application port
    [...]

A query is ran to fetch a single invoice and the transactions related to the invoice belonging to the user.

运行查询以获取单个发票以及与属于用户的发票相关的交易。

结论 (Conclusion)

In this tutorial, you set up your server with all the needed routes for a lightweight invoicing application.

在本教程中,您将使用轻量级发票应用程序所需的所有路由来设置服务器。

翻译自: https://www.digitalocean.com/community/tutorials/how-to-build-a-lightweight-invoicing-app-with-node-database-and-api

应用双节点,数据库是dg


http://www.niftyadmin.cn/n/3649060.html

相关文章

查找算法集:顺序查找、二分查找、插值查找、动态查找(数组实现、链表实现)

//search.cpp : Defines the entry point for the console application.//#include "stdafx.h"#include "LinkTable.h"#defineMAX_KEY 500//------------------------------数组实现部分----------------------------------/**//* 无序数组顺序查找算法…

网页开端第四次培训笔记

CSS常用属性设置 1.背景&#xff08;css背景属性用于定义HTML元素的背景效果 background-color 设置元素的背景效果background-image 设置元素的背景图像&#xff0c;默认情况下&#xff0c;背景图像进行平铺重复显示&#xff0c; …

父子节点 构建_如何使用节点构建轻量级发票应用程序:用户界面

父子节点 构建介绍 (Introduction) In the first part of this series, you set up the backend server for the invoicing application. In this tutorial you will build the part of the application that users will interact with, known as the user interface. 在本系列…

Android零碎小知识

获取当前的版本号 public double getVersionCode() {try {int versionCode getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;return versionCode;} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return 0; }将版本号设置成1.…

TabActivity实现多页显示效果

由于手机屏幕有限&#xff0c;所以我们要尽量充分利用屏幕资源。在我们的应用程序中通常有多个Activity&#xff0c;而且会经常切换显示&#xff0c;这样我们就可以用TabActivity来显示。其效果如图1所示。 图1 tabActivity显示效果 本文就来研究TabActivity。根据帮助文档的解…

网页开端第五次培训笔记

JavaScript简介 JavaScript是一种具有面向对象能力的、解释型的程序设计语言。更具体点&#xff0c;它是基于对象和时间驱动并具有相对安全性的客户端脚本语言。它的主要目的是&#xff0c;验证发往服务器端的数据、增加Web互动、加强用户体验度等。 JavaScript的组成 ECMASc…

react中使用构建缓存_如何使用React,GraphQL和Okta构建健康跟踪应用

react中使用构建缓存介绍 (Introduction) In this tutorial, you will build a health tracking app using GraphQL API with Vesper framework, TypeORM, and MySQL as a database. These are Node frameworks, and you’ll use TypeScript for the language. For the client,…

动态添加流式布局

自定义流式布局&#xff1a;之前的一篇文章写过&#xff0c;这里就不阐述了&#xff1a;http://blog.csdn.net/qq_24675479/article/details/78921070 随后封装一个方法工具类&#xff1a;GradientDrawable代替shape,StateListDrawable替换selector设置 public class DrawUtil…