← Build · Back to README · Build Notes →
Twenty self-contained Delphi applications demonstrating real-world use of DelphiLibNodeJS.
Each example is a Delphi console application with an accompanying JS script. All examples use the shared helper unit ExHelpers.pas.
# Build all examples
.\examples\BuildExamples.ps1
# Run any example
.\examples\02_bidirectional\Win64\Release\ExBidirectional.exe
.\examples\07_bridge\Win64\Release\ExBridge.exe
.\examples\17_generic_handler\Win64\Release\ExGenericHandler.exeEach Win64\Release\ directory contains the .exe and accompanying .js/.json files.
The DLL is located in build\dll\ and is resolved automatically via GetDllPath in ExHelpers.pas.
| Requirement | Note |
|---|---|
DelphiLibNodeJS.dll |
Build via scripts\BuildHostDll.ps1 |
| RAD Studio 12+ | Examples 1–19 (example 14 — DCC64 + WebView2) |
npm in PATH |
Required only for examples 1, 5, 6, 8, 9, 10 |
| MySQL server | Required only for examples 5 and 8 |
| Internet (first run) | npm downloads packages; example 6 uses OpenStreetMap CDN |
Shared node_modules: examples 1, 5 and 6 use the shared directory examples\common\node_modules.
| # | Directory | Demonstrates |
|---|---|---|
| 0 | 00_basic |
Smoke test: factory/runtime/log/QI/PathToFileUrl |
| 1 | 01_templating |
npm install + Handlebars HTML report from JSON data |
| 2 | 02_bidirectional |
Bidirectional RPC via RunScriptText — no JS files on disk |
| 3 | 03_pipeline |
ESM entry script, top-level await, Promise.all, crypto/perf_hooks |
| 4 | 04_callback |
Push-callback mode, CRITICAL_SECTION, Win32 event — async DB simulation |
| 5 | 05_mysql |
npm install mysql2, connect to MySQL, display tables and data |
| 6 | 06_gis |
npm install @turf/turf, K-nearest cities, convex hull, K-Means, Leaflet HTML map |
| 7 | 07_bridge |
INodeBridge — bidirectional Delphi↔JS calls, Direction A + B |
| 8 | 08_geodata |
INodeBridge + mysql2 + d3-node: load MySQL spatial data, render SVG |
| 9 | 09_osm |
Overpass API, d3-node SVG, admin boundaries + city labels |
| 10 | 10_vectortiles |
node:sqlite + proj4, HTTP server, OpenLayers GeoJSON map |
| 11 | 11_embedded_npm |
INodeNpmRunner — embedded npm without system npm |
| 12 | 12_bidir_metrics |
Direction B live-pull: JS reads Delphi metrics synchronously |
| 13 | 13_async_calls |
PostFunction — non-blocking JS calls from Delphi |
| 14 | 14_webbrowser |
VCL + TEdgeBrowser (WebView2), bidirectional bridge with UI |
| 15 | 15_marshal |
Full Automation type matrix through the bridge |
| 16 | 16_inspect_repl |
V8 Inspector (CDP), Evaluate, IDNCallProxy typed calls |
| 17 | 17_generic_handler |
BridgeRegisterWorkerHandler, TDNJson, IDNMainThreadLog, IDNRuntimeSession, WithEnvPair |
| 18 | 18_dispatch_proxy |
TDNDispatchObject, BridgeRegisterWorkerDispatch, TDNJson.ArgObj, IDNAsyncCallProxy (PostStr/PostFloat) |
| 19 | 19_binary_transfer |
Binary transfer v3.2.1: CallFunctionWithBuffer, CallFunctionWithStream, CallFunctionWithDataObject, PostFunctionWithBuffer, AllocSharedBuffer/IDNSharedBufferHandle |
What it demonstrates: minimal DLL health check: factory/runtime creation, log polling, QI, PathToFileUrl.
Key APIs: DN_CreateFactory, INodeFactory.CreateRuntime, INodeRuntime.Start/Shutdown, INodeLog.Poll, DN_PathToFileUrl
What it demonstrates: how to run npm install, load the Handlebars templating engine, and get an HTML report from Delphi.
Delphi (ExTemplating.dpr):
- Checks for shared
node_modules, installs handlebars if needed - Runs
render.js, polls the log viaINodeLog.Poll, collects HTML strings - Saves to
ExTemplating_out.html
JavaScript (render.js):
- Handlebars CJS renderer with 5 custom helpers (currency, revenue, pct, ifEven, bar)
- Reads
report-data.json(10 products, Q4 2024 sales) - Logs each HTML string with the
[RENDER]prefix
Key APIs: INodeNpm.InstallFromPackageJson, INodeLog.Poll, PollCollect
What it demonstrates: bidirectional data exchange, embedded JS scripts (no files on disk), keep-alive pattern.
Highlights:
- All JS scripts are embedded as Delphi
stringconstants — no.jsfiles - Keep-alive pattern:
setIntervalkeeps the event loop alive - 5 JS commands injected via
RunScriptTextafterStart - JSON request/response, regex text processing, async timers, graceful shutdown
Key APIs: INodeRuntime.RunScriptText, globalThis persistence between injections
This example is the best starting point for learning the API. Requires no npm and no files.
What it demonstrates: ESM modules (.mjs), top-level await, parallel execution via Promise.all, standard Node.js modules.
JavaScript (pipeline.mjs):
- ESM with
top-level awaitandimport Promise.allfor parallel document processing- SHA-256 hashes, word statistics, lexical density
- Uses
node:crypto,node:os,node:perf_hooks
Key APIs: DN_MODULE_FORMAT_AUTO, ESM auto-detect, INodeLog.Poll
What it demonstrates: push-mode logging, thread-safe callback, Win32 synchronization.
JavaScript (multidb.js):
- 6-stage async DB simulation: schema → users → orders → JOIN → dept aggregates → top customers
- Seeded RNG,
tick()yields event loop between stages
Delphi (ExCallback.dpr):
TCallbackCtxwithTRTLCriticalSection+TStringList+ Win32 manual-reset event- Callback registered via
INodeLog.SetCallbackbeforeStart WaitForSingleObjectwaits for JS work to complete
Key APIs: INodeLog.SetCallback, thread safety, WaitForSingleObject
⚠️ The callback is called on the Node.js event loop thread — always wrap the body intry..except.
What it demonstrates: connecting to a real database, passing configuration via a JSON file, interactive password prompt.
JavaScript (mysql_viewer.js):
mysql2/promise— async/await APISHOW DATABASES,SHOW TABLES,SHOW COLUMNS,SELECT COUNT(*)in parallelSELECT * LIMIT 5with ASCII box-table formatting
Key APIs: INodeNpm, JSON config file pattern, INodeLog.Poll
A running MySQL server is required for this example.
What it demonstrates: spatial analysis, K-nearest neighbours, interactive HTML map generation.
JavaScript (gis_processor.js):
- 89 world cities with lat/lon coordinates and population
@turf/turf:turf.distance,turf.convex,turf.clustersKmeans- Generates interactive HTML with Leaflet + OpenStreetMap
Key APIs: INodeNpm, INodeLog.Poll, ShellExecute
What it demonstrates: full bidirectional bridge between Delphi and JavaScript via INodeBridge.
Direction A (Delphi → JS): INodeBridge.CallFunction — call a JS function by name with JSON arguments.
Direction B (JS → Delphi): register a Delphi object via TDNExport.Wrap — JS calls Delphi methods as bridge.ns.method(...).
Key APIs: INodeBridge, CallFunction, RegisterExport, TDNCallProxy, TDNExport.Wrap, IDNMainDispatcher
What it demonstrates: INodeBridge.CallFunction (Direction A) — passing MySQL configuration from Delphi to JS; loading spatial data; rendering SVG without a browser.
Key APIs: INodeBridge.CallFunction, TDNCallProxy, d3-node, mysql2/promise
A MySQL server with a geometry table is required for this example.
What it demonstrates: fetching OpenStreetMap data via the Overpass API, SVG rendering via d3-node.
JavaScript (osm_service.js):
query-overpass— Overpass API queriesd3-nodegeoMercator — renders admin boundaries + city labels- Saves SVG, signals
[OSM:done]
Key APIs: INodeBridge.CallFunction, TDNCallProxy, ShellExecute
What it demonstrates: embedded HTTP server in Node.js, SQLite (node:sqlite), coordinate projection (proj4), OpenLayers map.
JavaScript (geo_tiles.js):
node:sqlite— built-in SQLite (no npm required)proj4— UTM40N → WGS84 coordinate conversionwkt— WKT geometry parsing- HTTP server serves GeoJSON and HTML with OpenLayers
Delphi (ExVectorTiles.dpr):
- Starts the HTTP server via
Bridge.CallFunction('startServer') - Opens browser via
ShellExecute - Stops the server via
Bridge.CallFunction('stopServer')on exit
Key APIs: INodeBridge.CallFunction, node:sqlite, HTTP server from Delphi
What it demonstrates: INodeNpmRunner — embedded npm without needing a globally installed npm.
Delphi (ExEmbeddedNpm.dpr):
- Gets
INodeNpmRunnerviaFactory.GetNpmRunner - Installs lodash into the working directory
- Runs
data_processor.js
Key APIs: INodeNpmRunner, INodeNpmRunner.Install, INodeNpmRunner.Run
What it demonstrates: "live data pull" pattern — JS synchronously reads Delphi metrics via Direction B at the time of each Direction A call.
Delphi (ExBidirMetrics.dpr):
TServerMetrics— published object with CPU/Memory/Users/Errors metricsBridgeRegisterWorker(Bridge, 'srv', FMetrics)— Direction B (no parameters)- Three scenarios: Baseline → Load spike → Recovery
- Direction A:
Proxy.Call('snapshot'),getHealthScore(),getAlert()
Key APIs: BridgeRegisterWorker, IDNCallProxy, TDNExport.Wrap
What it demonstrates: INodeBridge.PostFunction — non-blocking JS calls from Delphi.
Delphi (ExAsyncCalls.dpr):
BridgePostFunctionOnMainClosure— anonymous callback on the main threadIDNMainDispatcher— hidden window forWM_DN_BRIDGE_RESULT- Three parallel calls:
greetAsync,computeAsync,failAsync - Message pump waits for three callbacks
Key APIs: PostFunction, BridgePostFunctionOnMainClosure, IDNMainDispatcher
What it demonstrates: VCL GUI with TEdgeBrowser (WebView2), bidirectional bridge via window.chrome.webview.postMessage.
Note: DCC64 only (RAD Studio), requires WebView2Loader.dll.
Key APIs: TEdgeBrowser, INodeBridge, TDNExport.Wrap, Direction A + B
What it demonstrates: full Automation type matrix through the bridge in both directions.
Direction B → Delphi: JS calls bridge.types.getXxx() — each method returns a distinct type:
Integer (VT_I4) → Int64 (VT_I8, BigInt) → Double (VT_R8) → Currency (VT_CY) → TDateTime (VT_DATE) → Boolean (VT_BOOL) → WideString (VT_BSTR) → OleVariant → SAFEARRAY.
Direction A → JS: Delphi calls marshalArgs() with the full set of types, including {"$bigint":"..."} for Int64.
Also demonstrates: IDNArgs, IDNCallProxy, TDNResult, TDNSafeArrayBuilder.
Key APIs: TDNSafeArrayBuilder, IDNArgs, IDNCallProxy, TDNResult, full VT_* matrix
What it demonstrates: V8 Inspector (Chrome DevTools Protocol) and INodeBridge.Evaluate.
Delphi (ExInspectRepl.dpr):
Runtime.EnableInspector(nil, 9229, False)— opens WebSocket debug port beforeStartBridgeEvaluate(Bridge, expr)— evaluate JS expressions withoutINodeCallResultboilerplateIDNCallProxy.Evaluate* / Call*— typed variants (EvaluateInt, CallStr, ...)IDNArgsfluent builder — builds a JSON argument array without string concatenationTDNResultstatic extractors — decode JSON result into typed Delphi valuesCallResultError / CallResultErrorName / CallResultStack— read error details
Key APIs: EnableInspector, Evaluate, IDNCallProxy, IDNArgs, TDNResult
chrome://inspect → Configure → 127.0.0.1:9229
What it demonstrates: a set of high-level V3 APIs in a single example: BridgeRegisterWorkerHandler, TDNJson, IDNMainThreadLog, IDNRuntimeSession, WithEnvPair, RuntimeStateName.
Delphi (ExGenericHandler.dpr):
-
TDNRuntimeConfigBuilder.WithEnvPair— injects environment variables into the Node.js process:RtCfg := TDNRuntimeConfigBuilder.Default .WithScriptsPath(ScriptDir) .WithModuleFormat(DN_MODULE_FORMAT_COMMONJS) .WithEnvPair('SERVICE_NAME', 'generic-handler-demo') .WithEnvPair('SERVICE_VER', '1.0');
-
CreateDNRuntimeSession— replaces the four-step initialization (factory → runtime → QI → Start) with a single call:Session := CreateDNRuntimeSession(RtCfg); // Session.Bridge, Session.Log, Session.Runtime, Session.Npm are ready -
RuntimeStateName— human-readable state name:if Session.Runtime.GetState(State) = S_OK then WriteLn(RuntimeStateName(State)); // → 'RUNNING'
-
IDNMainThreadLog / CreateDNMainThreadLog— delivers log entries to the main thread via a hiddenHWND_MESSAGEwindow:MainLog := CreateDNMainThreadLog(Session.Log, App.OnLog); // App.OnLog is called on the main thread for each console.log from JS -
BridgeRegisterWorkerHandler— registers a Direction B handler as an anonymous procedure (without a published class):BridgeRegisterWorkerHandler(Session.Bridge, 'calc', HandleCalc); BridgeRegisterWorkerHandler(Session.Bridge, 'store', HandleStore);
-
TDNJson— parse arguments and build results inside a handler:procedure HandleCalc(const Method, ArgsJson: string; var ResultJson: string); var Args: TJSONArray; begin Args := TDNJson.ParseArray(ArgsJson); try if Method = 'add' then ResultJson := TDNJson.ResultInt( TDNJson.ArgInt(Args, 0) + TDNJson.ArgInt(Args, 1)) else if Method = 'divide' then if TDNJson.ArgInt(Args, 1) = 0 then ResultJson := TDNJson.Error('Division by zero') // raises JS Error on the JS side ... finally Args.Free; end; end;
JavaScript (handler_service.js):
- Reads
process.env.SERVICE_NAMEandSERVICE_VER - Calls
bridge.calc.add/mul/greet/divideandbridge.store.set/get - Demonstrates error catching via
try/catchfordivide(10, 0) - Signals
[DONE]on completion
Sample output:
[state] After CreateDNRuntimeSession: RUNNING
[delphi] Registered bridge.calc and bridge.store
[delphi] handler_service.js queued to event loop
[log] [env] SERVICE_NAME = generic-handler-demo
[log] [env] SERVICE_VER = 1.0
[log] [calc] add(10, 32) = 42
[log] [calc] mul(6, 7) = 42
[log] [calc] greet = Hello, World!
[log] [calc] divide(10, 0) caught: Division by zero
[log] [store] get("user") = alice
[log] [store] get("role") = admin
[log] [store] get("nope") = null
[log] [DONE]
[state] After Shutdown: STOPPED
Example 17 completed successfully.
Files: examples\18_dispatch_proxy\ExDispatchProxy.dpr, dispatch_service.js
Demonstrates all four V3.x NodeBridgeHelpers improvements:
| Feature | Where used |
|---|---|
TDNDispatchObject — abstract base class |
TProductCatalog.DoDispatch |
BridgeRegisterWorkerDispatch — one-line registration |
BridgeRegisterWorkerDispatch(Bridge, 'catalog', Catalog) |
TDNJson.ArgObj / ArgObjStr / ArgObjInt — named arguments |
search({query:'node', limit:3}) |
IDNAsyncCallProxy.PostStr / PostFloat — typed async Direction A |
Proxy.PostStr('getRecommendations', ...) |
What the example does:
- Creates
TProductCatalog : TDNDispatchObjecthandling methods:search({query, limit})— filter by name, named arguments viaTDNJson.ArgObjStr/IntgetById(id)— by product IDgetSummary()— count + average price
- Registers the object as
bridge.catalogviaBridgeRegisterWorkerDispatch - Runs
dispatch_service.js, which calls all three Direction B methods - Creates
IDNAsyncCallProxyand makes two Direction A calls:getRecommendations('books')→PostStr→ string callbackcomputeDiscount(100, 15)→PostFloat→ Double callback
- Collects results via the message loop, sends
__doneCbsignal to terminate JS
[delphi] Registered bridge.catalog (TProductCatalog)
[delphi] dispatch_service.js queued
[delphi] --- Direction A: IDNAsyncCallProxy ---
[delphi] 2 async calls posted, pumping message loop...
[async] getRecommendations("books") → Clean Code, SICP, The Pragmatic Programmer
[async] computeDiscount(100, 15%) → 85.00
Example 18 completed successfully.
Added in v3.2.1. Files:
examples\19_binary_transfer\ExBinaryTransfer.dpr,binary_service.js
Demonstrates all binary APIs added in v3.2.1: TBytes ↔ Uint8Array transfer through the bridge.
| Demo | API | Description |
|---|---|---|
| A | CallFunctionWithBuffer |
Send TBytes as Uint8Array, receive TBytes back |
| B | CallFunction + CallResultBytes / GetStream |
Receive binary result as TBytes or IStream |
| C | CallFunctionWithStream |
Send data via IStream (BytesToStream) |
| D | AllocSharedBuffer + IDNSharedBufferHandle |
Allocate a V8 SharedArrayBuffer, write from Delphi, read from JS |
| E | PostFunctionWithBuffer |
Async buffer send, result via callback |
JavaScript (binary_service.js):
invertBytes(buf)— XOR each byte with0xFF, returnsUint8ArraymakePng(w, h)— synthetic 24-byte PNG header (for GetBytes/GetStream verification)echoBuffer(buf)— echo buffer (async, for PostFunctionWithBuffer)processImage(meta, buf)— named arguments:{operation: 'invert'}or{operation: 'sum'}readShared(name)— readsglobalThis.__dn_buffers[name], returns JSON byte array
Key points:
AllocSharedBuffermust be called afterRuntime.Start(requires a live V8 isolate)- SAB layout:
[4-byte Int32 control word][payload]; Delphi writes viaSABWriteBytes, JS reads viaUint8Arrayview - After using SAB:
Handle.Invalidateremoves it fromglobalThis.__dn_buffers,Handle := nilreleases the backing store CallResultBytes/StreamToBytes/BytesToStream— high-level helpers inNodeBridgeHelpers.pas
Sample output:
Example 19 — Binary Data Transfer (v3.2.1)
Demo A — CallFunctionWithBuffer (invertBytes)
Input: 01 02 03 04
Output: FE FD FC FB
PASS
Demo B — GetBytes + GetStream (makePng)
Length: 24 bytes
Header (first 8): 89 50 4E 47 0D 0A 1A 0A
PASS — PNG magic verified
GetStream: PASS (same 24 bytes)
Demo C — CallFunctionWithStream
Input: 10 20 30 40
Output: EF DF CF BF
PASS
Demo D — AllocSharedBuffer / IDNSharedBufferHandle
AllocSharedBuffer + SABWriteBytes([DE AD BE EF]) — OK
readShared("signature") = [222,173,190,239]
PASS — values 0xDE(222) and 0xAD(173) found
Demo E — PostFunctionWithBuffer (async)
Received: AA BB CC
PASS
ALL BINARY TRANSFER DEMOS PASSED
| Function | Purpose |
|---|---|
GetDllPath |
Return the path to DelphiLibNodeJS.dll relative to the .exe |
PollForMessage |
Poll the log with a timeout, return the first matching message |
PollCollect |
Collect all messages with a given prefix up to a done-marker |
MakeTempDir |
Create a unique temporary directory |
WriteFileAnsi |
Write raw bytes (UTF-8 JS/JSON) to a file |
GetCommonDir |
Return the path to the shared examples\common directory |
Banner, Sep |
Console formatting helpers |
LevelChar |
Convert DN_LOG_* level to a display character |
# With RAD Studio (DCC64)
call "D:\Embarcadero RAD Studio\23.0\bin\rsvars.bat"
dcc64 -U"%BDS%\lib\win64\release;<repo>\source;<repo>\examples" ^
-E"examples\17_generic_handler\Win64\Release" ^
-NSSystem;Winapi ^
examples\17_generic_handler\ExGenericHandler.dpr- Getting Started — installing and running the DLL
- API Reference — full interface reference
- INodeBridge Guide — bidirectional bridge developer guide
- Build from Source — how to build the DLL