Hướng Dẫn Tạo PDF từ HTML với Express và PDF Echo
Giới Thiệu
Trong các ứng dụng web, việc tạo ra tài liệu PDF như hóa đơn, báo cáo hay biên lai là một trong những nhiệm vụ phổ biến nhất. Mặc dù việc này có thể khá phức tạp nếu thực hiện từ đầu, nhưng với HTML + CSS và một engine template như Handlebars, nhiệm vụ này trở nên dễ dàng hơn nhiều. Bằng cách sử dụng PDF Echo, bạn có thể dễ dàng chuyển đổi những tài liệu HTML thành PDF sẵn sàng cho người dùng của mình.
Trong bài viết này, tôi sẽ hướng dẫn bạn cách xây dựng một dự án nhỏ sử dụng Node.js + Express + Handlebars để tạo ra một hóa đơn và chuyển đổi nó thành PDF với PDF Echo.
Mục Lục
- Tạo Dự Án Node.js
- Cấu Hình package.json
- Cấu Hình Express và Handlebars
- Tạo Template Hóa Đơn với Handlebars
- Kết Nối với PDF Echo để Tạo PDF
- Thực Hành và Kiểm Tra
- Mẹo và Tránh Các Lỗi Thường Gặp
- Kết Luận
1. Tạo Dự Án Node.js
Đầu tiên, bạn cần khởi tạo một dự án mới:
bash
mkdir invoice-pdf
cd invoice-pdf
npm init -y
npm install -E express express-handlebars
2. Cấu Hình package.json
Trước khi viết mã, hãy cập nhật file package.json
để đảm bảo Node.js chạy ở chế độ ESM (ECMAScript Modules) và có sẵn các script cho phát triển và sản xuất.
Trong file package.json
, thêm:
json
{
"type": "module",
"scripts": {
"start": "node --env-file=.env src/index.js",
"dev": "node --watch --env-file=.env src/index.js"
}
}
"type"
:"module"
cho phép chúng ta sử dụng cú phápimport
/export
hiện đại.- Script
"start"
chạy ứng dụng bình thường. - Script
"dev"
chạy ứng dụng trong chế độ theo dõi, tự động tải lại khi có thay đổi.
3. Cấu Hình Express và Handlebars
Tạo file src/app.js
với nội dung sau:
javascript
import express from 'express'
import { engine } from 'express-handlebars'
import path from 'node:path'
const app = express()
app.engine('hbs', engine({ defaultLayout: false }))
app.set('view engine', 'hbs')
app.set('views', path.join('src', 'views'))
export { app }
Trong file src/index.js
:
javascript
import { app } from './app.js'
const PORT = process.env.PORT ?? 4000
app.listen(PORT, '0.0.0.0', (error) => {
if (error !== undefined) {
console.log(error)
return
}
console.log(`SV ON PORT: ${PORT}`)
})
4. Tạo Template Hóa Đơn với Handlebars
Bây giờ hãy thiết kế template hóa đơn. Tạo một file tại src/views/invoice.hbs
và dán mã sau:
html
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hóa Đơn</title>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
.wave-top {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 12rem;
background: linear-gradient(to right, #F69474, #ED2762);
clip-path: ellipse(65% 100% at 0% 0%);
}
main {
position: relative;
padding: 2rem 4rem;
}
header {
display: flex;
justify-content: space-between;
margin-bottom: 4rem;
}
.hero {
h1 {
font-size: 3rem;
text-transform: uppercase;
margin-bottom: 0.5rem;
color: #FFFFFF;
}
h2 {
font-weight: 400;
font-size: 1rem;
span {
font-weight: 600;
text-transform: uppercase;
}
}
}
.company {
span {
font-size: 2rem;
margin-bottom: 1rem;
display: block;
}
ul {
list-style: none;
display: flex;
flex-direction: column;
gap: 0.25rem;
li {
font-size: 0.75rem;
}
}
}
table {
border-spacing: 0;
width: 100%;
thead {
background: linear-gradient(to right, #EE2762, #F89675);
tr {
th {
padding: 1rem 2rem;
text-transform: uppercase;
color: #FFFFFF;
text-align: left;
}
}
}
tbody {
tr {
td {
padding: 1rem 2rem;
font-weight: 600;
}
}
}
}
</style>
</head>
<body>
<div class="wave-top"></div>
<main>
<header>
<div class="hero">
<h1>Hóa Đơn</h1>
<h2>Hóa đơn cho: <span>{{customer_name}}</span></h2>
</div>
<div class="company">
<span>LOGO</span>
<ul>
<li>Địa chỉ: Số 223 NY USA</li>
<li>Điện thoại: <a href="tel:+1234567890">+123 456 7890</a></li>
<li>Email: <a href="mailto:your@email.com">your@email.com</a></li>
<li>Website: <a href="https://example.com">example.com</a></li>
</ul>
</div>
</header>
<table>
<thead>
<tr>
<th>Mô Tả Hàng Hóa</th>
<th>Giá</th>
<th>Số Lượng</th>
<th>Tổng</th>
</tr>
</thead>
<tbody>
{{#each items}}
<tr>
<td>{{name}}</td>
<td>${{unit_price}}</td>
<td>{{quantity}}</td>
<td>${{multiply unit_price quantity}}</td>
</tr>
{{/each}}
</tbody>
</table>
</main>
</body>
</html>
Template này sử dụng các helper tùy chỉnh:
multiply
→ để tính tổng từng dòngcalculateTax
→ để áp dụng tỷ lệ thuếsum
→ để cộng tổng và thuế
Để Render template này, chúng ta cần đăng ký các helper này trong ứng dụng Express.
Trong src/app.js
:
javascript
app.engine('hbs', engine({
defaultLayout: false,
helpers: {
sum: (a, b) => a + b,
multiply: (a, b) => a * b,
calculateTax: (tax, totalAmount) => Math.round(totalAmount * (tax / 100))
}
}))
5. Kết Nối với PDF Echo để Tạo PDF
Giờ chúng ta đã xác nhận rằng template render đúng, hãy tạo một route mới gửi HTML đã render đến PDF Echo và trả về PDF cho client:
javascript
app.get('/invoice/pdf', (req, res) => {
const MOCKUP_DATA = {
customer_name: 'Joe Doe',
items: [
{ name: 'Thiết Kế Đồ Họa', unit_price: 125, quantity: 2 },
{ name: 'Thiết Kế Web', unit_price: 150, quantity: 1 }
],
tax: 7.5
}
const totalAmount = MOCKUP_DATA.items.reduce((acc, value) => acc + value.unit_price * value.quantity, 0)
res.render('invoice', { ...MOCKUP_DATA, total_amount: totalAmount }, async (error, html) => {
if (error) return res.status(400).send('Lỗi render HTML');
try {
const request = await fetch('https://api.pdfecho.com', { method: 'POST', body: JSON.stringify({ source: html }), headers: { Authorization: `Basic ${Buffer.from('sk_test_****' + ':').toString('base64')}` } });
const data = await request.arrayBuffer();
res.contentType('application/pdf').send(Buffer.from(data));
} catch (error) {
res.status(500).json({ error: { code: 'server_internal_error', type: 'api_error' } });
}
});
});
6. Thực Hành và Kiểm Tra
Mở trình duyệt và truy cập vào http://localhost:4000/invoice/pdf
để tải xuống PDF hóa đơn đã render.
7. Mẹo và Tránh Các Lỗi Thường Gặp
- Kiểm tra template: Đảm bảo rằng template của bạn render mà không có lỗi nào để tránh gặp rắc rối khi gửi đến PDF Echo.
- Quản lý API Key: Nhớ bảo mật API Key của bạn và không nên đưa vào mã nguồn công khai.
8. Kết Luận
Trong bài viết này, chúng ta đã thiết lập một server Express và tích hợp với PDF Echo để tạo ra tài liệu PDF trong thời gian thực. Từ đây, bạn có thể mở rộng chức năng để tạo ra các loại tài liệu khác nhau như hóa đơn, báo cáo hay hợp đồng một cách linh hoạt. Hãy thử nghiệm và phát triển dự án của bạn nhé!
Nguồn tham khảo: GitHub Repository