PDF Generation and Download in Laravel-Vue By Snappy

In this guide, we’ll explore how to implement a robust PDF generation and download system using Laravel for the backend and Vue.js for the frontend. We’ll specifically focus on generating purchase order PDFs using the wkhtmltopdf library through Laravel Snappy.

Prerequisites

  • Laravel 8+
  • Vue.js 2/3
  • wkhtmltopdf installed on your server
  • Laravel Snappy package

Setting Up Laravel Snappy

First, install the Laravel Snappy package

composer require barryvdh/laravel-snappy

I’m assuming that you are using a Linux OS, In ubuntu I used which wkhtmltopdf to see the binary path.

Configure your config/snappy.php

return [
    'pdf' => [
        'enabled' => true,
        'binary'  => '/usr/bin/wkhtmltopdf',
        'timeout' => 120,
        'options' => [
            'enable-local-file-access' => true,
            'encoding' => 'UTF-8',
            'page-size' => 'A4'
        ],
    ]
];

Creating the PDF Template

Create a blade template for your PDF (resources/views/pdf_templates/purchase_order.blade.php)

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Purchase Order</title>
    <style>
        /* Inline styles for better PDF rendering */
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 20px;
        }
        /* ... more styles ... */
    </style>
</head>
<body>
    <!-- Your PDF content structure -->
    <div class="header">
        <h1>Purchase Order #{{ $purchaseOrder->po_number }}</h1>
    </div>
    <!-- ... more content ... -->
</body>
</html>

Backend Implementation

Create your controller methods:

class PurchaseOrderController extends Controller
{
    public function downloadInvoiceSnappyPdf(PurchaseOrder $purchaseOrder)
    {
        try {
            $pdf = SnappyPdf::loadView('pdf_templates.purchase_order', [
                'purchaseOrder' => $purchaseOrder
            ])
            ->setOption('encoding', 'UTF-8')
            ->setOption('margin-top', 10);
            
            return $pdf->download($purchaseOrder->po_number . '.pdf');
        } catch (\Exception $e) {
            return response()->json(['error' => 'Failed to generate PDF'], 500);
        }
    }
}

Frontend Implementation

Vue Service ( In short you need to make an Api Call, In my case I’m using service files for example: PurchaseOrderService.js, InvoiceService.js)

Create a service to handle API calls:

// services/PurchaseOrderService.js
export const exportPurchaseOrderInvoice = async (id) => {
    const response = await axios.get(`/api/purchase-orders/${id}/invoice/pdf`, {
        responseType: 'blob'
    });
    return response.data;
};

Vue Component

Implement the download functionality in your component: (You can point this to click event)

// components/PurchaseOrder.vue
export default {
    methods: {
        async downloadPdf() {
            try {
                const response = await exportPurchaseOrderInvoice(this.getSelectedItem.id);
                const blob = new Blob([response], { type: 'application/pdf' });
                const url = URL.createObjectURL(blob);
                const link = document.createElement('a');
                link.href = url;
                link.download = `${this.getSelectedItem.po_number}.pdf`;
                link.click();
                URL.revokeObjectURL(url);
            } catch {
                this.$notification.$emit('error', 'Failed to download PDF');
            }
        }
    }
}

Optimizing the Implementation

For better code organization and reusability, we can create a composable:

Introduction To Composition Api For Newbie

// composables/useDownloader.js
import { ref } from 'vue';

export function useDownloader() {
    const isDownloading = ref(false);

    const downloadFile = async (getData, filename) => {
        isDownloading.value = true;
        try {
            const response = await getData();
            const url = URL.createObjectURL(new Blob([response]));
            const link = document.createElement('a');
            link.href = url;
            link.download = filename;
            link.click();
            URL.revokeObjectURL(url);
        } finally {
            isDownloading.value = false;
        }
    };

    return { downloadFile, isDownloading };
}

It’s more likely creating a hook or a helper function in Laravel.

// TopButtons.vue
import { useDownloader } from '@/composables/useDownloader';
import { exportPurchaseOrderInvoice } from '@/services/PurchaseOrderService';

export default {
  name: 'TopButtons',
  props: {
    showBuilder: Boolean,
    loading: Boolean,
    instance: Object
  },
  
  setup(props) {
    const { downloadFile, isDownloading } = useDownloader();

    // Method to handle PDF download
    const downloadPdf = async () => {
      try {
        await downloadFile(
          // Pass the function that gets the data
          () => exportPurchaseOrderInvoice(props.instance.getSelectedItem.id),
          // Set the filename using PO number
          `${props.instance.getSelectedItem.po_number}.pdf`
        );
      } catch (error) {
        // Handle any errors
        console.error('PDF download failed:', error);
      }
    };

    return {
      downloadPdf,
      isDownloading
    };
  },

  // In template
  template: `
    <div>
      <v-btn
        color="#F3F4FA"
        :loading="isDownloading"
        @click="downloadPdf"
      >
        <v-icon size="22">
          {{ mdiPdfBox }}
        </v-icon>
      </v-btn>
    </div>
  `
});

Common Issues and Solutions

1. wkhtmltopdf Installation Issues

If you encounter issues with wkhtmltopdf, ensure proper installation:

    2. PDF Rendering Issues

    • Ensure all assets are accessible
    • Use absolute paths for images
    • Include all styles inline
    • Avoid external dependencies

    3. File Download Issues

    • Set proper response headers
    • Handle binary data correctly
    • Clean up object URLs to prevent memory leaks

    Best Practices

    1. Error Handling: Implement comprehensive error handling both on frontend and backend
    2. Loading States: Show loading indicators during PDF generation
    3. Clean Code: Use composables or utilities for reusable functionality
    4. Performance: Clean up resources after download
    5. Security: Validate user permissions before generating PDFs

    Additional Resources

    Happy coding! 🚀

    Leave a Comment