Node.js — Runtime Internals, Express, APIs and SQL Server
In short: A JavaScript runtime built on Chrome's V8 engine and libuv.
- It runs your JavaScript on a single main thread with an event loop and performs I/O (network, disk) asynchronously, so while one request waits for the database the thread serves other requests.
- Without thread-per-request overhead it handles many concurrent connections efficiently — ideal for APIs, proxies and real-time apps.
In short: The event loop is libuv's loop that repeatedly checks for completed work and runs callbacks on the main thread in phases: timers (setTimeout/setInterval), pending callbacks, poll (I/O events), check (setImmediate), close callbacks.
- Between callbacks Node drains the
process.nextTickqueue and then the promise microtask queue. - Blocking the loop with long synchronous code delays every other request.
In short: Your JavaScript runs on one main thread, but Node itself is not: libuv uses a thread pool (default 4) for file system, DNS lookup, crypto and compression, and the OS handles network I/O asynchronously.
- You can also use worker threads for CPU-heavy JavaScript and the cluster module or multiple containers to use all CPU cores.
In short: CommonJS uses require()/module.exports, loads synchronously at runtime and was Node's original system.
- ES modules use
import/export, are statically analysable (tree-shaking), support top-level await and are the JavaScript standard — enabled with.mjsor"type": "module". - Modern Node supports both and recent versions can
require()ES modules.
In short: A function (req, res, next) in the request pipeline that can read/modify the request and response, end the response, or call next() to pass control on.
- Used for logging, parsing bodies, authentication, CORS, validation and error handling (error middleware has four parameters
(err, req, res, next)). - Middleware runs in the order it's registered.
In short: package.json describes the project: name, scripts, engines, dependency ranges.
package-lock.jsonrecords the exact resolved versions of the whole dependency tree so installs are reproducible (npm ci).dependenciesare needed at runtime in production;devDependenciesonly for development, testing and building.
In short: process.nextTick callbacks run immediately after the current operation, before promise microtasks and before the event loop continues.
setTimeout(fn, 0)runs in the timers phase of a later loop iteration (minimum ~1 ms).setImmediateruns in the check phase right after the poll phase.- Inside an I/O callback, setImmediate always fires before a zero timeout.
- Overusing nextTick can starve I/O.
In short: Use try/catch around await, .catch() on promises, and error-first callbacks for callback APIs.
- In Express 5 rejected async handlers reach the error middleware automatically (Express 4: wrap or use express-async-errors).
- Add a central error middleware, log
unhandledRejectionanduncaughtException(then exit and let the process manager restart), validate input to avoid predictable errors, and never swallow errors silently.
In short: Use the mssql package (tedious driver): create one ConnectionPool at startup with server, database, credentials and encrypt: true, then run parameterised queries with request().input(...).query(...), stored procedures with .execute(), and transactions with sql.Transaction.
- Alternatives: Prisma (
provider = "sqlserver") or Sequelize/TypeORM. - Keep credentials in environment variables or use Entra ID authentication for Azure SQL.
In short: Objects for reading or writing data in chunks (Readable, Writable, Duplex, Transform).
- They keep memory constant for large files or responses, start delivering data before everything is loaded, and handle backpressure.
- Use
stream.pipelineto connect streams with proper error handling — e.g. read a CSV, gzip it, write it to the HTTP response.
In short: Cluster forks multiple Node processes (one per core) that share the same server port, each with its own event loop and memory — scaling a stateless HTTP server on one machine (PM2 does this).
- Worker threads run JavaScript in parallel threads inside one process (separate V8 isolates, can share memory via SharedArrayBuffer) — for CPU-heavy tasks without blocking the main event loop.
Helmet for security headers, strict CORS, HTTPS, input validation (zod/Joi), parameterised queries/ORM against injection, authentication (JWT/OAuth with libraries like passport or jose) and role checks, rate limiting, body size limits, password hashing (bcrypt/argon2), secrets in env/Key Vault, dependency scanning (npm audit, Dependabot), no stack traces in responses, and keeping Node and packages updated.
In short: Long synchronous operations: big loops, synchronous fs/crypto (readFileSync, pbkdf2Sync), huge JSON.parse/stringify, regex catastrophes, sorting large arrays per request.
- Detect with event-loop lag metrics (
perf_hooks.monitorEventLoopDelay), clinic.js, profiling with--inspect/--prof. - Fix by using async APIs, streaming, chunking work with
setImmediate, moving CPU work to worker threads or another service, and caching results.
In short: Objects kept reachable: growing global arrays/maps used as caches without limits, event listeners added per request and never removed, closures capturing large data, timers not cleared, unclosed DB connections.
- Find them by watching heap usage over time, taking heap snapshots (
--inspect+ Chrome DevTools,heapdump) and comparing retained objects; - fix by bounding caches (LRU), removing listeners and cleaning up timers.
In short: Node.js: JavaScript/TypeScript across front and back end, huge npm ecosystem, great for I/O-heavy APIs, real-time apps, BFFs and serverless functions, fast prototyping.
- ASP.NET Core: strong typing and tooling by default, true multithreading for CPU-heavy work, rich built-in framework (DI, auth, EF Core), excellent performance and long-term support — common in enterprises with SQL Server/Azure.
- Choose based on team skills, workload and ecosystem.
In short: The upload is probably read and parsed synchronously in memory (readFileSync, JSON/CSV parse of the whole file) on the main thread, blocking the event loop for all users.
- Stream the upload (multer/busboy to disk or Blob storage), parse it with a streaming CSV parser, insert in batches (bulk insert/TVP), and ideally move the import to a background job/queue with a worker, returning
202 Acceptedand a status endpoint.
In short: Find the promise without await/.catch from the stack trace and logs;
- fix it by awaiting or handling the error;
- add a global
process.on('unhandledRejection')handler that logs (then exits for safety), run under a process manager (PM2/containers) that restarts the app, and add lint rules (@typescript-eslint/no-floating-promises) to prevent new ones.