A Node.js DOCX generator for creating beautifully styled educational module documents. Feed it raw content, run one command, get a fully styled Word document.
Follow the Setup section to clone and run the project locally.
If someone already has this project running and wants to generate a new module, skip to the Block Components Reference and Typical Structure sections — everything needed to write a perfect current.js is there.
- Node.js >= 18
- npm
git clone https://github.com/Nubaise/Docmaker.git
cd Docmaker
npm install
node generate.js
Output: output/module.docx
Docmaker/
│
├── generate.js # Entry point — builds and outputs the DOCX (never edit this)
│
├── framework/
│ ├── blocks.js # All reusable document components
│ └── theme.js # All colors, fonts, sizes — single source of truth
│
├── modules/
│ └── current.js # The module content — THIS IS THE ONLY FILE YOU CHANGE
│
├── output/
│ └── module.docx # Generated output (gitignored)
│
├── package.json
└── README.md
- Write (or generate)
modules/current.jswith your module content - Run
node generate.js - Open
output/module.docxin Word
Only modules/current.js changes. Everything else is frozen.
All blocks live in framework/blocks.js. They are passed into current.js automatically via the blocks argument.
Some blocks return arrays and require the spread operator ...
Some blocks return a single element and use plain push
| Block | Returns | Usage |
|---|---|---|
createModuleTitle(moduleNumber, title) |
array | children.push(...blocks.createModuleTitle(...)) |
createLearningObjectives(items[]) |
array | children.push(...blocks.createLearningObjectives([...])) |
createCodeBlock(code) |
array | children.push(...blocks.createCodeBlock(...)) |
createInfoBox(label, text) |
array | children.push(...blocks.createInfoBox(...)) |
createWarningBox(label, text) |
array | children.push(...blocks.createWarningBox(...)) |
createPartHeading(text) |
single | children.push(blocks.createPartHeading(...)) |
createSubHeading(text) |
single | children.push(blocks.createSubHeading(...)) |
createMinorHeading(text) |
single | children.push(blocks.createMinorHeading(...)) |
createBody(text) |
single | children.push(blocks.createBody(...)) |
createBullet(text, level?) |
single | children.push(blocks.createBullet(...)) |
createNumbered(text) |
single | children.push(blocks.createNumbered(...)) |
createChecklistItem(text) |
single | children.push(blocks.createChecklistItem(...)) |
createTable(headers[], rows[][], colWidths[]?) |
single | children.push(blocks.createTable(...)) |
createSpacer(spacingAfter?) |
single | children.push(blocks.createSpacer()) |
children.push(
...blocks.createModuleTitle("Module 3", "Docker Volumes & Networking"),
);children.push(
...blocks.createLearningObjectives([
"Understand what Docker volumes are",
"Create and manage named volumes",
]),
);children.push(blocks.createPartHeading("1. Main Section")); // H1 — navy, large
children.push(blocks.createSubHeading("Subsection Title")); // H2 — blue
children.push(blocks.createMinorHeading("Minor Topic")); // H3 — dark, smallerchildren.push(blocks.createBody("This is a paragraph of body text."));children.push(blocks.createBullet("First point"));
children.push(blocks.createBullet("Nested point", 1)); // level 1 = indentedchildren.push(blocks.createNumbered("Step one"));
children.push(blocks.createNumbered("Step two"));children.push(blocks.createChecklistItem("I understand packet switching"));
children.push(
blocks.createChecklistItem("I can explain client-server architecture"),
);children.push(
...blocks.createCodeBlock(
`docker run -d \\
--name my_container \\
nginx`,
),
);children.push(
...blocks.createInfoBox(
"💡 Pro Tip",
"Always use named volumes in production.",
),
);children.push(
...blocks.createWarningBox(
"⚠ Warning",
"Never store secrets in a Docker image layer.",
),
);children.push(
blocks.createTable(
["Feature", "Option A", "Option B"], // headers
[
["Speed", "Fast", "Slow"], // rows
["Cost", "High", "Low"],
],
[3120, 3120, 3120], // col widths — must sum to 9360
),
);children.push(blocks.createSpacer()); // default 200
children.push(blocks.createSpacer(400)); // larger gapEvery module follows this pattern:
module.exports = (blocks, children) => {
// 1. Title
children.push(...blocks.createModuleTitle("Module N", "Title"));
children.push(blocks.createSpacer());
// 2. Learning Objectives
children.push(...blocks.createLearningObjectives([...]));
children.push(blocks.createSpacer());
// 3. Parts — repeat for each section
children.push(blocks.createPartHeading("1. Section Name"));
children.push(blocks.createBody("..."));
children.push(blocks.createSubHeading("Subsection"));
children.push(blocks.createBullet("..."));
children.push(...blocks.createCodeBlock(`...`));
children.push(...blocks.createInfoBox("💡 Tip", "..."));
children.push(...blocks.createWarningBox("⚠ Warning", "..."));
children.push(blocks.createTable(headers, rows));
children.push(blocks.createSpacer());
// 4. Interview Questions
children.push(blocks.createSubHeading("Interview Questions"));
children.push(blocks.createMinorHeading("Easy"));
children.push(blocks.createBullet("..."));
// 5. Assignment
children.push(blocks.createSubHeading("Assignment"));
children.push(blocks.createNumbered("..."));
// 6. Summary Checklist
children.push(blocks.createSubHeading("Module N Summary"));
children.push(blocks.createChecklistItem("..."));
// 7. Up Next
children.push(...blocks.createInfoBox("📘 Up Next", "Module N+1 — ..."));
};Edit framework/theme.js to change the look globally.
colors: {
h1: "1A3C5E", // Deep navy — Part headings
h2: "2E75B6", // Medium blue — Sub headings
h3: "2C3E50", // Dark slate — Minor headings
codeBg: "F0F4F8", // Code block background
infoBg: "F0F7FF", // Info box background
warningBg: "FFF8E1", // Warning box background
tableHeader: "2E75B6", // Table header row
tableRow: "EBF5FB", // Table alternating row
}
fonts: {
heading: "Arial",
body: "Arial",
code: "Courier New",
}- Add the function to
framework/blocks.js - Export it at the bottom
- Use it in
modules/current.js
If the function returns multiple paragraphs → return an array, use ...spread when calling.
If it returns one element → return directly, use plain push.
| Package | Purpose |
|---|---|
docx@^9.7.1 |
DOCX generation |
jszip@^3.x |
XML patching for Word navigation pane support |
See modules/current.js in the repo — it's a full showcase of every block component rendered in one document.