Desktop Application Development with NW.js
NW.js (formerly node-webkit) is a framework for desktop applications combining Node.js and Chromium in a single process. Unlike Electron with separated main and renderer, in NW.js Node.js API is available directly in the DOM context. This simplifies some scenarios but creates other security issues.
Key Difference from Electron
In Electron: renderer → IPC → main → Node.js API.
In NW.js: renderer directly calls require('fs'), require('path'), etc.
// In NW.js this works directly in browser code
const fs = require('fs');
const os = require('os');
document.getElementById('hostname').textContent = os.hostname();
fs.readFile('/etc/hosts', 'utf8', (err, data) => {
document.getElementById('hosts').textContent = data;
});
For small utility applications this is indeed more convenient. For complex applications — harder to reason about security.
Creating a Project
npm init -y
npm install nw --save-dev # or nw-builder for production builds
{
"name": "my-nw-app",
"main": "index.html",
"window": {
"title": "My Application",
"width": 1200,
"height": 800,
"min_width": 800,
"min_height": 600,
"icon": "icons/icon.png",
"frame": true,
"resizable": true
},
"nodejs": true,
"node-remote": "",
"chromium-args": "--enable-features=WebRTC-H264WithOpenH264FFmpeg"
}
The main field in package.json is the entry point — here it's an HTML file, not JS.
Project Structure
my-app/
├── package.json # NW.js configuration
├── index.html # main window
├── js/
│ ├── app.js # app logic
│ └── native.js # Node.js integration
├── css/
│ └── style.css
└── icons/
└── icon.png
Example: File Browser
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>File Browser</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div id="toolbar">
<button id="btn-open">Open Folder</button>
<span id="current-path"></span>
</div>
<div id="file-list"></div>
<script src="js/app.js"></script>
</body>
</html>
// js/app.js — Node.js API directly in renderer
const fs = require('fs');
const path = require('path');
const { dialog } = nw;
let currentPath = require('os').homedir();
function renderFiles(dirPath) {
document.getElementById('current-path').textContent = dirPath;
const list = document.getElementById('file-list');
list.innerHTML = '';
try {
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
entries.forEach(entry => {
const item = document.createElement('div');
item.className = `file-item ${entry.isDirectory() ? 'dir' : 'file'}`;
item.textContent = entry.name;
list.appendChild(item);
});
} catch (err) {
list.innerHTML = `<div class="error">${err.message}</div>`;
}
}
document.getElementById('btn-open').addEventListener('click', () => {
const input = document.createElement('input');
input.type = 'file';
input.setAttribute('nwdirectory', ''); // NW.js extension for folder selection
input.addEventListener('change', () => {
currentPath = input.value;
renderFiles(currentPath);
});
input.click();
});
renderFiles(currentPath);
Distribution Build
npm install nw-builder --save-dev
// build.js
const NwBuilder = require('nw-builder');
const nw = new NwBuilder({
files: ['./src/**/**', './package.json'],
version: '0.89.0',
platforms: ['win64', 'osx64', 'linux64'],
buildDir: './release',
macIcns: './icons/icon.icns',
winIco: './icons/icon.ico'
});
nw.build().then(() => {
console.log('Build complete');
}).catch(console.error);
NW.js Versions: Normal vs SDK
NW.js available in two versions:
- Normal — for production, no DevTools by default, smaller size
- SDK — with DevTools, for development and debugging
When NW.js Makes Sense
Consider NW.js for: porting existing web applications without rearchitecting, quick internal tools where security model is less critical, projects where team previously worked with node-webkit.
For new production applications, Electron or Tauri have more active communities, better documentation, and more predictable security models.







