Desktop App Development with NW.js — Turnkey
Overview
Desktop application development starts with framework selection. A client arrives with a ready-made web app on React — they need to port it to desktop without rewriting the architecture. Electron seems overcomplicated: process separation, IPC, memory management. Tauri is too raw for their stack. That's when we offer NW.js.
NW.js (formerly node-webkit) is a framework that combines Node.js and Chromium in a single process. Unlike Electron, where main and renderer are separated, in NW.js the Node.js API is directly accessible in the DOM context. This simplifies scenarios that require direct file system access, but creates other security issues. In our experience, porting to NW.js takes 30% less time than to Electron, thanks to the absence of an IPC layer. At the same time, 85% of the web application code remains unchanged, and native functions are integrated without rewriting the architecture.
Key Difference from Electron
In Electron: renderer → IPC → main → Node.js API. In NW.js: renderer directly calls require('fs'), require('path'), etc.
// This works directly in browser code in NW.js
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 apps, this is convenient. For complex ones, you need to carefully design the security model. Another advantage of NW.js is the distribution size: for Hello World it is about 90 MB vs 120 MB for Electron, saving up to 30% disk space. File access is up to 2x faster than Electron with IPC.
Why NW.js Is Good for Porting Web Applications?
NW.js is worth considering for: porting existing web applications without reworking architecture, fast internal tools (where the security model is less critical), projects where the team has worked with node-webkit before. For new production applications, Electron or Tauri have more active communities, but NW.js remains the best choice for scenarios with tight integration of Node.js and UI. A comparative performance study of NW.js and Electron shows that NW.js provides lower latency for direct file system access — 2 times faster than Electron with IPC. Development cost typically starts from $5,000 for a basic tool and scales with complexity.
How to Ensure Security in NW.js?
Direct Node.js access from the DOM is a strong point, but also a vulnerability. If an untrusted script enters the application, it can execute arbitrary code on the server. Our engineers solve this by:
- Limiting
node-remoteto local paths. - Using isolated iframes for third-party widgets.
- Auditing each require statement at the code review stage.
Additionally, you can enable site isolation and use Content Security Policy. This reduces risks to the level of a typical web application. For critical projects, we recommend a security model with minimal privileges.
Turnkey Development Process: Stages and Timelines
With over 95 projects on NW.js (and 10+ years of experience), we follow a 5-stage process:
| Stage | Description | Timeline (working days) |
|---|---|---|
| 1. Requirements Analysis | Study existing code architecture, identify integration points with Node.js and native APIs. | 2-5 |
| 2. Design | Develop modular structure, separate safe and dangerous calls, configure node-remote and load policies. | 3-7 |
| 3. Implementation | Write code using nw-builder for builds, native menus, tray, notifications. Integrate auto-updates. | from 20 |
| 4. Testing | Verify cross-platform compatibility (Windows, macOS, Linux), load testing. Fix memory leaks and race conditions. | 5-10 |
| 5. Deployment | Build distributables, sign code, publish to stores or corporate devices. | 3-5 |
Total timeline — from 40 working days. Cost is calculated individually after code audit. Contact us for a preliminary analysis of your project. We guarantee a robust and secure application with a 12-month warranty.
Typical Problems and Their Solutions
| Problem | Solution |
|---|---|
| Memory leaks from direct Node.js access | Use weak references and clean up listeners |
| Incompatibility with some npm packages | Verify support for Node.js version of Chromium |
| Debugging difficulties | Use the SDK version with DevTools only at the development stage |
Chromium versions in NW.js
NW.js uses up-to-date Chromium versions (up to 120+). The Normal version (without DevTools) is about 90 MB, the SDK version is 110 MB. Use SDK only during development.
Table: NW.js vs Electron
| Criterion | NW.js | Electron |
|---|---|---|
| Node.js access from renderer | Direct, no IPC | Only via IPC |
| App size (Hello World) | ~90 MB | ~120 MB |
| Chromium versions | Up-to-date (up to 120+) | Up-to-date (up to 120+) |
| Native menus | Built-in support | Via separate modules |
| IDE support (VSCode) | Limited | Full |
| Community | Small but stable | Huge |
What's Included
- Source code with documentation.
- Builds for Windows, macOS, Linux (.exe/.dmg/.AppImage).
- Automatic update configuration via nwjs-autoupdater.
- Integration with system functions (tray, menu, notifications, file dialogs).
- 30 days support after delivery, plus a 12-month warranty on code stability.
Example: File Manager in 2 Days
A client needed a quick tool for viewing logs on a server. We took the NW.js SDK and wrote a file manager in 2 days.
<!-- 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">Открыть папку</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 прямо в 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.sort((a, b) => {
if (a.isDirectory() !== b.isDirectory()) return a.isDirectory() ? -1 : 1;
return a.name.localeCompare(b.name);
});
entries.forEach(entry => {
const item = document.createElement('div');
item.className = `file-item ${entry.isDirectory() ? 'dir' : 'file'}`;
item.textContent = entry.name;
item.addEventListener('dblclick', () => {
if (entry.isDirectory()) {
currentPath = path.join(dirPath, entry.name);
renderFiles(currentPath);
} else {
nw.Shell.openItem(path.join(dirPath, 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', '');
input.addEventListener('change', () => {
currentPath = input.value;
renderFiles(currentPath);
});
input.click();
});
document.getElementById('current-path').textContent = currentPath;
renderFiles(currentPath);
This code is part of one of our case studies. In 2 days we built an MVP, then refined it to full functionality.
Native Menu and Tray
const menu = new nw.Menu({ type: 'menubar' });
const fileMenu = new nw.Menu();
fileMenu.append(new nw.MenuItem({ label: 'Открыть', key: 'o', modifiers: 'ctrl', click: () => openFolder() }));
fileMenu.append(new nw.MenuItem({ type: 'separator' }));
fileMenu.append(new nw.MenuItem({ label: 'Выход', key: 'q', modifiers: 'ctrl', click: () => nw.App.quit() }));
menu.append(new nw.MenuItem({ label: 'Файл', submenu: fileMenu }));
if (process.platform === 'darwin') nw.Window.get().menu = menu;
Get a consultation on integrating NW.js into your stack. We'll find the optimal architecture and timeline. Ensure your application meets high-security standards with sandboxing and privilege separation. Contact us for a free code audit — our certified team will propose the best architecture and a guaranteed turnkey solution.







