API for rendering PDF documents through the use of modern web templates.
This project was made with the intentions to make rendering PDFs with premade templates easier. This project uses cutting edge technology and can render anything from simple text to SVG.
Template creations are made with Vue.js empowering the template creator to build beautiful templates while parsing their own data. This removes the need for any one team to maintain this project and makes it simple for anyone to create new templates.
Each template is a directory uploaded to the pages directory of this project. Each template directory will consist of index.vue and mockData.js
# Template boilerplate example
/pages/template-name/index.vue
/pages/template-name/mockData.js
Adding Templates
Templates will be selected by passing a template name value to the API end point in the key of template. It is imperative that you add your template name to the templates object in,
wuno-pdf-generator/server/controllers/pdf.js
// e.g.
const templates = {
exampleTemplate: "example-template"
};
const template = templates[req.body.template];
All available template links are displayed on the home page of this project so people can easily browse available templates. The link will open the template in the browser loading the mock data. This rendered page will be an exact representation of the generated PDF. This allows us to keep the project organized and well documented without.
Adding Fonts
To add a new font to the API simply add your font file to the /assets/fonts directory. Once the font exists in the project you can use @font-face in the component to bring your font into scope.
/* Example */
@font-face {
font-family: "antonio-regular-webfont";
font-weight: normal;
font-style: normal;
src: url("~assets/fonts/antonio-regular-webfont.woff"); /* IE9 Compat Mode */
src: url("~assets/fonts/antonio-regular-webfont.woff2") format("woff");
}
Puppeteer has known issues running on Debian Linux servers. It fails due to dependency issues. The missing dependencies can be installed and the puppeteer instance must be ran passing options to the launch function. More on this issue and how to solve it can be found here.
const browser = await puppeteer.launch({
args: ["--no-sandbox", "--disable-setuid-sandbox"],
ignoreHTTPSErrors: true,
dumpio: false
});
This project was built with Nuxt.js, Express, Bootstrap and Puppeteer.
Nuxt.js
Nuxt is a framework for server side rendering Vue.js application. SSR allows us to build a PDF generating application that performs very well. Nuxt gives us better control over the routing in this application out of the box as well as easy to create end points for our API. One of the important features in this application is the ability to determine which parts of our application are rendered server side and which parts are rendered client side. The outcome is a scalable application with if any, very few limits.
Express
Express is the most popular Node.js framework used for building a robust API. It comes packaged with countless features allowing the very fast creation of a new API. Performance is high and the dependency is very light.
Bootstrap
Bootstrap is a front end framework used for building mobile first applications. It is well documented and highly supported by a strong community. Through the use of Bootstrap it is possible to build responsive web pages very quickly with HTML and CSS.
Puppeteer
Puppeteer is a very cutting edge technology that is used for controlling headless Chrome or Chromium. It can be used for rendering webpages without a browser. In our case we are using it to render our Vue templates server side and then creating a PDF from them.
API end points are versioned to keep track of the evolution of this application. This versioning is mentioned in every end point.
# e.g.
api/v1/pdf
This is not to be confused with the versioning of deployment branches. Deployments are versioned in their respective branches.
# e.g.
https://github.com/wuno/wuno-pdf-generator/tree/versions/v1
Yarn
# install dependencies
$ yarn install
# serve with hot reload at localhost:8080
$ yarn run dev
# build for production or launch server
$ yarn run build
$ yarn start
# generate static project
$ yarn run generate
NPM
# install dependencies
$ npm install
# serve with hot reload at localhost:8080
$ npm run dev
# build for production or launch server
$ npm run build
$ npm start
# generate static project
$ npm run build
This application uses Jest and Puppeteer for unit testing.
Configuration
jest-puppeteer
is used to start an instance of the server in which the unit tests will be performed.
It will basically call npm start
before the unit tests start.
To configure how the server is launched, you can update jest-puppeteer.config.js
.
Note that the timeout has been changed from the default 5s, to 30s.
Launch the tests
Yarn
# launch the test suite
$ yarn test
NPM
# launch the test suite
$ npm test
This application is completely stateless and has been deployed to a Cloud Foundry environment in the past. Due to the known issues mentioned above it was necessary to wrap this application in a Docker image. The Docker image provides the ability to install any missing dependencies while running in Cloud Foundry.
Once deployed, the service can take POST and GET requests.
You can pass in some parameters that will be consumed by both the API and the component you would like to render.
Note that the template
parameter is mandatory, as it determines which template will be rendered.
Parameters
The template
parameter is mandatory and determines which template will be rendered.
The pdfOptions
parameter will be passed to the .pdf
method of the page
object, see https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagepdfoptions
All the other parameters will be passed to the component generated, as data parameters.
Here is an example of the object you can create:
const data = {
template: "exampleTemplate",
testFieldOne: "wunO",
testFieldTwo: "PDF",
testFieldThree: "Generator",
testFieldFour: "For generating PDFs",
testFieldFive: "Pass data in with a POST or GET request",
testFieldSix: "wunO PDF Generator",
chartOne: "100",
chartTwo: "200",
chartThree: "300",
chartFour: "400",
chartFive: "500"
pdfOptions: {
landscape: true,
margin: {
top: "20px",
left: "200px",
right: "20px",
bottom: "20px"
}
}
};
GET request sample, using fetch
const url = new URL(`${myEndpoint}`);
url.search = "data=" + encodeURI(JSON.stringify(data));
return fetch(url).then(response => response.blob());
POST request sample, using fetch
const url = new URL(`${myEndpoint}`);
return fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
}).then(response => response.blob());
Example API Call
http://localhost:8080/api/v1/pdf?data={"template":"exampleTemplate","testFieldOne":"wunO","testFieldTwo":"PDF Param","testFieldThree":"Generator Param","testFieldFour":"For generating PDFs Param","testFieldFive":"Pass data in with a POST or GET request","testFieldSix":"wunO PDF Generator","chartOne":"100","chartTwo":"200","chartThree":"300","chartFour":"400","chartFive":"500"}