Node.js on Windows, in production, may not be the most common configuration – but it’s immensely powerful with the help of edge.js, iisnode, and other open source projects. In fact, it’s a great tool for building highly performant, scalable front- and back-end websites on the Microsoft stack (IIS, .NET, SQL Server, etc).
In this talk, I’ll share some details, tips-and-tricks, and experiences building a production website on Windows, using CellarTracker – the world’s largest collection of community wine reviews and tools for cellar management – as an example.
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
Building production websites with Node.js on the Microsoft stack
1. Building production
websites with Node.js
on the Microsoft stack
(aka “How I Learned to Stop Worrying
and Love edge.js”)
Dan Polivy
dan@cellartracker.com
@DanPolivy
2. CellarTracker is the world's largest collection of wine reviews,
tasting notes and personal stories from people who love wine.
1.7M+ wines, 4.5M+ tasting notes (community and professional), 300,000+ registered users
5. Yes, that’s VBScript
Front End IIS
Business
Data
Classic ASP
(VBScript)
MS SQL
Server
6. 11 years of legacy…
• Two web experiences built on Classic ASP/VBScript
• Desktop and “Classic”
• 170+ SQL tables, 25+ SQL Views
• Existing infrastructure
• Physical rack in Seattle datacenter
• Virtualized on VMWare, guests running Windows Server
7. Mobile: not again!
With limited resources (e.g., me), how can we modernize our
stack to support next generation mobile web and apps
without sacrificing our existing investments?
8. Node.js to the rescue?
We already use a lot of JavaScript for our client side
Lightweight development model (no compilation)
Async nature seems well-suited to our workload (lots of blocking DB
queries)
It is (was) the new hot-ness (circa March 2013)
? Integration with IIS
iisnode!
? Integration with SQL
Tedious, MS NodeSQL, edge.js!
9. Will it blend?
0 100 200 300 400 500 600 700
iisnode
Standalone
Simulated Requests/sec
Edge.js MS Node SQL Tedious Classic ASP
HTTP Request returning HTML wine detail page with 5 SQL queries for data
10. Conclusion: Yes!
• Async is a HUGE win for higher throughput & lower latency compared
to serial systems like Classic ASP (and even Rails)
• Contrary to what you might think, loading .NET in-process to talk to
SQL yields significant performance gains vs pure JS or native (C++)
drivers
• Offloading heavy DB/computation lifting into .NET thread pool frees up
Node.js’ main thread for other work
• Marshalling data between JS and .NET has incremental cost
• iisnode adds some (latency) overhead, but adds many useful features
11. iisnode
A native IIS module that allows hosting of Node.js applications in IIS on Windows
https://github.com/tjanczuk/iisnode
13. How does it work?
Client
• Initiates HTTP Request, e.g. ‘GET /m’
IIS
• URL Rewrite rule routes request to app, e.g. ‘GET /m/server.js’
• iisnode handler spawns process as necessary and fwds request
node.exe
• Receives request via named pipe port (process.env.PORT)
• Otherwise, works exactly the same as if it were standalone!
14. Why is this awesome?
• Node.js app runs side-by-side with existing Classic ASP (or
ASP.NET/etc) site
• With URL rewrite module, enable IIS to serve up static content (e.g.
client JS, CSS, images) more performant
• X-Forwarded-For/X-Forwarded-Proto headers can be added so you
know the true source of the request
• app.set('trust proxy', true);
• Lifecycle management gracefully drains/recycles processes when JS
changes
16. edge.js and SQL
.NET and Node.js in-process
https://github.com/tjanczuk/edge
17. SQL Requirements
• Parameterized queries
• Input, output parameters and RETURN values
• Call SPROCs (EXEC sp_FetchWineData)
• Typically require multiple queries to fetch all data to render a page
ADO.NET
18. Data model
Proxy
.NET assembly
Bottles
Notes
Wines
…
SQL Client
index.js
node module
Bottles
Notes
Wines
…
SQL Client
edge.js
• Separate C# assembly for data model, wrapped in JS API
• Utilize .NET threadpool and C# async/await to parallelize queries
• More efficient to write queries in C# and return JSON data
22. Example: Fetching a tasting note
{
"Note": {
"iNote": 3122730,
"iWine": 1314868,
"iUser": 60873,
"Name": "I",
"Favorite": 0,
"Rating": 95,
"TastingDate": "October 25, 2012",
"TastingNotes": "Dark red body. Subtle perfumed nose. A beautifully elegant body
full of plum, black cherry, and darker spices. This is a powerful wine with a long,
lingering finish. Refined tannins, good acids and structure. This is a really nice wine
right now, and will only get better with time!",
"LikeIt": true,
"AllowComments": true,
"Votes": 1,
"Views": 1234,
"Defective": false,
},
"Comments": [
{
"iComment": 5056,
"iUser": 1,
"Name": "Eric",
"Comment": "Yum, serious wines!",
"PostedDate": "11/13/2012 4:58pm",
"LastEditDate": "11/13/2012 4:58pm"
}
],
"Wine": {
"iWine": 1314868,
"Wine": "Gaja Langhe Nebbiolo Sorì San Lorenzo",
"Vintage": "2009",
"iProducer": 91,
"Producer": "Gaja",
"Locale": "Italy, Piedmont, Langhe, Langhe DOC",
"Type": "Red",
"Varietal": "Nebbiolo",
"DisplayVintage": "2009",
"FullWineName": "2009 Gaja Langhe Nebbiolo Sorì San Lorenzo",
"SplitLocale": [
"Italy",
"Piedmont",
"Langhe",
"Langhe DOC"
]
}
} JSON Data returned from C#
23. General Purpose SQL Class
• Support for SELECT, INSERT, UPDATE, DELETE, EXEC (SPROCs)
• Parameters
• Fully parameterized queries
• Type (if not default)
• Direction (in, out, inout, return)
• Recordset Processors
• Default: dump each row as JSON object in array
• Return single JSON object (first row)
• Return scalar value
• Offset/Multiple recordsets
24. Error Handling
1. Return empty JSON objects/arrays
2. .NET Exceptions JavaScript Error objects
• Standard node callback signature: callback(error, result)
• Exception is packaged up as standard Error object
{
[System.AggregateException: One or more errors occurred.]
message: 'One or more errors occurred.',
name: 'System.AggregateException',
InnerExceptions: 'System.Collections.ObjectModel.ReadOnlyCollection`1[[System.Exception, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]',
Message: 'One or more errors occurred.',
Data: 'System.Collections.ListDictionaryInternal',
InnerException:
{ [System.Exception: Sample exception]
message: 'Sample exception',
name: 'System.Exception',
Message: 'Sample exception',
Data: 'System.Collections.ListDictionaryInternal',
TargetSite: 'System.Reflection.RuntimeMethodInfo',
StackTrace: ' at Startup.<<Invoke>b__0>d__2.MoveNext() in c:UsersUser.NameSourceReposeCash2testedge2.js:line 7rn--- End of stack trace from previous location where
exception was thrown ---rn at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)rn at
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)rn at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()rn at
Startup.<Invoke>d__4.MoveNext() in c:UsersUser.NameSourceReposeCash2testedge2.js:line 5',
Source: 'cp2luegt',
HResult: -2146233088
},
HResult: -2146233088
}
27. Apps
• Built using Cordova framework
• Leverage investment in mobile web
• Backend completely powered by node.js
• Remotely serve script to allow faster iteration
• Add native capabilities where necessary/viable
• Barcode scanning
• Label image upload
28. Traffic
Last 30 days: 1.6M+ page views served up by
node.js!
Nearly 6M page views served by Classic ASP
on the same server.
Mobile App Page Views/week
Mobile Web Page Views/month
30. Encoding: UTF8 vs Windows 1252
Our database uses Windows 1252 encoding. JavaScript uses UTF8.
What to do?
If only http://howfuckedismydatabase.com/ had a joke about this…
31. Encoding: UTF8 vs cp1252
• Database
• Characters with codes > 256 are HTML entity encoded, as are < and > (XSS
prevention)
• Classic ASP
• Most pages set codepage to 1252, so ASP automatically does conversion/encoding
for incoming values
• All URLs on our desktop site are URL encoded using 1252 values
• Node.js
• Natively deal in UTF8, and convert when going to/from the DB
• Edge.js/.NET handle much of it automatically, BUT we must do proper HTML entity encoding
ourselves
• Some manual character substitution is required (e.g. some chars have high codepoints in
UTF8, but are below 256 in 1252)
• Mobile URLs all use UTF8 encoding
32. Log file management
• IIS logs
• All node traffic ends up in IIS logs, so you have unified log for the server
• BUT, you can’t tell which traffic is actually handled by node (unless you use
paths)
• express/node logs
• Handled automatically by iisnode: console and stderr
• One per node process, with a handy index.html
• Still trying to figure out best management strategy (tips welcome!)
33. Git
• Git and GitHub for Windows work great!
• git-posh: PowerShell add-ins make life easier
• Managing line-endings a small challenge
• Most modules use unix endings; most of our code uses Windows
• For now (but not ideal): set core.autocrlf to false
• ‘git pull’ deploy
• iisnode’s auto-refresh will automatically detect changes to JS and gracefully
recycle processes while draining existing traffic