DEV Community

Cover image for How to build a PDF invoice generator in minutes
Odewole Babatunde Samson for Hackmamba

Posted on • Updated on

How to build a PDF invoice generator in minutes

Building a PDF invoice generator doesn't have to be a complex and time-consuming task. Creating a serverless function for generating PDF invoices is a great use case for serverless computing. Read on to see how you can leverage the power of serverless computing to employ a minimalist approach using readily available tools and technologies.

At the forefront of this serverless revolution is Appwrite, an open-source platform that leverages the power of serverless functions to create scalable, efficient, and cost-effective solutions.

Their Appwrite Functions feature enables developers to create and automatically run custom backend code in response to events triggered by Appwrite or according to a predefined schedule. With Appwrite Functions, we can access several benefits, including scalability and enhanced security.

In this tutorial, we'll showcase the Appwrite Generate PDF Function template, show you how to set it up, and guide you through the swift and straightforward process of building a PDF invoice generator in just a few minutes.

By the end of this tutorial, we'll have a functional PDF invoice generator that can be easily integrated into your applications or workflows, making the traditionally complex task of invoice generation a quick and efficient process.

GitHub

Check out the complete source code here.

Prerequisites

To follow along with this tutorial comfortably, you’ll need a few things:

Getting started

In this tutorial, we'll set up a frontend demo app. The demo app will be a Svelte invoice app that'll send data from the invoice input form to the Appwrite PDF Functions — cool, right?

To set up our application, clone this repository using the command below:

git clone https://github.com/Tundesamson26/svelte-invoice-generator.git
Enter fullscreen mode Exit fullscreen mode

Then, run the command below in the project directory:

$ cd svelte-invoice-generator
$ npm install
Enter fullscreen mode Exit fullscreen mode

This repository contains all the initial setups we'll need for our frontend app, helping us focus on our app's main functionality.

To use Appwrite in our Svelte application, install the Appwrite client-side SDK for web applications.

npm install appwrite
Enter fullscreen mode Exit fullscreen mode

Then, run the development server using the command below:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Once the development server is running, we can view it on our browser at http://localhost:5173.

Creating Appwrite Functions

To integrate Appwrite Functions, we must create a project on Appwrite’s console. To do this, log into the Appwrite console, click the Create project button, name the project appwrite-invoice-pdf, and click Create.

Create an Appwrite project

Creating an Appwrite Function using the Generate PDF template

Here, we'll easily create an Appwrite Function to automate our backend tasks and extend Appwrite with custom code.

Let's navigate to the side menu on our project dashboard and click on Functions to access the template:

Locating the Appwrite Functions tab

Click on the Templates tab on the Functions page:

Locating the Templates tab

Scrolling down, you’ll see the Generate PDF template; click on the Create function button as shown below:

Generate PDF functions template

We'll select Node.js-18.0 as the runtime for our PDF Invoice generator and click Next:

Appwrite functions config

For the Appwrite’s Generate PDF Functions, no environment variables are required, so we’ll proceed by clicking on Next.

Getting to the next step of connecting our Appwrite Functions to a new repository or an existing one within a selected Git organization. Select Create a new repository and click Next:

Connecting Appwrite Functions with a GitHub repo

Next, we’ll have the GitHub organization and repository name created for us automatically, as shown below. Let’s leave our repository private and click Next. Afterward, the repository will be successfully created.

Connecting the repo

Note: You can change the repository name to anything of your choosing.

Next, we'll name our production branch or leave it as main, then click the Create button

Appwrite Functions branch

After completing the configurations, an active Appwrite function should be deployed, as shown below.

Successful deployment

Completing the above configuration will create a new Appwrite function folder in our repository.

Appwrite function in our GitHub repository

Glancing through the deployed source code, you’ll notice three files: main.js, faker.js, and pdf.js. For brevity, we’ll highlight two essential files here, namely:

  • main.js file: This file in the functions/src folder houses a function to interact with the Svelte app invoice data
  • pdf.js file: This file is also inside the functions/src folder that’ll be used to construct our PDF file

Next, navigate to the Domains tab from the Functions page and click on the verified generated URL.

Appwrite functions domain

Clicking on the verified generated URL will take us to the fake data from the Appwrite Generate PDF functions template.

Adding functionality to the invoice frontend

We will start our logic by adding a function that sends user-inputted data to our Appwrite Functions and generates a PDF document based on the input from our Svelte client side.

Navigate to the App.svelte file and add the code as shown below:

<script>
import { Client, Functions } from "appwrite";

const client = new Client()
    .setEndpoint("https://cloud.appwrite.io/v1")
    .setProject("[PROJECT_ID]");
  const functions = new Functions(client);

// Function to send data to the serverless function
const generatePdf = async () => {
    const data = {
      name: $name,
      address: $address,
      email: $email,
      phone: $phone,
      bankName: $bankName,
      bankAccount: $bankAccount,
      website: $website,
      clientName: $clientName,
      clientAddress: $clientAddress,
      invoiceNumber: $invoiceNumber,
      invoiceDate: $invoiceDate,
      dueDate: $dueDate,
      notes: $notes,
      list: $list,
    };
    try {
      let response = await functions.createExecution(
        "[FUNCTION_ID]",
        JSON.stringify({
          data
        }),
        false,
        "/https://655f28d1449b15f23a3a.appwrite.global/",
        "POST",
        {
          "Content-Type": "application/json",
        }
      );
      let pdfString = response.responseBody;
      let blob = b64toBlob(pdfString, "application/pdf");
      const blobUrl = URL.createObjectURL(blob);
      console.log(blobUrl);
      window.open(blobUrl);
    } catch (error) {
      console.error("Error:", error.message);
    }
  };

  // Function to convert pdfstring to blob
  const b64toBlob = (b64Data, contentType = "", sliceSize = 512) => {
    const byteCharacters = atob(b64Data);
    const byteArrays = [];
    for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
      const slice = byteCharacters.slice(offset, offset + sliceSize);
      const byteNumbers = new Array(slice.length);
      for (let i = 0; i < slice.length; i++) {
        byteNumbers[i] = slice.charCodeAt(i);
      }
      const byteArray = new Uint8Array(byteNumbers);
      byteArrays.push(byteArray);
    }
    const blob = new Blob(byteArrays, { type: contentType });
    return blob;
  };
</script>
Enter fullscreen mode Exit fullscreen mode

In the code above, we:

  1. Import the Appwrite library and initialize an Appwrite client with the endpoint and project ID. We get the project ID from our Appwrite dashboard by clicking on the Overview tab.
  2. Create instances of the Account and Functions classes using the initialized client. Appwrite's Account class allows us to set up an anonymous user, providing a convenient means to test the functionality of our Appwrite function without the need to implement a custom authentication system.
  3. Define a generatePdf function that gathers invoice data from the Svelte store, formats it into a payload, and it to the Appwrite functions.
  4. Call the functions.createExecution method to invoke the Appwrite serverless function. It takes several parameters:
    • [FUNCTION_ID]: The ID of the serverless function to be executed.
    • JSON.stringify({ data }): The data object is converted to a JSON string and sent as the payload to the serverless function.
    • false: This parameter indicates whether the function should execute asynchronously (in the background). In this case, it is set to false, suggesting the functions will execute synchronously.
    • /https://655f28d1449b15f23a3a.appwrite.global/: The endpoint where the serverless function is hosted.
    • POST: The HTTP method used for the request.
    • { "Content-Type": "application/json" }: The headers for the HTTP request specifying that the payload is in JSON format.
  5. Define a b64toBlob function that takes base64-encoded data b64Data, an optional content type contentType, and a slice size as parameters and returns a Blob.

Next, add a Generate PDF button to the HTML template to trigger the generatePdf function.

<button class="mt-5 bg-green-500 py-2 px-8 text-white font-bold shadow border-2 border-green-500 hover:bg-transparent hover:text-green-500 transition-all duration-300" 
on:click={generatePdf}>
    Generate PDF
</button>
Enter fullscreen mode Exit fullscreen mode

We'll now focus on altering the main.js and pdf.js files to achieve the app's functionality.

Modifying the generated Functions code on GitHub

Open the source code and navigate to the src/main.js file. Then, edit the file as shown below:

import { createPdf } from "./pdf.js";
export default async ({ res, req, log, error }) => {
  if (req.method === "POST") {
    try {
      const payload = (req.body.data);
      const pdfBuffer = await createPdf(payload);
      const pdfBase64 = pdfBuffer.toString('base64');
      return res.send(pdfBase64, 200, { "Content-Type": "application/pdf" });
    } catch (err) {
      log('Error processing the request: ' + err.message);
      return res.send('Internal Server Error ' + err.message);
    }
  } else {
    log(error)
    return res.send('Bad Request');
  }
};
Enter fullscreen mode Exit fullscreen mode

The code above did the following:

  • Import the createPdf function from src/pdf.js file.
  • It expects POST requests, extracts payload data, generates a PDF using the createPdf function, and responds with the generated PDF in base64 format. If there's any error, it logs it and sends an error response. If the request method is not POST, it logs an error and sends a Bad Request response.

Next, we need to structure our data in an arranged PDF format. Navigate to the src/pdj.js file and edit the code as shown below:

The code above is organized to create a visually structured invoice with information such as invoice date, due date, invoice number, billing details, line items, and additional notes. The pdf-lib library handles PDF generation and renders text on pages.

Demo

The app should function like this demo after applying the necessary configurations above:

Conclusion

This post discussed the integration of an Appwrite Function template, the Generate PDF template, in a Svelte application. We addressed the importance of adopting the Appwrite Functions template, which fast-tracks productivity by making the traditionally complex task of invoice generation a quick and efficient process.

The tutorial not only emphasized the straightforward setup of the template but also presented a practical demonstration of its integration in a real-world scenario. This hands-on example is a valuable reference for developers seeking to implement similar functionalities in their applications.

The Generate PDF function template from Appwrite is flexible and can be easily customized to suit your needs. Developers can customize the template to accept payload from our frontend app. For instance, in the case of the invoice generator we built in this article, you can get multiple data points from our invoice form and quickly generate a PDF in minutes for business and individual use. The ability to tailor the function to particular needs can significantly improve the process of adding features to your applications, ultimately saving time and effort.

Resources

These resources may also be helpful:

Top comments (0)