By the end of this article, you should have a working mix project that can generate a PDF from an
HTML
template and json
payload.This tutorial assumes you have basic experience with the Elixir language.
Generate a New MixĀ Project
Obviously, you can call your project w/e you want. For the sake of this article, Iāll call mine āPDFerā because it PDFs things. No other reasonā¦
Weāll generate our project using
mix
. Running mix new pdfer --sup
(or w/e you call your project) will produce something resembling the following output:Once the project is created, change into the project directory (
cd pdfer
) and open it in your editorāāā codeĀ .
if youāre using VSCode (and have that configured).Once open, you should see the following files:
Install Dependencies
We actually only need three dependencies (two depending on how you decide to do it).
elixir-pdf-generator: This will handle the actual generation of the PDF
jason: This will be used to parse our template āmappingā data.
NOTE: Iād love some insight from the Elixir community on the differences between jason and poison, and if there is a growing preference.
bbmustache: This handles merging our
json
mapping with our template file. Itās all done using mustache, so itās super clean and simple.Initially, I tried using the library youāre linked to from mustacheās own website, however, I wasnāt able to get it working non-empty lists and inverted-selections (something I needed).
After adding the above dependencies to your
mix.exs
, it should look something like this:Ā defp deps do
[
{:bbmustache, github: "soranoba/bbmustache"},
{:jason, "~> 1.1"},
{:pdf_generator, ">=0.5.5"}
]
end
Install all our dependencies with
mix deps.get
and get ready to write some codeā¦Ready to Write SomeĀ Code!
In the Pdfer module, youāll see a hello method. Weāre going to replace this with a generate method. generate will do a couple of things:
- Read both the
andtemplate
files from disk. This step is actually optional. If you wanted to, you could simply create the variablesmapping
andtemplate
and set their values inline to bemapping
andhtml
respectively. Iām reading from disk for now simply because itās more useful out-of-the-box if you wanted to start generating PDFs right away.json
will take thebbmustache
andtemplate
and āmustacheā them together for us. This is actually really awesome because, otherwise, weād have to do some messy string interpolation stuff. And frankly, it would be impossible if you were trying to use multiple templates and mappings to generate different PDFs.mapping
- Generate a PDF. For now, weāll be using
to generate our PDF, however, Iād love to explore usingwkhtmltopdf
and headless chrome in the future. Luckily,puppeteer
gives us that flexibility.elixir-pdf-generator
- Write the resulting PDF to disk. In the future, we could send the resulting PDF somewhere like S3, but for now, we just want to see the result of all our hard work!
defmodule Pdfer do
@moduledoc """
Documentation for Pdfer.
"""
#
def generate(pdf_path, template_path, mapping_path) do
mapping = Jason.decode!(read_file(mapping_path))
template = read_file(template_path)
template
|> :bbmustache.render(mapping, key_type: :binary)
|> PdfGenerator.generate_binary!()
|> (&File.write("#{pdf_path}.pdf", &1)).()
end
#
defp read_file(filepath) do
__DIR__
|> Path.join(filepath)
|> File.read!()
end
end
Last Step
Depending on if you decided to read these from disk, or just drop them into the code inline, the final pieces to this may or may not be adding the
template.html
and mapping.json
files.For now, weāll just dump both of them in the file alongside our module. Create a
template.html
and mapping.json
at pdfer/lib/
with the following contents:<html>
<body>
<h1>Hello {{ location }}</h1>
</body>
</html>
{"location": "World"}
Generate aĀ PDF
Thatās it! Weāre ready to generate a PDF. Run our code with
iex -S mix
to drop into iex
. From there weāll run our module, giving it three arguments. A PDF name, the template file name, and the mapping file name:iex(1)> Pdfer.generate("test", "template.html", "mapping.json"
Almost instantly, you should see a
test.pdf
in the root of your project directory.Conclusion
Itās pretty awesome that, with barely 20 lines of code, weāre able to generate a PDF from a dynamic template and data set.
From here you could set up an endpoint to accept a payload and generate the PDF that way. Or, as Iāll explain in another article, you could set up a sweet AWS messaging pipe that your PDFer reads from!