IIS is a way to setup web servers on windows. mostly used when you want to run asp.net with a a domain name instead of a random port. IISNode can be used to run node.js within IIS, so the frontend can have a domain name as well while testing. which can also be frameworks like webpack, vite and next-js. with on-filechange re-rendering. For other languages, like go and python, the httpplatformhandler is recommended. Which can be downloaded here.
For iisnode to work you need to enable the Application Development Features within IIS:

The IIS-site can automatically be created or updated via PowerShell, this example creates a IIS-site on the current location:
# https://learn.microsoft.com/en-us/powershell/module/webadministration/new-website?view=windowsserver2025-ps
$name = "gametools-website-v1" # update version number on changes
$loc = Get-Location # current location
# Check if a site exist with the current path
$has_site = Get-Website | Select-Object PhysicalPath, Name | Where-Object PhysicalPath -eq $loc.path
# Check if the name of the site has a specific name
if ($has_site -and $has_site.Name -ne $name) {
Write-Output "Removed old site"
Remove-Website -Name $has_site.Name
}
# Check if the site still exists
$has_site = Get-Website | Select-Object PhysicalPath, Name | Where-Object PhysicalPath -eq $loc.path
# Otherwise recreate
if ( !$has_site )
{
New-WebSite -Name $name -Port "80" -HostHeader "gametools.localhost" -PhysicalPath $loc.path
}The settings for the created IIS-site can be changed with a web.config file, where you can setup IISNode. This file has to be placed at the entry point of the site:
<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="server.js" verb="*" modules="iisnode" />
</handlers>
<rewrite>
<rules>
<rule name="myapp">
<match url=".*" />
<!-- optional, allow access to the iisnode folder -->
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_URI}" matchType="Pattern" pattern="iisnode/(.*)" negate="true" />
</conditions>
<!-- optional -->
<action type="Rewrite" url="server.js" />
</rule>
</rules>
</rewrite>
<iisnode
nodeProcessCommandLine=""C:\Program Files\nodejs\node.exe""
interceptor=""%programfiles%\iisnode\interceptor.js""
loggingEnabled="true"
debuggingEnabled="true"
logDirectory="iisnode" />
</system.webServer>
</configuration>After that a server.js can be used as the entry point of the nodejs instance, attached to iisnode. The entrypoint has to use a named pipe as port, which both http and express support. Here are a few versions for different frameworks, with the change to http or express to meet that requirement:
server.js: https://vite.dev/guide/api-javascript.html#createserver
import express from 'express'
import { createServer as createViteServer } from 'vite'
async function createServer() {
const app = express()
// Create Vite server in middleware mode
const vite = await createViteServer({
server: { middlewareMode: true },
})
// Use vite's connect instance as middleware
app.use(vite.middlewares)
app.listen(process.env.PORT, () => console.log(`Example app listening on port ${process.env.PORT}!`))
}
createServer()server.js: https://nextjs.org/docs/pages/building-your-application/configuring/custom-server
import { createServer } from "http";
import { parse } from "url";
import next from "next";
const port = process.env.PORT;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer((req, res) => {
const parsedUrl = parse(req.url, true);
handle(req, res, parsedUrl);
}).listen(port);
console.log(
`> Server listening at ${port} as ${
dev ? "development" : process.env.NODE_ENV
}`,
);
});server.js: https://webpack.js.org/api/webpack-dev-server/
const express = require('express'); //your original BE server
const app = express();
const webpack = require('webpack');
const webpackConfig = require('./configs/webpack/dev.js');
const middleware = require('webpack-dev-middleware'); //webpack hot reloading middleware
const compiler = webpack(webpackConfig); //move your `devServer` config from `webpack.config.js`
app.use(middleware(compiler, {
// webpack-dev-middleware options
}));
app.listen(process.env.PORT, () => console.log(`Example app listening on port ${process.env.PORT}!`))
The iisnode can also be attached to the debugger of vscode according to link. after appending --inspect behind the nodeProcessCommandLine within the iisnode.yml, mentioned below.
.vscode\launch.json:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Debug IISNode",
"address": "127.0.0.1",
"port": 9229,
"localRoot": "${workspaceFolder}",
"remoteRoot": "${workspaceFolder}"
}
]
}Further IIS-Node changes can be made by creationg a iisnode.yml config file, it will override all iisnode settings set within web.config:
# # The optional iisnode.yml file will override all iisnode configuration settings specified in web.config.
# # node_env - determines the environment (production, development, staging, ...) in which
# # child node processes run; if nonempty, is propagated to the child node processes as their NODE_ENV
# # environment variable; the default is the value of the IIS worker process'es NODE_ENV
# # environment variable
# node_env:
# # nodeProcessCommandLine - command line starting the node executable; in shared
# # hosting environments this setting would typically be locked at the machine scope.
nodeProcessCommandLine: "C:\Users\zjobse\AppData\Local\nvm\v23.11.0\node.exe" --inspect
>iisnode nodeProcessCommandLine=”C:\Program Files\nodejs\node.exe –inspect”<
# # interceptor - fully qualified file name of a node.js application that will run instead of an actual application
# # the request targets; the fully qualified file name of the actual application file is provided as the first parameter
# # to the interceptor application; default interceptor supports iisnode logging
interceptor: "%programfiles%\iisnode\interceptor.js"
# # nodeProcessCountPerApplication - number of node.exe processes that IIS will start per application;
# # setting this value to 0 results in creating one node.exe process per each processor on the machine
nodeProcessCountPerApplication: 1
# # maxConcurrentRequestsPerProcess - maximum number of reqeusts one node process can
# # handle at a time
maxConcurrentRequestsPerProcess: 1024
# # maxNamedPipeConnectionRetry - number of times IIS will retry to establish a named pipe connection with a
# # node process in order to send a new HTTP request
maxNamedPipeConnectionRetry: 100
# # namedPipeConnectionRetryDelay - delay in milliseconds between connection retries
namedPipeConnectionRetryDelay: 250
# # maxNamedPipeConnectionPoolSize - maximum number of named pipe connections that will be kept in a connection pool;
# # connection pooling helps improve the performance of applications that process a large number of short lived HTTP requests
maxNamedPipeConnectionPoolSize: 512
# # maxNamedPipePooledConnectionAge - age of a pooled connection in milliseconds after which the connection is not reused for
# # subsequent requests
maxNamedPipePooledConnectionAge: 30000
# # asyncCompletionThreadCount - size of the IO thread pool maintained by the IIS module to process asynchronous IO; setting it
# # to 0 (default) results in creating one thread per each processor on the machine
asyncCompletionThreadCount: 0
# # initialRequestBufferSize - initial size in bytes of a memory buffer allocated for a new HTTP request
initialRequestBufferSize: 4096
# # maxRequestBufferSize - maximum size in bytes of a memory buffer allocated per request; this is a hard limit of
# # the serialized form of HTTP request or response headers block
maxRequestBufferSize: 65536
# # watchedFiles - semi-colon separated list of files that will be watched for changes; a change to a file causes the application to recycle;
# # each entry consists of an optional directory name plus required file name which are relative to the directory where the main application entry point
# # is located; wild cards are allowed in the file name portion only; for example: "*.js;node_modules\foo\lib\options.json;app_data\*.config.json"
# watchedFiles: *.js;iisnode.yml
# # uncFileChangesPollingInterval - applications are recycled when the underlying *.js file is modified; if the file resides
# # on a UNC share, the only reliable way to detect such modifications is to periodically poll for them; this setting
# # controls the polling interval
# uncFileChangesPollingInterval: 5000
# # gracefulShutdownTimeout - when a node.js file is modified, all node processes handling running this application are recycled;
# # this setting controls the time (in milliseconds) given for currently active requests to gracefully finish before the
# # process is terminated; during this time, all new requests are already dispatched to a new node process based on the fresh version
# # of the application
# gracefulShutdownTimeout: 60000
# # loggingEnabled - controls whether stdout and stderr streams from node processes are captured and made available over HTTP
# loggingEnabled: true
# # logDirectory - directory name relative to the main application file that will store files with stdout and stderr captures;
# # individual log file names have unique file names; log files are created lazily (i.e. when the process actually writes something
# # to stdout or stderr); an HTML index of all log files is also maintained as index.html in that directory;
# # by default, if your application is at http://foo.com/bar.js, logs will be accessible at http://foo.com/iisnode;
# # SECURITY NOTE: if log files contain sensitive information, this setting should be modified to contain enough entropy to be considered
# # cryptographically secure; in most situations, a GUID is sufficient
# logDirectory: iisnode
# # debuggingEnabled - controls whether the built-in debugger is available
debuggingEnabled: true
# # debuggerPortRange - range of TCP ports that can be used for communication between the node-inspector debugger and the debugee; iisnode
# # will round robin through this port range for subsequent debugging sessions and pick the next available (free) port to use from the range
debuggerPortRange: 5058-6058
# # debuggerPathSegment - URL path segment used to access the built-in node-inspector debugger; given a node.js application at
# # http://foo.com/bar/baz.js, the debugger can be accessed at http://foo.com/bar/baz.js/{debuggerPathSegment}, by default
# # http://foo.com/bar/baz.js/debug
# debuggerPathSegment: debug
# # debugHeaderEnabled - boolean indicating whether iisnode should attach the iisnode-debug HTTP response header with
# # diagnostics information to all responses
# debugHeaderEnabled: false
# # maxLogFileSizeInKB - maximum size of a single log file in KB; once a log file exceeds this limit a new log file is created
# maxLogFileSizeInKB: 128
# # maxTotalLogFileSizeInKB - maximum total size of all log files in the logDirectory; once exceeded, old log files are removed
# maxTotalLogFileSizeInKB: 1024
# # maxLogFiles - maximum number of log files in the logDirectory; once exceeded, old log files are removed
# maxLogFiles: 20
# devErrorsEnabled: true
# # flushResponse - controls whether each HTTP response body chunk is immediately flushed by iisnode; flushing each body chunk incurs
# # CPU cost but may improve latency in streaming scenarios
# flushResponse: false
# # enableXFF - controls whether iisnode adds or modifies the X-Forwarded-For request HTTP header with the IP address of the remote host
# enableXFF: false
# # promoteServerVars - comma delimited list of IIS server variables that will be propagated to the node.exe process in the form of
# # x-iisnode-<server_variable_name>
# # HTTP request headers; for a list of IIS server variables available see
# # http://msdn.microsoft.com/en-us/library/ms524602(v=vs.90).aspx; for example "AUTH_USER,AUTH_TYPE"
# promoteServerVars:
When using NVM, add the IUSR, IIS_IUSRS to the symbolicly linked folder as mentioned here