How to build a PDF invoice generator in minutes
Published: 2024-01-04 19:14:56

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](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog), 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](https://appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog) 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](https://github.com/Tundesamson26/appwrite-generate-invoice?utm_source=hackmamba&utm_medium=hackmamba-blog).

## Prerequisites

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

- Basic knowledge of Javascript and [Svelte](https://svelte.dev/)
- An [Appwrite Cloud account](https://cloud.appwrite.io/?utm_source=hackmamba&utm_medium=hackmamba-blog)
- A [GitHub account](https://github.com/login)

## 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](https://github.com/Tundesamson26/svelte-invoice-generator?utm_source=hackmamba&utm_medium=hackmamba-blog) using the command below:

```js
git clone https://github.com/Tundesamson26/svelte-invoice-generator.git
```
Then, run the command below in the project directory:

```js
$ cd svelte-invoice-generator
$ npm install
```
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.

```js
npm install appwrite
```
Then, run the development server using the command below:

```js
npm run dev
```
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](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701788306749_image.png)

## 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](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701790502071_image.png)


Click on the **Templates** tab on the Functions page:

![Locating the Templates tab](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701790888600_image.png)


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

![Generate PDF functions template](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701792289546_image.png)


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

![Appwrite functions config](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701792527887_image.png)


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](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701794734196_image.png)


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](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701796354910_image.png)

> 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](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701796550421_image.png)


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

![Successful deployment](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701797259959_image.png)


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

![Appwrite function in our GitHub repository](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701797520321_image.png)


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](https://paper-attachments.dropboxusercontent.com/s_F8FA8DEFE444C5E0C36F8ACE4B3E5D40CAC4588EAE20CEA57BDED67C49DE4E4D_1701798151328_image.png)


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:

```js
<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>
```

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.

```html
<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>
```
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:

```js
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');
  }
};
```

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:

{% embed https://gist.github.com/Tundesamson26/898ef0f0117af6e026622aad273d033b %}

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:

{% embed
https://www.loom.com/share/18dc3f67bf4747fa81a637ed88891ca0?sid=043c8aa7-fa75-41b6-9271-98dacfe055b1 %}

## 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:

- [Appwrite Cloud Function Guide](https://appwrite.io/docs/references/cloud/client-web/functions?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Appwrite Cloud Function Development](https://appwrite.io/docs/products/functions/development?utm_source=hackmamba&utm_medium=hackmamba-blog)
- [Svelte Documentation](https://svelte.dev/)