DEV Community

Cover image for Develop a Simple PDF Invoicing App for your Business with CASE
Bruno Pérez for Manifest

Posted on

Develop a Simple PDF Invoicing App for your Business with CASE

In our example, we want to develop a simple invoicing web app as fast as possible for our business. 💰💰💰

For that motive, we are going to use CASE, a simple tool to quickly generate dashboards and add our own custom logic.

This simple guide will follow 4 steps:

  1. Install CASE
  2. Generate entities
  3. Add properties
  4. Generate a PDF on invoice creation

1. Install CASE

Let's start generating the app with this simple command ✨✨

npx create-case-app my-invoicing-app
Enter fullscreen mode Exit fullscreen mode

CASE dashboard login page

This will launch your invoicing app on your browser. You can go to the my-invoicing-app folder and open the folder with your favorite IDE.

That's it for the install.

2. Generate entities

Our project will need 2 entities: a Customer and an Invoice. Invoices belongs to Customers, and a Customer can have many invoices.

The command to generate a new entity is straightforward:

npm run case:entity customer
Enter fullscreen mode Exit fullscreen mode

and the second one:

npm run case:entity invoice
Enter fullscreen mode Exit fullscreen mode

You should see that the customer.entity.ts and the invoice.entity.ts files have been created ! Check out the UI to see those entities in action.

3. Add properties

By default, entities only come with a name property. We are going to add more to it. Let's start with the customers:

// entites/customer.entity.ts

@Entity({
  nameSingular: "customer",
  namePlural: "customers",
  propIdentifier: "name",
  slug: "customers",
})
export class Customer extends CaseEntity {

  // Customer logo.
  @Prop({
    type: PropType.Image,
  })
  logo: string;

  // Customer name.
  @Prop({
    type: PropType.Text,
    seed: () => faker.company.name(),
  })
  name: string;

  // Customer address with a nice seeder for the dummy data.
  @Prop({
    type: PropType.Text,
    seed: () =>
      faker.location.streetAddress() +
      ", " +
      faker.location.city() +
      ", " +
      faker.location.state() +
      " " +
      faker.location.zipCode(),
  })
  address: string;
}

Enter fullscreen mode Exit fullscreen mode

The @Prop decorator is getting:

Let's go with the invoices now:

// entities/invoice.entity.ts

@Entity({
  nameSingular: "invoice",
  namePlural: "invoices",
  propIdentifier: "label",
  slug: "invoices",
})
export class Invoice extends CaseEntity {
  @Prop({
    type: PropType.Text,
  })
  label: string;

  @Prop({
    type: PropType.Date,
    label: "Issue Date",
  })
  issueDate: string;

  // Represents the relation with the Customer entity.
  @Prop({
    type: PropType.Relation,
    options: {
      entity: Customer,
    },
  })
  customer: Customer;

  // PropType.Currency accepts all currencies, default is USD.
  @Prop({
    type: PropType.Currency,
  })
  amount: number;

  @Prop({
    type: PropType.Currency,
  })
  taxes: number;

  // This prop will store the path of the generated PDF file.
  @Prop({
    type: PropType.File,
    options: {
      isHiddenInCreateEdit: true,
    },
  })
  path: string;
}
Enter fullscreen mode Exit fullscreen mode

Now that our props are filled, we can add some dummy data to see it in action:

npm run seed
Enter fullscreen mode Exit fullscreen mode

This will generate a bunch of customers and invoices with the dummy properties. As you can see, we did not specify a seed function for all of those props, by default CASE tries to generate a mock data that corresponds to your type.

Form to create an invoice

4. Generate PDF on insert

Last but not least, we need to generate a PDF on save and store its path as the invoice.path.

We are going to use the @BeforeInsert() decorator that triggers a function just before our item is inserted to the database:

// entities/invoice.entity.ts

@BeforeInsert()
generatePDF() {
  // We need a feature that generates a PDF based on our values and that stores it in the disk and returns the path.
  this.path = generateInvoiceInPDF({
      reference: this.reference,
      label: this.label,
      issueDate: this.issueDate,
      customerName: this._relations.customer.name,
      customerAddress: this._relations.customer.address,
      amount: this.amount,
      taxes: this.taxes
  })
}
Enter fullscreen mode Exit fullscreen mode

Let's create that generateInvoiceInPDF() function !

We are going to use the jspdf package to create the PDF files. Let's create a utils/pdf.ts file with this content:

// utils/pdf.ts

import { jsPDF } from "jspdf";
import * as fs from "fs";

export const saveInvoiceInPDF = (content: {
  reference: string;
  label: string;
  issueDate: string;
  customerName: string;
  customerAddress: string;
  amount: number;
  taxes: number;
}): string => {
  var doc = new jsPDF();

  // Title.
  doc.setFontSize(28);
  doc.text(`Invoice: ${content.reference}`, 20, 25);

  // Other content.
  const fontSize: number = 14;
  const x: number = 35;
  doc.setFontSize(fontSize);

  doc.text(content.label, 20, x);
  doc.text(content.customerName, 20, x + fontSize);
  doc.text(content.customerAddress, 20, x + fontSize * 2);
  doc.text(content.issueDate, 20, x + fontSize * 3);
  doc.text(`Amount: ${content.amount}`, 20, x + fontSize * 4);
  doc.text(`Taxes: ${content.taxes}`, 20, x + fontSize * 5);

  var data = doc.output();

  const storagePath = "./public/storage";
  const path = `/invoices/${content.reference}.pdf`;

  fs.writeFileSync(storagePath + path, data, "binary");

  return path;
};
Enter fullscreen mode Exit fullscreen mode

This code is producing a basic PDF will the invoice information. I am not digging to much on that part in this tutorial but if you want to create a nice PDF, checkout jspdf doc or you can even get directly invoice templates for it.

You can find all that code in the Github repo of this project, Enjoy your coding !

Wanna give some ❤️ ?

If you enjoyed this tutorial and developing with CASE, Star us on Github and like or share this post ! We are actively looking for CASE testers and users to improve our project !

Top comments (0)