0
0
Lập trình
Thaycacac
Thaycacac thaycacac

Hướng Dẫn Tạo PDF từ HTML với Express và PDF Echo

Đăng vào 3 ngày trước

• 8 phút đọc

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

  1. Tạo Dự Án Node.js
  2. Cấu Hình package.json
  3. Cấu Hình Express và Handlebars
  4. Tạo Template Hóa Đơn với Handlebars
  5. Kết Nối với PDF Echo để Tạo PDF
  6. Thực Hành và Kiểm Tra
  7. Mẹo và Tránh Các Lỗi Thường Gặp
  8. 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 Copy
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 Copy
{
  "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áp import / 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 Copy
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 Copy
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 Copy
<!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òng
  • calculateTax → để á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 Copy
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 Copy
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

Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào