Thứ năm, 12/12/2019 | 00:00 GMT+7

Cách tạo một ứng dụng lập hóa đơn đơn giản với node: database và API

Để được thanh toán cho hàng hóa và dịch vụ được cung cấp, doanh nghiệp cần gửi hóa đơn cho khách hàng thông báo về những dịch vụ mà họ sẽ bị tính phí. Hồi đó, người ta có hóa đơn giấy mà họ đưa cho khách hàng khi họ liên hệ mua dịch vụ của họ. Với sự ra đời và tiến bộ của công nghệ, giờ đây mọi người đã có thể gửi hóa đơn điện tử cho khách hàng của bạn .

Trong hướng dẫn này, bạn sẽ xây dựng một ứng dụng lập hóa đơn bằng VueNodeJS . Ứng dụng này sẽ thực hiện các chức năng như tạo, gửi, chỉnh sửa và xóa hóa đơn.

Yêu cầu

Để theo dõi đầy đủ bài viết, bạn cần các thành phần sau :

  • Nút được cài đặt trên máy của bạn
  • Trình quản lý gói nút (NPM) được cài đặt trên máy của bạn

Chạy phần sau để xác minh Node đã được cài đặt trên máy của bạn:

  • node --version

Chạy các bước sau để xác minh NPM đã được cài đặt trên máy của bạn:

  • npm --version

Nếu bạn nhận được số version là kết quả thì bạn đã sẵn sàng tiếp tục.

Bước 1 - Cài đặt server

Bây giờ ta đã đặt tất cả các yêu cầu, điều tiếp theo cần làm là tạo server backend cho ứng dụng. Server backend sẽ duy trì kết nối database .

Bắt đầu bằng cách tạo một folder để chứa dự án mới:

  • mkdir invoicing-app

Khởi tạo nó như một dự án Node:

  • cd invoicing-app && npm init

Để server hoạt động thích hợp, có một số gói Node cần được cài đặt. Bạn có thể cài đặt chúng bằng cách chạy lệnh sau:

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

Lệnh đó cài đặt các gói sau:

  • bcrypt để băm password user
  • express sức mạnh ứng dụng web
  • sqlite3 để tạo và duy trì database
  • path để giải quyết các đường dẫn file trong ứng dụng
  • bluebird để sử dụng Lời hứa khi viết di chuyển
  • umzug như một người chạy tác vụ để chạy di chuyển database
  • body-parserconnect-multiparty để xử lý các yêu cầu biểu mẫu gửi đến

Tạo file server.js sẽ chứa logic ứng dụng:

  • touch server.js

Trong file server.js , hãy nhập các module cần thiết và tạo một ứng dụng express:

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());      [...] 

Tạo một / route để kiểm tra xem server có hoạt động không:

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() cho server biết cổng để lắng nghe các tuyến đường đến. Để khởi động server , hãy chạy phần sau trong folder dự án của bạn:

  • node server

Ứng dụng của bạn bây giờ sẽ bắt đầu lắng nghe các yêu cầu đến.

Bước 2 - Tạo và kết nối với database bằng SQLite

Đối với ứng dụng lập hóa đơn, cần có database để lưu trữ các hóa đơn hiện có. SQLite sẽ là ứng dụng database được lựa chọn cho ứng dụng này.

Bắt đầu bằng cách tạo một folder database :

  • mkdir database

Tiếp theo, di chuyển vào folder mới và tạo file cho database của bạn:

  • cd database && touch InvoicingApp.db

Trong folder database , chạy client sqlite3 :

  • invoicing-app/database/ sqlite3

Mở database InvoicingApp.db :

  • .open InvoicingApp.db

Bây giờ database đã được chọn, việc tiếp theo là tạo các bảng cần thiết.

Ứng dụng này sẽ sử dụng ba bảng:

  • User - Điều này sẽ chứa dữ liệu user (id, tên, email, tên_công ty, password )
  • Hóa đơn - Lưu trữ dữ liệu cho hóa đơn (id, tên, thanh toán, user_id)
  • Giao dịch - Các giao dịch đơn lẻ kết hợp với nhau để tạo hóa đơn (tên, giá, hóa đơn_id)

Vì các bảng cần thiết đã được xác định, bước tiếp theo là chạy các truy vấn để tạo các bảng.

Di chuyển được sử dụng để theo dõi các thay đổi trong database khi ứng dụng phát triển. Để thực hiện việc này, hãy tạo một folder migrations trong folder database .

  • mkdir migrations

Điều này sẽ chứa tất cả các file di chuyển.

Bây giờ, tạo file 1.0.js trong folder migrations . Quy ước đặt tên này là để theo dõi những thay đổi mới nhất.

  • cd migrations && touch 1.0.js

Trong file 1.0.js , trước tiên bạn nhập các module nút:

database / di chuyển 1.0.js
    "use strict";     const Promise = require("bluebird");     const sqlite3 = require("sqlite3");     const path = require('path');      [...] 

Sau đó, ý tưởng bây giờ là xuất một hàm up sẽ được thực thi khi file di chuyển được chạy và một hàm down để đảo ngược các thay đổi đối với database .

database / di chuyển / 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`);            [...] 

Trong chức năng up , kết nối được thực hiện đầu tiên với database . Sau đó, các foreign keys được kích hoạt trên database sqlite . Trong SQLite, các foreign keys bị tắt theo mặc định để cho phép tương thích ngược, vì vậy các foreign keys phải được bật trên mọi kết nối.

Tiếp theo, chỉ định các truy vấn để tạo bảng:

database / di chuyển / 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();         });       },       [...] 

Hàm serialize() được sử dụng để chỉ định rằng các truy vấn sẽ được chạy tuần tự và không đồng thời.

Sau đó, các truy vấn để đảo ngược các thay đổi cũng được chỉ định trong hàm down() :

database / di chuyển / 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();         });       }     }; 

Khi các file di chuyển đã được tạo, bước tiếp theo là chạy chúng để áp dụng các thay đổi trong database . Để thực hiện việc này, hãy tạo một folder scripts từ folder root của ứng dụng của bạn:

  • mkdir scripts

Sau đó, tạo một file có tên là migrate.js :

  • cd scripts && touch migrate.js

Thêm phần sau vào file migrate.js :

scripts / 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"     });      [...] 

Đầu tiên, các module nút cần thiết được nhập. Sau đó, một đối tượng umzug mới được tạo với các cấu hình. Đường dẫn và mẫu của các tập lệnh di chuyển cũng được chỉ định. Để tìm hiểu thêm về các cấu hình, hãy umzug trang umzug GitHub .

Để cung cấp một số phản hồi dài dòng, hãy tạo một hàm ghi lại các sự kiện như được hiển thị bên dưới và cuối cùng thực thi hàm up để chạy các truy vấn database được chỉ định trong folder di chuyển:

scripts / 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")); 

Bây giờ, để thực thi tập lệnh, hãy đi tới terminal và trong folder root của ứng dụng, hãy chạy:

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

Bạn sẽ thấy kết quả giống như sau :

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

Bước 3 - Tạo các tuyến ứng dụng

Bây giờ database đã được cài đặt đầy đủ, việc tiếp theo là quay lại file server.js và tạo các tuyến ứng dụng. Đối với ứng dụng này, các tuyến đường sau sẽ được cung cấp:

URL PHƯƠNG PHÁP CHỨC NĂNG
/register POST Để đăng ký một user mới
/login POST Để đăng nhập một user hiện có
/invoice POST Để tạo một hóa đơn mới
/invoice/user/{user_id} GET Để tìm nạp tất cả các hóa đơn cho một user
/invoice/user/{user_id}/{invoice_id} GET Để tìm nạp một hóa đơn nhất định
/invoice/send POST Gửi hóa đơn cho khách hàng

Để đăng ký một user mới, một yêu cầu đăng sẽ được thực hiện đến tuyến /register trên server . Tuyến đường này sẽ giống như sau:

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          [...] 

Kiểm tra được thực hiện để xem có bất kỳ trường nào trống không và dữ liệu được gửi có trùng với tất cả các thông số kỹ thuật hay không. Nếu xảy ra lỗi, thông báo lỗi sẽ được gửi đến user dưới dạng phản hồi. Nếu không, password sẽ được băm và dữ liệu sau đó sẽ được lưu trữ trong database và phản hồi được gửi đến user thông báo rằng họ đã được đăng ký.

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();       });     }); 

Nếu user hiện tại cố gắng đăng nhập vào hệ thống bằng đường dẫn /login , họ cần cung cấp địa chỉ email và password của bạn . Khi họ làm điều đó, tuyến xử lý yêu cầu như sau:

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"           });         }        [...] 

Một truy vấn được thực hiện đối với database để tìm nạp bản ghi của user với một email cụ thể. Nếu kết quả trả về một mảng trống, thì điều đó nghĩa là user không tồn tại và phản hồi được gửi thông báo cho user về lỗi.

Nếu truy vấn database trả về dữ liệu user , kiểm tra thêm sẽ được thực hiện để xem liệu password đã nhập có trùng với password đó trong database hay không. Nếu đúng, thì phản hồi sẽ được gửi cùng với dữ liệu user .

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"         });       });     });      [...] 

Khi tuyến đường được kiểm tra, bạn sẽ nhận được kết quả thành công hoặc thất bại.

Lộ trình /invoice xử lý việc tạo hóa đơn. Dữ liệu được chuyển đến tuyến sẽ bao gồm ID user , tên của hóa đơn và trạng thái hóa đơn. Nó cũng sẽ bao gồm các giao dịch đơn lẻ để tạo nên hóa đơn.

Server xử lý yêu cầu như sau:

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        [...] 

Đầu tiên, dữ liệu được gửi đến server được xác thực. Sau đó, một kết nối được thực hiện với database cho các truy vấn tiếp theo.

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       )`;       [...] 

Truy vấn INSERT cần thiết để tạo hóa đơn được viết và sau đó được thực thi. Sau đó, các giao dịch đơn lẻ được chèn vào bảng transactions với hóa invoice_id làm foreign keys để tham chiếu chúng.

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"           });         });       });     [...] 

Khi điều này được thực hiện, hóa đơn được tạo thành công.

Khi kiểm tra database SQLite , kết quả sau thu được:

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

Bây giờ, khi user muốn xem tất cả các hóa đơn đã tạo, khách hàng sẽ thực hiện yêu cầu GET đối với tuyến đường /invoice/user/:id . user_id được truyền dưới dạng tham số định tuyến. Yêu cầu được xử lý như sau:

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         });       });     });      [...] 

Một truy vấn được chạy để tìm nạp tất cả các hóa đơn và các giao dịch liên quan đến hóa đơn của một user cụ thể.

Để tìm nạp một hóa đơn cụ thể, một yêu cầu GET được thực hiện với user_idinvoice_id tới tuyến /invoice/user/{user_id}/{invoice_id} . Yêu cầu được xử lý như sau:

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     [...] 

Một truy vấn được chạy để tìm nạp một hóa đơn và các giao dịch liên quan đến hóa đơn đó thuộc về user .

Kết luận

Trong hướng dẫn này, bạn cài đặt server với tất cả các tuyến cần thiết cho một ứng dụng lập hóa đơn đơn giản .


Tags:

Các tin trước

Cách cài đặt WordPress với dịch vụ database trên Ubuntu 18.04 2019-08-20
Tìm hiểu database phân đoạn - Database Sharding 2019-02-07
Cách thiết lập database từ xa để tối ưu hóa hiệu suất trang web với MySQL trên Ubuntu 18.04 2018-11-28
Cách quản lý database SQL 2018-09-26
Cách cải thiện tìm kiếm database với tìm kiếm toàn văn bản (Full Text Search) trong MySQL 5.6 trên Ubuntu 16.04 2017-10-30
Cách thiết lập database đồ thị Titan với Cassandra và ElasticSearch trên Ubuntu 16.04 2017-06-27
Cách thiết lập database từ xa để tối ưu hóa hiệu suất trang web với MySQL trên Ubuntu 16.04 2017-06-05
Cách gỡ lỗi WordPress "Lỗi thiết lập kết nối database" 2017-04-21
Cách bảo mật database OrientDB của bạn trên Ubuntu 16.04 2017-03-24
Cách backup, khôi phục và di chuyển database MongoDB trên Ubuntu 14.04 2016-04-15