node-xml

A fast, minimal JavaScript object-to-XML string builder for Node.js projects.

Library
npm
v1.0.1
284stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
36/100Needs Attention
Development Activity0
Maintenance0
Community64
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
51/100Fair
Architecture55
Code Quality50
Innovation55
Learning Curve45

xml is a lightweight Node.js library that converts plain JavaScript objects into well-formed XML strings. It handles the common building blocks of XML generation - nested elements, attributes via a special _attr key, CDATA sections via a _cdata key, and custom indentation - without requiring a schema, template engine, or DOM implementation.

For larger documents, xml can emit output incrementally as a Node.js stream instead of building the entire string in memory, and it can optionally prepend an XML declaration with configurable encoding and standalone attributes. Its API surface is intentionally tiny - a single xml() function plus an element() helper for streaming - making it easy to drop into RSS/Atom feed generators, SOAP request builders, or any project that needs to produce XML from in-memory data structures.

What You Get

  • A single xml() function that converts objects/arrays into an XML string with automatic escaping of &, <, >, ', and ".
  • Attribute support via the reserved _attr key, letting you attach XML attributes to any element inline in the same object literal.
  • CDATA support via the reserved _cdata key so raw, unescaped content (such as embedded HTML) can be embedded safely.
  • Configurable indentation (a custom string, or true for a four-space default) for human-readable pretty-printed output.
  • A streaming mode (options.stream plus the element()/push()/close() helpers) for emitting large XML documents incrementally instead of building one giant string in memory.

Common Use Cases

  • Generating RSS/Atom feed XML from a list of post or item objects.
  • Building SOAP request/response bodies for legacy API integrations that don’t support JSON.
  • Serializing configuration or data-export files that must conform to XML rather than JSON.
  • Streaming very large XML payloads (e.g. bulk exports or sitemaps) to a client without buffering the whole document in memory.

Under The Hood

Architecture The library is a single small module (lib/xml.js) exporting one function xml() plus an element()/Element alias, backed by a separate lib/escapeForXML.js helper for character escaping. The core recursive logic lives in two closures-heavy functions: resolve(), which normalizes an input object into a descriptor tree of {name, attributes, content, indent}, and format(), which walks that tree and calls an append(interrupt, output) callback to build the final string or push stream chunks. Streaming is achieved by having element() return a mutable node whose push/close methods get wired into the main append/proceed closures via an interrupt() mechanism when format() encounters a partially-resolved sub-element. It is a monolithic, closure-heavy design with no classes or dependency injection - all state (output, stream, interrupted) is shared through closures inside the top-level xml() call, so extending the recursion (e.g. adding a new reserved key) requires understanding the whole resolve/format/interrupt state machine at once.

Tech Stack The package has zero runtime dependencies; its only devDependency is the ava test runner. It is written in plain CommonJS JavaScript with no transpiler, bundler, or TypeScript involved - package.json’s main field points straight at lib/xml.js. Streaming relies on Node’s legacy stream.Stream base class rather than the modern Readable/Transform APIs. CI is configured only via a .travis.yml file, consistent with a package that has seen no structural changes in years.

Code Quality A single test file (test/xml.test.js, ~150 lines) using AVA covers a good range of edge cases - null/empty values, attributes, CDATA escaping, multiple indentation styles, arrays of same-named elements, and nested attribute combinations. There is no TypeScript, no JSDoc type annotations, and no linter configuration in the repository. Error handling is minimal: element.push() throws a plain Error if called before being assigned a parent, but most of the resolve/format code silently coerces unexpected input types rather than validating them. Naming is short and terse (resolve, format, append, elem), typical of small utility modules from the early Node.js ecosystem.

API Design The public API is deliberately tiny: one function xml(data, options) for the common case, plus a parallel element()/push()/close() object for streaming, avoiding two large parallel class hierarchies. The _attr/_cdata reserved-key convention lets attributes and raw content be expressed directly in the same object literal used for element structure, which is a genuinely convenient choice for hand-written object literals. However, the options argument is overloaded - it accepts a boolean shorthand for indent, or a full options object - which isn’t self-documenting without reading the README. The README itself is solid, with a runnable example for every documented feature.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search