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 Vue và NodeJS . Ứ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 userexpress
sức mạnh ứng dụng websqlite3
để tạo và duy trì databasepath
để giải quyết các đường dẫn file trong ứng dụngbluebird
để sử dụng Lời hứa khi viết di chuyểnumzug
như một người chạy tác vụ để chạy di chuyển databasebody-parser
vàconnect-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:
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:
[...] 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:
"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 .
[...] 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:
[...] 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()
:
[...] 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
:
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:
[...] 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 :
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:
[...] 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ý.
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:
[...] 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 .
[...] 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:
[...] 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.
[...] // 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.
[...] 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:
[...] 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_id
và invoice_id
tới tuyến /invoice/user/{user_id}/{invoice_id}
. Yêu cầu được xử lý như sau:
[...] 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 .
Các tin liên quan
Cách cài đặt WordPress với dịch vụ database trên Ubuntu 18.042019-08-20
Dịch vụ Database online
2019-02-14
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