<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ashish Kushwaha</title>
    <description>The latest articles on DEV Community by Ashish Kushwaha (@ashish_kushwaha).</description>
    <link>https://dev.to/ashish_kushwaha</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2721845%2Fa16ec701-c638-4956-8504-4b6cc325ba19.png</url>
      <title>DEV Community: Ashish Kushwaha</title>
      <link>https://dev.to/ashish_kushwaha</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ashish_kushwaha"/>
    <language>en</language>
    <item>
      <title>Generate PDFs in your n8n workflows with Cellystial</title>
      <dc:creator>Ashish Kushwaha</dc:creator>
      <pubDate>Thu, 25 Jun 2026 08:23:52 +0000</pubDate>
      <link>https://dev.to/ashish_kushwaha/generate-pdfs-in-your-n8n-workflows-with-cellystial-4877</link>
      <guid>https://dev.to/ashish_kushwaha/generate-pdfs-in-your-n8n-workflows-with-cellystial-4877</guid>
      <description>&lt;p&gt;&lt;strong&gt;A practical walkthrough: install the Cellystial community node, add your API key, generate a PDF from a template using data from a previous step, and save the file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I build Cellystial, a PDF generation tool, and one of the things people kept asking for was an n8n node. So we made one. This post walks through using it: installing the node, adding your key, and generating a real PDF from data that comes out of an earlier step in your workflow.&lt;/p&gt;

&lt;p&gt;The PDF is returned as a binary file, so you can drop it straight into Send Email, Write Binary File, Google Drive, S3, or whatever you already use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feeqp0nvwfgnmrzyohdz8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feeqp0nvwfgnmrzyohdz8.png" alt="A finished n8n workflow: data comes in, the Cellystial node makes the PDF, the next node sends it." width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One thing up front so nobody is surprised: this is an official Cellystial community node. You can install it on self-hosted n8n today. It is not yet a verified node and it is not on n8n Cloud yet (verification is in progress). If you run self-hosted n8n, you are good to go.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A self-hosted n8n instance.&lt;/li&gt;
&lt;li&gt;A Cellystial account. Registration is open and free, no card required.&lt;/li&gt;
&lt;li&gt;A template in your account that you want to fill with data.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 1: Install the node
&lt;/h2&gt;

&lt;p&gt;In n8n, go to &lt;strong&gt;Settings -&amp;gt; Community nodes -&amp;gt; Install&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Type the package name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;n8n-nodes-cellystial
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accept the community-node prompt and click Install. After a moment you'll see two new nodes in the panel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cellystial&lt;/strong&gt; is the action node (generate PDFs, check batch status).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cellystial Trigger&lt;/strong&gt; fires your workflow when something happens in your account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqxpcsvxr5ef0dcqlpjby.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqxpcsvxr5ef0dcqlpjby.png" alt="Open the Community nodes tab." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Facwpwzxfuby36tjvh32k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Facwpwzxfuby36tjvh32k.png" alt="Click Install a community node." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx19nafhdb58c753ucu3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fx19nafhdb58c753ucu3i.png" alt="Enter n8n-nodes-cellystial and accept the prompt." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fohirtkmz9vdn80gi45mo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fohirtkmz9vdn80gi45mo.png" alt="Installed: the Cellystial and Cellystial Trigger nodes are ready." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Get an API key and add the credential
&lt;/h2&gt;

&lt;p&gt;Log in to your dashboard at app.cellystial.com and go to &lt;strong&gt;Settings -&amp;gt; Developer &amp;amp; API&lt;/strong&gt;. Create a key. Keys start with &lt;code&gt;sk_prod_&lt;/code&gt; for real renders, or &lt;code&gt;sk_test_&lt;/code&gt; for free, watermarked renders that are good for trying things out.&lt;/p&gt;

&lt;p&gt;Back in n8n, create a new &lt;strong&gt;Cellystial API&lt;/strong&gt; credential and paste the key in. n8n validates the key the moment you save it, so if you typo'd it or grabbed the wrong one, you'll know right away instead of getting a confusing error later.&lt;/p&gt;

&lt;p&gt;My advice: start with an &lt;code&gt;sk_test_&lt;/code&gt; key. Test renders are free and watermarked, so you can wire the whole thing up and prove the flow works without paying for it. Swap to &lt;code&gt;sk_prod_&lt;/code&gt; when you're happy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq39t5whbwhf6z8dts0im.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq39t5whbwhf6z8dts0im.png" alt="Get your key in the dashboard under Settings, Developer and API." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc29amakeq0xyop0njbnx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc29amakeq0xyop0njbnx.png" alt="Paste it into a new Cellystial API credential in n8n." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Generate a PDF
&lt;/h2&gt;

&lt;p&gt;Add a &lt;strong&gt;Cellystial&lt;/strong&gt; node and set the operation to &lt;strong&gt;Generate PDF&lt;/strong&gt;. This generates one PDF per incoming item.&lt;/p&gt;

&lt;p&gt;You'll see these fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Template Name or ID&lt;/strong&gt; is a dropdown that loads the templates from your account automatically. Pick the one you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON Payload&lt;/strong&gt; is the data that goes into the template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File Name&lt;/strong&gt; defaults to &lt;code&gt;document.pdf&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put Output File in Field&lt;/strong&gt; is the field name the binary lands on. Defaults to &lt;code&gt;data&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8lwyr0ohxkwgd3jqz0sx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8lwyr0ohxkwgd3jqz0sx.png" alt="Add the Cellystial node." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0v1i16qzx9y76cp3unyb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0v1i16qzx9y76cp3unyb.png" alt="Choose the Generate PDF operation." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjk1o018z1cxjuni0dj5y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjk1o018z1cxjuni0dj5y.png" alt="Pick your template; it loads from your account." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Mapping data into the JSON Payload
&lt;/h3&gt;

&lt;p&gt;This is the part worth slowing down on.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;JSON Payload&lt;/strong&gt; field holds the data your template expects. The keys have to match the variable names in your template. You can type a literal JSON object, or you can map values from earlier nodes.&lt;/p&gt;

&lt;p&gt;To map from a previous step, click the field and switch it to &lt;strong&gt;Expression&lt;/strong&gt; mode (the toggle near the field). Then you can write an n8n expression.&lt;/p&gt;

&lt;p&gt;If the incoming item already has the exact shape your template wants, the simplest thing is to pass the whole item through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or build the object yourself and pull individual fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{{ $json.name }}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$json.amount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a fuller example payload. Say you have a coffee shop receipt template:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"customer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mara Ellis"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mara@example.com"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"receipt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"number"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"RCPT-5821"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Jun 17, 2026"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"method"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Visa ····4242"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"items"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cappuccino (12 oz)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$4.50"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Almond croissant"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$3.90"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"qty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"House blend beans (250g)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$10.50"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totals"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$23.40"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"balanceDue"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"$0.00"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PAID"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a real workflow those values would be expressions pointing at whatever came before, like a webhook payload or a row from your database. The literal version above is handy for a first test run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrqy6glh5zr6oeo8j1o4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrqy6glh5zr6oeo8j1o4.png" alt="Map data from a previous step into the JSON Payload, in expression mode." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flt2ewozuwayn9qow13m7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flt2ewozuwayn9qow13m7.png" alt="Execute the step to generate the PDF." width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Do something with the PDF
&lt;/h2&gt;

&lt;p&gt;When the node runs, the PDF comes out as &lt;strong&gt;binary&lt;/strong&gt; on the field you chose (&lt;code&gt;data&lt;/code&gt; by default). The incoming JSON is carried through unchanged, so you keep any fields you need later, like the order id or the customer's email.&lt;/p&gt;

&lt;p&gt;From there it's normal n8n. A few common next steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write Binary File&lt;/strong&gt; saves it to disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Send Email&lt;/strong&gt; attaches it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Drive / S3&lt;/strong&gt; uploads it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Point the binary property of those nodes at your output field (&lt;code&gt;data&lt;/code&gt;) and you're done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft7ayw0nxfwh2p6k36hu9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft7ayw0nxfwh2p6k36hu9.png" alt="The generated PDF comes back as binary on the data field." width="800" height="1061"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9xvzo8odzostohl58t4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9xvzo8odzostohl58t4t.png" alt="The finished PDF." width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Doing a lot of them: the Batch operation
&lt;/h2&gt;

&lt;p&gt;If you're generating many PDFs at once, switch the operation to &lt;strong&gt;Generate PDFs (Batch)&lt;/strong&gt;. It queues an async batch where each incoming item becomes one row.&lt;/p&gt;

&lt;p&gt;The data field here is named &lt;strong&gt;Row Data&lt;/strong&gt; instead of JSON Payload. It already ships in expression mode and defaults to &lt;code&gt;{{ $json }}&lt;/code&gt;, the whole incoming item, used once per row. (For single Generate PDF you flip JSON Payload to expression mode yourself; Row Data is already set up that way.)&lt;/p&gt;

&lt;p&gt;A few optional fields you might want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Document ID&lt;/strong&gt; is your own id for each PDF, echoed back so you can match each output to its source. It's also used as the filename.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output Filename&lt;/strong&gt; defaults to the Document ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Completion Webhook URL&lt;/strong&gt; gets called when the batch finishes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The batch operation returns a single item with the batch id and its status (queued while it gets going). To check on it later, use the &lt;strong&gt;Get Batch Status&lt;/strong&gt; operation with that &lt;strong&gt;Batch ID&lt;/strong&gt;. While it's still working you get a status summary. Once it's done it emits one item per row with the download links, including a &lt;code&gt;zipUrl&lt;/code&gt; for the whole batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trigger node, briefly
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Cellystial Trigger&lt;/strong&gt; starts a workflow when something happens in your account. On activation it registers an n8n webhook as a Cellystial subscription, and it cleans that up when you deactivate the workflow. The events are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pdf.generated&lt;/code&gt;, a single PDF finished&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;batch.completed&lt;/code&gt;, a bulk batch finished&lt;/li&gt;
&lt;li&gt;&lt;code&gt;template.created&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;template.updated&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;template.deleted&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deliveries are HMAC-signed (via the &lt;code&gt;X-Cellystial-Signature&lt;/code&gt; header), so you can confirm they actually came from us.&lt;/p&gt;

&lt;p&gt;A common pattern: have something generate a PDF, then use the &lt;code&gt;pdf.generated&lt;/code&gt; trigger in a second workflow to post a Slack message or update the order record once the file is ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's it
&lt;/h2&gt;

&lt;p&gt;Install the node, paste a test key, pick a template, map your data into the JSON Payload, and send the binary wherever it needs to go. Start with an &lt;code&gt;sk_test_&lt;/code&gt; key so the first run is free.&lt;/p&gt;

&lt;p&gt;The full guide with step-by-step screenshots lives here: &lt;a href="https://cellystial.com/integrations/n8n" rel="noopener noreferrer"&gt;https://cellystial.com/integrations/n8n&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you try it and hit something rough, I'd genuinely like to hear about it.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>cellystial</category>
      <category>nocode</category>
      <category>n8nbrightdatachallenge</category>
    </item>
    <item>
      <title>Why I ripped headless Chrome out of PDF generation and bet on Rust + Typst</title>
      <dc:creator>Ashish Kushwaha</dc:creator>
      <pubDate>Thu, 25 Jun 2026 07:11:51 +0000</pubDate>
      <link>https://dev.to/ashish_kushwaha/why-i-ripped-headless-chrome-out-of-pdf-generation-and-bet-on-rust-typst-2mgi</link>
      <guid>https://dev.to/ashish_kushwaha/why-i-ripped-headless-chrome-out-of-pdf-generation-and-bet-on-rust-typst-2mgi</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is Part 2 of 2 — the architecture. &lt;a href="https://cellystial.com/blog/the-typst-rabbit-hole" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; is the story: how a Zerodha blog post about generating 1.5M PDFs in 25 minutes turned into a product.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This post is the how: the actual architecture, and the one trick that mattered more than any other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Most "PDF APIs" are a headless browser in a trench coat
&lt;/h2&gt;

&lt;p&gt;If you've shipped PDF generation in production, you know the dirty secret: a lot of "PDF APIs" are a headless Chrome browser wearing a trench coat. You send HTML, a copy of Chromium renders a web page, and a screenshot-of-a-document comes back. It works beautifully in the demo. Then it meets real traffic.&lt;/p&gt;

&lt;p&gt;Browsers were built to render &lt;em&gt;interactive web pages&lt;/em&gt;, not to typeset documents at scale. Bending one into a document factory drags along a stack of problems:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts and weight.&lt;/strong&gt; Each render spins up (or holds open) a browser process measured in hundreds of megabytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output drift.&lt;/strong&gt; A Chromium version bump can quietly shift your invoice layout by a pixel — or a whole page. "Works on my machine" is not a great property for a legal document.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A wide attack surface.&lt;/strong&gt; You're executing a full browser engine on input that frequently contains user-controlled data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expensive concurrency.&lt;/strong&gt; More throughput means more browser processes, which means more memory and a bigger bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A permanent maintenance tax.&lt;/strong&gt; Fonts, sandboxes, zombie processes, and crashes become your problem forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is Chromium's fault. It's a phenomenal browser. It's just the wrong tool for printing a receipt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bet: a native engine on Typst
&lt;/h2&gt;

&lt;p&gt;Cellystial compiles templates to PDF natively in Rust, using &lt;a href="https://typst.app" rel="noopener noreferrer"&gt;Typst&lt;/a&gt; — a typesetting system designed for documents, not web pages. There is no browser anywhere in the pipeline. A template plus a JSON payload goes in; a pixel-perfect PDF comes out. The request below is illustrative — use your own template id and fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://api.cellystial.com/api/v1/generate &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_test_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "templateId": "invoice",
    "data": {
      "company_name": "Acme Inc.",
      "client_name": "Globex Corp",
      "items": [
        { "description": "Design retainer", "quantity": 1, "price": 3000 },
        { "description": "Development", "quantity": 40, "price": 50 }
      ],
      "tax_rate": 5
    }
  }'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--output&lt;/span&gt; invoice.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That one architectural decision pays off everywhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic output&lt;/strong&gt; — the same input produces the same document, byte for byte. No environment drift.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A tiny attack surface&lt;/strong&gt; — removing Chromium eliminates an entire class of rendering-engine vulnerabilities.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable, low memory&lt;/strong&gt; — no browser processes to babysit under load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real typesetting&lt;/strong&gt; — proper pagination, tables that expand to fit, crisp typography by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So far, so clean. But there's a wrinkle the marketing pages never mention.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part nobody warns you about: warm fonts are everything
&lt;/h2&gt;

&lt;p&gt;Here's the thing that ate the most of my time, and the part I'm proudest of.&lt;/p&gt;

&lt;p&gt;Not all templates are equal in how much you trust them. First-party templates — the ones Cellystial ships and controls — are safe to run &lt;strong&gt;in-process&lt;/strong&gt;, on the same heap as the HTTP server (Axum, in our case). They reuse a font cache that's already warm in memory, so a typical document renders in roughly &lt;strong&gt;2–10 ms per page&lt;/strong&gt; with sub-1 MB of overhead per concurrent render. Cheap enough to do thousands of times a second.&lt;/p&gt;

&lt;p&gt;But the whole point of the product is that &lt;em&gt;users&lt;/em&gt; author their own templates. And user-authored Typst is &lt;strong&gt;untrusted&lt;/strong&gt; — it has to run in an isolated child process with hard resource limits (a memory &lt;code&gt;rlimit&lt;/code&gt;, kill-on-drop, the works). You cannot let it share your server's heap.&lt;/p&gt;

&lt;p&gt;The naive way to isolate it is to spawn a fresh worker process per render. I tried that. It's correct, and it's slow — because &lt;strong&gt;the in-process font cache can't cross a process boundary.&lt;/strong&gt; Every fresh worker re-parses the &lt;em&gt;entire&lt;/em&gt; font set on startup. On my laptop that's about &lt;strong&gt;190 ms&lt;/strong&gt; of pure font warmup before a single glyph is drawn. In production, with the full Noto set loaded for international coverage, it's much worse. Suddenly the "milliseconds, not seconds" promise evaporates — not because Typst is slow, but because you're paying font warmup on every single call.&lt;/p&gt;

&lt;p&gt;The fix is a &lt;strong&gt;warm worker pool&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A small set of long-lived worker processes is pre-spawned at startup. Each one pays the font-warmup cost &lt;strong&gt;exactly once&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;They then serve many renders over a framed &lt;code&gt;stdin&lt;/code&gt;/&lt;code&gt;stdout&lt;/code&gt; protocol, so subsequent renders skip the re-parse entirely.&lt;/li&gt;
&lt;li&gt;Isolation is fully preserved — each worker is still a separate process with a memory &lt;code&gt;rlimit&lt;/code&gt; and kill-on-drop.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;cancellation-safe&lt;/strong&gt;: a worker is &lt;em&gt;owned&lt;/em&gt; by the in-flight render future. If that future is dropped (say, a per-render timeout fires), the worker is dropped too and &lt;code&gt;kill_on_drop&lt;/code&gt; terminates it — it's never handed back to the pool mid-render, so the framed protocol can't desync.&lt;/li&gt;
&lt;li&gt;Workers are &lt;strong&gt;recycled after a fixed number of renders&lt;/strong&gt; to bound memory growth, and any I/O or protocol error kills the worker instead of reusing it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: you get the safety of one-process-per-untrusted-render with (almost) the speed of in-process rendering. Warmup is paid at startup and on recycle, not on the hot path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The warm pool wasn't the whole story
&lt;/h2&gt;

&lt;p&gt;Once the worker pool was warm, fonts stopped being the bottleneck. So I went looking for what was left, and found a dumb one: the same template got compiled again on every single render. Parsing the Typst program, building it, then checking the request data against the template's schema. None of that changes between requests for a template you render over and over, but I was paying for it every time anyway.&lt;/p&gt;

&lt;p&gt;The fix is boring: keep the compiled template around and reuse it. The first render of a template does the parse-and-validate work, and every render after that skips straight to drawing the document. Same idea for a batch job, compile the template once, then render every row of data against it instead of recompiling per row.&lt;/p&gt;

&lt;p&gt;One line I want to be clear about, because "cache" plus "fast" usually means someone is handing you a saved file: we never cache the final PDF. Every document is rendered fresh from the data in your request, every time. The caching only removes the repeated compile work that sits in front of the render. The render itself is still done live, which is the whole point, and it's why editing the data on the homepage demo actually changes the document instead of returning the same one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it looks like in numbers
&lt;/h2&gt;

&lt;p&gt;Because the engine keeps its fonts warm and renders without a browser, a typical document comes back in &lt;strong&gt;single-digit to low double-digit milliseconds once warm&lt;/strong&gt; — not the seconds you wait on a browser screenshot.&lt;/p&gt;

&lt;p&gt;I'm deliberately &lt;em&gt;not&lt;/em&gt; quoting a big "100× faster" multiplier here, because I haven't published a head-to-head benchmark I'd want a skeptical engineer to hold me to. The honest, defensible version is the one above: warm renders land in milliseconds, and the architecture is the reason — not a tuning project you graduate to later.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually shipped
&lt;/h2&gt;

&lt;p&gt;To keep this grounded in reality rather than roadmap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual drag-and-drop builder&lt;/strong&gt; that compiles to Typst (with a raw-Typst escape hatch).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST API&lt;/strong&gt; for single renders, plus &lt;strong&gt;batch/async&lt;/strong&gt; generation with webhook callbacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AES-256 password protection&lt;/strong&gt; with granular permissions (print / modify / extract).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Email + cloud delivery&lt;/strong&gt; — generate and hand back a pre-signed link, or email the PDF on render.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;sample template library&lt;/strong&gt; (invoices, contracts, certificates, tickets) you can clone.&lt;/li&gt;
&lt;li&gt;An official &lt;strong&gt;&lt;a href="https://cellystial.com/integrations/n8n" rel="noopener noreferrer"&gt;n8n node&lt;/a&gt;&lt;/strong&gt; for no-code workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the real surface today. (More integrations are in progress; I'd rather under-promise here.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The short version: if your documents have outgrown HTML-to-PDF, that's a good problem — it means they matter. The browser was never the right tool to typeset them; a native engine is. Typst made the speed possible, and a warm worker pool made it possible &lt;em&gt;safely&lt;/em&gt;, even for untrusted user templates.&lt;/p&gt;

&lt;p&gt;If you missed it, &lt;a href="https://cellystial.com/blog/the-typst-rabbit-hole" rel="noopener noreferrer"&gt;&lt;strong&gt;Part 1&lt;/strong&gt;&lt;/a&gt; is the origin story behind all of this. And the product is live and free to try at &lt;strong&gt;&lt;a href="https://cellystial.com" rel="noopener noreferrer"&gt;cellystial.com&lt;/a&gt;&lt;/strong&gt; — it has only few users as I write this, so genuinely: come find the edges and tell me where it breaks. ☕&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally written for the Cellystial blog. If you build with PDFs, I'd love your sharpest feedback.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>typst</category>
      <category>htmltopdf</category>
      <category>cellystial</category>
    </item>
    <item>
      <title>A blog post sent me down a 6-month rabbit hole. Here's what I built.</title>
      <dc:creator>Ashish Kushwaha</dc:creator>
      <pubDate>Thu, 25 Jun 2026 06:54:30 +0000</pubDate>
      <link>https://dev.to/ashish_kushwaha/a-blog-post-sent-me-down-a-6-month-rabbit-hole-heres-what-i-built-49e8</link>
      <guid>https://dev.to/ashish_kushwaha/a-blog-post-sent-me-down-a-6-month-rabbit-hole-heres-what-i-built-49e8</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This is Part 1 of 2 — the story. &lt;a href="https://cellystial.com/blog/why-typst-not-headless-chromium" rel="noopener noreferrer"&gt;Part 2&lt;/a&gt; is the architecture: how it actually hits single-digit-millisecond renders.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The blog post that nerd-sniped me
&lt;/h2&gt;

&lt;p&gt;It started, like a lot of bad decisions do, with a blog post at 1 a.m.&lt;/p&gt;

&lt;p&gt;I came across Zerodha's engineering write-up, &lt;a href="https://zerodha.tech/blog/1-5-million-pdfs-in-25-minutes/" rel="noopener noreferrer"&gt;"1.5 million PDFs in 25 minutes."&lt;/a&gt; It describes how they tore headless Chrome out of their PDF pipeline, tried LaTeX, and eventually landed on &lt;strong&gt;Typst&lt;/strong&gt; — a modern, open-source typesetting engine written in Rust. The number that stuck with me: a 2,000-page document that took &lt;em&gt;18 minutes&lt;/em&gt; to render with LaTeX came back in about &lt;em&gt;1 minute&lt;/em&gt; with Typst, out of a tiny statically-linked binary.&lt;/p&gt;

&lt;p&gt;Around the same time, I read how Zomato had rebuilt their own PDF pipeline — they call it &lt;a href="https://www.zomato.com/blog/espresso-brewing-pdfs-at-zomato-faster-than-you-can-say-cappuccino" rel="noopener noreferrer"&gt;"Espresso"&lt;/a&gt; — to render and sign over a million PDFs in minutes.&lt;/p&gt;

&lt;p&gt;Two of the biggest engineering teams I knew of, both ripping out their document stacks for the same reason: generating PDFs at scale had quietly become a serious bottleneck. And they'd taken &lt;em&gt;different&lt;/em&gt; roads — Zomato squeezed every drop of performance out of headless Chromium with Go; Zerodha abandoned the browser entirely.&lt;/p&gt;

&lt;p&gt;I couldn't stop thinking about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  So I had to see it myself
&lt;/h2&gt;

&lt;p&gt;The claims sounded too good, so I did the obvious thing: I tried Typst on my own machine.&lt;/p&gt;

&lt;p&gt;A document that took a few seconds to render through a headless browser was ready in &lt;strong&gt;milliseconds&lt;/strong&gt; — and it looked perfect every single time. I'd braced for "a bit faster." This wasn't a bit faster. It was a different category of thing. Once you've watched a browser screenshot-a-document next to a native engine compile one, you can't unsee the gap.&lt;/p&gt;

&lt;p&gt;That was the moment the rabbit hole opened.&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch nobody mentions
&lt;/h2&gt;

&lt;p&gt;Here's the part that complicates the happy ending: Typst is a &lt;em&gt;language&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;It's a beautifully designed one — far saner than wrestling LaTeX or hand-tuning HTML/CSS for print. But it's still something you have to sit down and learn. And the more I thought about who actually needs documents, the more that bothered me.&lt;/p&gt;

&lt;p&gt;The person who needs an invoice, a receipt, a contract, or an event ticket is usually not the person who wants to learn a typesetting language. They're founders, ops people, designers, marketers. They have a layout in their head and a pile of data in a database. Asking them to learn Typst syntax to get a PDF is like asking someone to learn PostScript to print a letter. They'll never do it — and honestly, they shouldn't have to.&lt;/p&gt;

&lt;p&gt;So the speed was incredible, but it was locked behind a wall most people would never climb.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: Typst's speed, without ever touching Typst
&lt;/h2&gt;

&lt;p&gt;That gap became the entire idea. &lt;strong&gt;What if you could get all of Typst's speed without ever writing a line of Typst?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I started with a rough proof-of-concept: a visual, drag-and-drop builder that quietly compiled down to Typst under the hood. You drag in a few blocks — a header, a table that expands to fit line items, a totals row — bind them to a JSON payload, and hit generate.&lt;/p&gt;

&lt;p&gt;The moment that POC actually worked — I dragged a few blocks around, clicked generate, and a clean PDF popped out in milliseconds — I knew this had to exist as a real product. Not a script in my side-projects folder. A real thing other people could use.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it became
&lt;/h2&gt;

&lt;p&gt;That's &lt;strong&gt;Cellystial&lt;/strong&gt;: a drag-and-drop visual builder &lt;em&gt;and&lt;/em&gt; a fast REST API, both sitting on top of a native Rust + Typst engine. You design a template visually (or drop down to raw Typst if you want), then generate pixel-perfect invoices, receipts, contracts, certificates, and tickets at scale — by sending a JSON payload to a single endpoint. No browser anywhere in the pipeline. No Typst syntax required.&lt;/p&gt;

&lt;p&gt;The whole thing is built around one belief I picked up that first night: &lt;strong&gt;performance is a feature, not a tuning project you get to later.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  It's live
&lt;/h2&gt;

&lt;p&gt;Full transparency, because build-in-public should mean it: Cellystial is live in production right now, and I'm just starting to get it in front of people. This post is part of that.&lt;/p&gt;

&lt;p&gt;So if you've ever fought headless-Chrome memory leaks, babysat zombie browser processes, or watched an invoice layout drift a pixel after a Chromium bump — I would genuinely love for you to come kick the tires and tell me where it breaks: &lt;strong&gt;&lt;a href="https://cellystial.com" rel="noopener noreferrer"&gt;cellystial.com&lt;/a&gt;&lt;/strong&gt; (the free tier needs no credit card).&lt;/p&gt;

&lt;p&gt;And to the Zerodha and Zomato engineering teams whose blogs kicked this whole thing off — if we ever cross paths, the coffee's on me. ☕&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Next post — **Part 2: the architecture.&lt;/em&gt;* Why I threw out the browser, how the Rust + Typst engine is wired, and the warm-worker-pool trick that keeps renders in single-digit milliseconds even for untrusted, user-authored templates. &lt;a href="https://cellystial.com/blog/why-typst-not-headless-chromium" rel="noopener noreferrer"&gt;Read Part 2 →&lt;/a&gt;*&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>htmltopdf</category>
      <category>typst</category>
      <category>cellystial</category>
    </item>
    <item>
      <title>Production Ready NestJS Microservice Setup with Candy-Nest-CLI 🍬</title>
      <dc:creator>Ashish Kushwaha</dc:creator>
      <pubDate>Mon, 18 May 2026 22:20:37 +0000</pubDate>
      <link>https://dev.to/ashish_kushwaha/automating-nestjs-microservice-setup-with-candy-nest-cli-2ig9</link>
      <guid>https://dev.to/ashish_kushwaha/automating-nestjs-microservice-setup-with-candy-nest-cli-2ig9</guid>
      <description>&lt;p&gt;If you build microservices with NestJS, you already know how powerful and structured the framework is. However, starting a new project often involves a fair amount of initial setup.&lt;/p&gt;

&lt;p&gt;Every time a new service is spun up, developers usually have to go through similar steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up &lt;code&gt;docker-compose.yml&lt;/code&gt; for local databases.&lt;/li&gt;
&lt;li&gt;Configuring TypeORM, Prisma, or Mongoose.&lt;/li&gt;
&lt;li&gt;Wiring up message brokers like Kafka or RabbitMQ.&lt;/li&gt;
&lt;li&gt;Dealing with local environment startup issues.&lt;/li&gt;
&lt;li&gt;Setting up loggers and metrics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the time you actually start writing business logic, a good amount of time has already gone into scaffolding. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Save your AI tokens!&lt;/strong&gt; While you could prompt ChatGPT or Claude to write this boilerplate, generating a fully-wired NestJS microservice with infrastructure configs easily burns &lt;strong&gt;3,000 to 5,000+ tokens&lt;/strong&gt; per project (not to mention the context window eaten up by debugging configuration errors). Candy-Nest-CLI lets you bypass that entirely so you can save your usage limits for writing actual business logic.&lt;/p&gt;

&lt;p&gt;To help automate this process, I put together &lt;strong&gt;Candy-Nest-CLI&lt;/strong&gt;—an open-source interactive scaffolding tool designed to generate production-ready NestJS microservices.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Building a Service: Step-by-Step
&lt;/h2&gt;

&lt;p&gt;Let's walk through exactly how easy it is to spin up a fully-wired backend service with a PostgreSQL database and Kafka messaging queue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Installation &amp;amp; Initialization
&lt;/h3&gt;

&lt;p&gt;You can either install the CLI globally to have it available anywhere, or run it directly on the fly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Global Installation (Recommended)&lt;/strong&gt;&lt;br&gt;
Open your terminal and install the package globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Using npm&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; candy-nest-cli

&lt;span class="c"&gt;# Or using yarn&lt;/span&gt;
yarn global add candy-nest-cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed, you can generate a new project by running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;candy-nest-cli init user-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Using npx (No Installation)&lt;/strong&gt;&lt;br&gt;
If you prefer not to install packages globally, you can just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx candy-nest-cli init user-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Choose Your Stack
&lt;/h3&gt;

&lt;p&gt;The CLI uses &lt;code&gt;@inquirer/prompts&lt;/code&gt; to walk you through a highly detailed setup. To build our target microservice, we will answer the prompts like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What is the name of your project?&lt;/strong&gt; &lt;code&gt;user-service&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which package manager do you want to use?&lt;/strong&gt; &lt;code&gt;npm&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select the communication protocols to support:&lt;/strong&gt; &lt;code&gt;REST&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which HTTP adapter do you want to use?&lt;/strong&gt; &lt;code&gt;Express&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you want to include a database?&lt;/strong&gt; &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Select databases to include:&lt;/strong&gt; &lt;code&gt;PostgreSQL (RDBMS)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which ORM for PostgreSQL?&lt;/strong&gt; &lt;code&gt;TypeORM&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you want to configure an messaging queue?&lt;/strong&gt; &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which messaging queue do you want to use?&lt;/strong&gt; &lt;code&gt;Kafka&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you want to configure Dead Letter Queue (DLQ) and Retries?&lt;/strong&gt; &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you want to include Redis for caching?&lt;/strong&gt; &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which logger do you want to configure?&lt;/strong&gt; &lt;code&gt;Pino&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Which tracing &amp;amp; metrics solution do you want?&lt;/strong&gt; &lt;code&gt;Prometheus&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you want to generate API documentation (Swagger)?&lt;/strong&gt; &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you want to include a Circuit Breaker (Opossum)?&lt;/strong&gt; &lt;code&gt;Yes&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  1. Package Manager Selection
&lt;/h4&gt;

&lt;p&gt;Choose your preferred package manager (&lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;yarn&lt;/code&gt;, or &lt;code&gt;pnpm&lt;/code&gt;). We will select &lt;code&gt;npm&lt;/code&gt; for this project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff34ghxeeyz68qlvlfava.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff34ghxeeyz68qlvlfava.png" alt="Package Manager Selection" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  2. Protocol Selection
&lt;/h4&gt;

&lt;p&gt;Select the communication protocols your microservice will support. The CLI supports a multi-protocol combination of REST, GraphQL, gRPC, and WebSockets. We'll go with &lt;code&gt;REST&lt;/code&gt; for standard HTTP routing.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5rw91yh00f1952z1wpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk5rw91yh00f1952z1wpa.png" alt="Protocol Selection" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. HTTP Adapter Selection
&lt;/h4&gt;

&lt;p&gt;Choose between &lt;code&gt;Express&lt;/code&gt; (the industry standard) and &lt;code&gt;Fastify&lt;/code&gt; (high performance). We'll select &lt;code&gt;Express&lt;/code&gt; for maximum compatibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbge296izty1lhx1qx16k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbge296izty1lhx1qx16k.png" alt="Adapter Selection" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Database Integration
&lt;/h4&gt;

&lt;p&gt;Select whether you want to include a database module. We will select &lt;code&gt;Yes&lt;/code&gt; to set up persistence.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea4zbf4r6iaq1f9ggr5j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea4zbf4r6iaq1f9ggr5j.png" alt="Database Option Selection" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  5. Database Choice Selection
&lt;/h4&gt;

&lt;p&gt;Choose which database to use (PostgreSQL, MySQL, or MongoDB). We'll select &lt;code&gt;PostgreSQL (RDBMS)&lt;/code&gt; for robust relational database management.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiiiod80w4mnichidh27w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiiiod80w4mnichidh27w.png" alt="Database Choice Selection" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  6. Database ORM Selection
&lt;/h4&gt;

&lt;p&gt;Select your ORM of choice. The CLI supports &lt;code&gt;TypeORM&lt;/code&gt; or &lt;code&gt;Prisma&lt;/code&gt; for SQL databases. Let's select &lt;code&gt;TypeORM&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2blo41aoea57jcijtrtb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2blo41aoea57jcijtrtb.png" alt="ORM Selection" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  7. Messaging Queue Integration
&lt;/h4&gt;

&lt;p&gt;Select whether you want to configure an event-driven messaging queue. We'll select &lt;code&gt;Yes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1opjx13mn5prmo7e8rj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu1opjx13mn5prmo7e8rj.png" alt="Queue Option Selection" width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  8. Queue Choice Selection
&lt;/h4&gt;

&lt;p&gt;Choose between &lt;code&gt;Kafka&lt;/code&gt;, &lt;code&gt;RabbitMQ&lt;/code&gt;, and &lt;code&gt;BullMQ&lt;/code&gt; as your message broker. We'll select &lt;code&gt;Kafka&lt;/code&gt; to build an event-driven stream processing microservice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tlhplqyddf3qilju1ju.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7tlhplqyddf3qilju1ju.png" alt="Queue Choice Selection" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  9. Dead Letter Queue (DLQ) &amp;amp; Retries
&lt;/h4&gt;

&lt;p&gt;Select whether to set up robust message handling with a Dead Letter Queue (DLQ) and custom retry loops to ensure event resiliency out of the box. We'll select &lt;code&gt;Yes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0rmvus6rknp2s4f9obcm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0rmvus6rknp2s4f9obcm.png" alt="DLQ Prompt Selection" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  10. Redis Caching
&lt;/h4&gt;

&lt;p&gt;Choose whether to set up Redis as a fast, in-memory distributed cache. We'll select &lt;code&gt;Yes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusms7wdz2elleghfdsa0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fusms7wdz2elleghfdsa0.png" alt="Redis Prompt Selection" width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  11. Logger Selection
&lt;/h4&gt;

&lt;p&gt;Select your logging solution (&lt;code&gt;Winston&lt;/code&gt;, &lt;code&gt;Pino&lt;/code&gt;, or the default NestJS logger). We'll select &lt;code&gt;Pino&lt;/code&gt; for high-speed JSON logging.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7d4gytv3m4ouaz5j5cd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd7d4gytv3m4ouaz5j5cd.png" alt="Logger Selection" width="800" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  12. Observability &amp;amp; Tracing
&lt;/h4&gt;

&lt;p&gt;Select your observability stack (&lt;code&gt;Prometheus&lt;/code&gt; or &lt;code&gt;OpenTelemetry&lt;/code&gt;). We will configure &lt;code&gt;Prometheus&lt;/code&gt; metrics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncj9rdjph5cdjnkt0y7e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fncj9rdjph5cdjnkt0y7e.png" alt="Observability Selection" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  13. API Documentation
&lt;/h4&gt;

&lt;p&gt;Choose if you want fully-featured auto-generating Swagger documentation for your REST APIs. We'll select &lt;code&gt;Yes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgs1pj5tf3hxmz8r7548a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgs1pj5tf3hxmz8r7548a.png" alt="Swagger Prompt Selection" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  14. Circuit Breaker
&lt;/h4&gt;

&lt;p&gt;Enable &lt;code&gt;Opossum&lt;/code&gt;-based Circuit Breakers to guard your outgoing HTTP requests and RPC calls from downstream failures. We'll select &lt;code&gt;Yes&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm81g64rqu6321v9kd1iz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm81g64rqu6321v9kd1iz.png" alt="Circuit Breaker Prompt Selection" width="800" height="310"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After confirming your final choice, the CLI will output the progress as it generates the files, writes configurations, and installs the dependencies automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwu0uv4c1jr29abfre4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvwu0uv4c1jr29abfre4m.png" alt="Generation Progress" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ne27354hdul26vm5ovt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ne27354hdul26vm5ovt.png" alt="Scaffolding Complete" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Sensible Defaults vs. Ultimate Flexibility
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;candy-nest-cli&lt;/code&gt; is designed with sensible defaults, allowing you to breeze through by just pressing &lt;code&gt;Enter&lt;/code&gt;. In our guide, we only customized a few options (like choosing &lt;strong&gt;Pino&lt;/strong&gt; for faster logging and enabling &lt;strong&gt;Dead Letter Queues&lt;/strong&gt; for Kafka). &lt;/p&gt;

&lt;p&gt;Here is how our choices compare against the CLI's default settings and alternative integrations:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Selected in Walkthrough&lt;/th&gt;
&lt;th&gt;Default Preset&lt;/th&gt;
&lt;th&gt;Alternative Choices&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Package Manager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;yarn&lt;/code&gt;, &lt;code&gt;pnpm&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Protocols&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;REST&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[REST]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GraphQL&lt;/code&gt;, &lt;code&gt;gRPC&lt;/code&gt;, &lt;code&gt;WebSockets&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HTTP Adapter&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Express&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Express&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Fastify&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PostgreSQL (RDBMS)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PostgreSQL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MySQL&lt;/code&gt;, &lt;code&gt;MongoDB&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SQL ORM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TypeORM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;TypeORM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Prisma&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Messaging Queue&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Kafka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Kafka&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RabbitMQ&lt;/code&gt;, &lt;code&gt;BullMQ&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DLQ &amp;amp; Retries&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes (Customized)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;No&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;No&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Redis Caching&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;No&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Logger&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Pino (Customized)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Winston&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Winston&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Prometheus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Prometheus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;OpenTelemetry&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Swagger API Docs&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;No&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Circuit Breaker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Yes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;No&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Step 3: Boot Up the Infrastructure
&lt;/h3&gt;

&lt;p&gt;Once the CLI finishes generating the code, all your infrastructure is ready to go. No writing Docker files from scratch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;user-service
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env
docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
npm run start:dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because of how the CLI scaffolds the project, &lt;strong&gt;Kafka boots in KRaft mode&lt;/strong&gt; with a special initialization container. You will see Postgres, Redis, and Kafka all start up perfectly without any race conditions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2x689c891v26j9rtmyn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2x689c891v26j9rtmyn.png" alt="Docker Compose Up" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Run the App
&lt;/h3&gt;

&lt;p&gt;With our infrastructure running, we just start the NestJS app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm run start:dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpbbuu51x6i03nsd1hp1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpbbuu51x6i03nsd1hp1x.png" alt="NestJS Startup Logs" width="800" height="464"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Just like that, you have a production-ready microservice running in under 2 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; you might get kafka-topic related error at startup, you can simply restart the application after a few seconds, and topic will be created or you can exec into docker and create the topic manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Interactive Developer Playgrounds
&lt;/h3&gt;

&lt;p&gt;If you enabled &lt;strong&gt;Swagger API Docs&lt;/strong&gt; or &lt;strong&gt;GraphQL&lt;/strong&gt;, the CLI pre-configures and exposes rich, interactive playgrounds out of the box so you can immediately execute and test operations:&lt;/p&gt;

&lt;h4&gt;
  
  
  Swagger API Playground (REST)
&lt;/h4&gt;

&lt;p&gt;Access the auto-generated Swagger UI at &lt;code&gt;http://localhost:3000/api/docs&lt;/code&gt; to see your REST endpoints, database CRUD examples, and health checks beautifully laid out:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9911e7v73vass9bpdc0v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9911e7v73vass9bpdc0v.png" alt="Swagger API Documentation" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Apollo Sandbox Playground (GraphQL)
&lt;/h4&gt;

&lt;p&gt;If you enabled GraphQL, open &lt;code&gt;http://localhost:3000/graphql&lt;/code&gt; to view the Apollo Sandbox where you can run queries, check schemas, and test mutations immediately:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1yok3amlb4vctebu2nvv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1yok3amlb4vctebu2nvv.png" alt="Apollo GraphQL Sandbox" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ What Exactly Gets Generated?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;candy-nest-cli&lt;/code&gt; dynamically generates &lt;em&gt;only&lt;/em&gt; the modules you asked for.&lt;/p&gt;

&lt;p&gt;Here is a look at the generated project structure for the service we just built:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2u1pzujj7u3o4pkn974.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj2u1pzujj7u3o4pkn974.png" alt="Generated Project Structure" width="385" height="1024"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user-service/
├── dist/
├── logs/
├── node_modules/
├── src/
│   ├── database/           # Database configuration and connection module
│   ├── examples/           # REST CRUD database examples
│   ├── graphql/            # GraphQL resolvers and schema setup
│   ├── grpc/               # gRPC controller and service implementations
│   ├── health/             # Terminus-based health indicators
│   ├── kafka/              # Kafka microservice controller and providers
│   ├── logger/             # Pino/Winston logging configurations
│   ├── redis/              # Redis caching module and service
│   ├── resiliency/         # Circuit Breaker (Opossum) configuration
│   ├── websockets/         # WebSockets gateway and adapter
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   ├── app.throttler.guard.ts # Robust cross-protocol rate limiting guard
│   ├── main.ts             # Microservice hybrid bootstrapper (HTTP, gRPC, Kafka)
│   └── schema.gql          # Auto-generated GraphQL schema
├── test/                   # E2E test suites
├── .env
├── .env.example
├── .prettierrc
├── docker-compose.yml      # Local dev stack (PostgreSQL, Kafka, Redis)
├── Dockerfile              # Multi-stage production build definition
├── eslint.config.mjs
├── nest-cli.json
├── package-lock.json
├── package.json
├── README.md
├── tsconfig.build.json
└── tsconfig.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Fully Wired Code Modules
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Databases:&lt;/strong&gt; It generates &lt;code&gt;database.module.ts&lt;/code&gt;, injects the &lt;code&gt;ConfigService&lt;/code&gt;, and creates a sample Entity or Schema. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Message Brokers:&lt;/strong&gt; It scaffolds the microservice transport layer, including client publishers and controller decorators to consume events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing Suite:&lt;/strong&gt; Fully-mocked unit tests (&lt;code&gt;.spec.ts&lt;/code&gt;) are generated for &lt;em&gt;every&lt;/em&gt; selected module (including database services, Kafka producers/consumers, Pino loggers, and Redis). It even includes a dedicated regression E2E test file (&lt;code&gt;regression.e2e-spec.ts&lt;/code&gt;) so you can run &lt;code&gt;npm run test&lt;/code&gt; out of the box!&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Documentation:&lt;/strong&gt; Because we selected REST, Swagger is automatically hooked up to &lt;code&gt;main.ts&lt;/code&gt;!&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🚀 Quick Start / Cheatsheet
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start a new interactive project:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx candy-nest-cli init my-awesome-microservice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Skip the prompts and generate everything (Kitchen Sink mode):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx candy-nest-cli init my-kitchen-sink-service &lt;span class="nt"&gt;--all&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Add a new feature to an existing CLI-generated project:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx candy-nest-cli add
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Candy-Nest-CLI?
&lt;/h2&gt;

&lt;p&gt;The NestJS ecosystem is fantastic, but maintaining consistency across a large microservice architecture can sometimes be challenging. &lt;/p&gt;

&lt;p&gt;The goal of this CLI is to provide a standardized, fast way to boot up services that follow common best practices from day one. &lt;/p&gt;

&lt;p&gt;If you work with NestJS, feel free to give it a try. I am actively maintaining it and always open to feedback or contributions!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;📦 &lt;strong&gt;NPM:&lt;/strong&gt; &lt;a href="https://www.npmjs.com/package/candy-nest-cli" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/candy-nest-cli&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;⭐ &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/knight-koder/candy-cli" rel="noopener noreferrer"&gt;https://github.com/knight-koder/candy-cli&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know what you think in the comments below. Happy coding! 🚀&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>microservices</category>
      <category>node</category>
    </item>
  </channel>
</rss>
