Blog
Mert Özen's blog posts on software development, backend, frontend, and DevOps.
What Is Node.js? V8, libuv and the Event-Driven Server Model Through an ASP.NET Core Developer's Eyes
Node.js is a runtime that runs JavaScript on the server with the V8 engine and the libuv event loop: your code runs on one thread, waiting on I/O holds no thread, but one long synchronous computation makes every request wait. With real measurements: 500 concurrent waits in 227 ms, a 1.4-second report holding a trivial request for 1,340 ms, the 4-thread libuv pool, a comparison with Kestrel and the ThreadPool, a Node-or-.NET table and the yearly release schedule that starts with Node 27.
Installing Node.js and Your First Project: Version Managers, .nvmrc, package.json, node --watch and --env-file
The right way to start a Node.js project is to install Node with a version manager, pin the version with .nvmrc and the engines field in package.json, and keep scripts in package.json. With real output: the EBADENGINE error engine-strict gives on Node 24, npm init and the anatomy of package.json, restarts with node --watch, the shell value overriding .env with --env-file, 0.04 versus 0.11 seconds for node --run and npm run, and the Bookstore API's first GET /health endpoint.
The Node.js Module System: CommonJS vs ES Modules, "type", "exports" and require(esm)
Node.js loads a file as CommonJS or as an ES module based on its extension (.mjs/.cjs) and the "type" field in the nearest package.json; since require(esm) is stable in Node 26, the two worlds can now load each other, except for top-level await. With real errors: extensionless imports, ERR_REQUIRE_ASYNC_MODULE, named imports from CommonJS, paths closed by exports, the dual package hazard, and #-aliases plus JSON import attributes in the Bookstore API.
npm in Depth: Dependencies, SemVer Ranges, package-lock, npm ci, Workspaces and Supply Chain Security
npm pins the range in package.json to the exact version it records in package-lock.json: as long as the lock satisfies the range npm install does not move to a newer version, and npm ci allows no deviation from it. Real output shows what ^ and ~ select among Express versions, the 0.x caret trap, npm ci stopping on a mismatched lock, nested content-type copies, the @bookstore/contracts workspace, npm audit, install script approval with strict-allow-scripts, and signature verification.
TypeScript in Node.js: Native Type Stripping, erasableSyntaxOnly and Build-Free Development
Node.js 26 runs .ts files without a build step by replacing types with whitespace, but it does not check them, so tsc --noEmit must be a separate, mandatory step. With real output: a file with a type error printing 699.8, enum and parameter property errors, mandatory import type, the tsconfig Node recommends, moving the Bookstore API to TypeScript 7, imports instead of paths, the ban on .ts in node_modules, and when you still need a build.
The Node.js Event Loop on the Server: libuv Phases, process.nextTick, setImmediate and Blocking the Loop
The Node.js event loop passes through the libuv phases (pending, poll, check, close, timers) on every turn and drains the nextTick queue, then the promise queue, after every callback; that is why setImmediate runs before setTimeout(0) inside an I/O callback. With real measurements: a full thread pool holding a file read for 371 ms, blocking with JSON.parse and ReDoS, monitorEventLoopDelay, and the Bookstore price report holding /health for 481, 13 and 1 ms inline, chunked and in a worker.
Async Patterns in Node.js: From Callbacks to Promises, EventEmitter, AbortSignal and AsyncLocalStorage
The building blocks of asynchronous Node.js code are the promise APIs, the synchronous EventEmitter, AbortSignal for cancellation, and AsyncLocalStorage, which carries request context across awaits. With real output: emit calling listeners synchronously, a process crashing on an unhandled 'error', captureRejections, AbortSignal.timeout and any(), request ids getting mixed up in a global variable, and a Bookstore OrderService that emits orderPlaced.
The File System in Node.js: fs/promises, path, Buffer and File Handling Pitfalls
On a Node.js server, file work is done with node:fs/promises, but the real job is handling encodings correctly, writing files atomically and keeping user-supplied paths inside the root folder. With real output: an 8-character Turkish title taking 11 bytes, Turkish characters breaking at chunk boundaries, the BOM in Excel's CSV, 61 of 62 reads seeing a half-written file with writeFile, lost updates, the startsWith trap, and the Permission Model disabling fsync.
Node.js Streams: Readable, Writable, Transform, pipeline() and Backpressure
Node.js streams process data piece by piece; waiting for drain when write() returns false, which is respecting backpressure, keeps memory constant, and pipeline() wires that into every stage together with error handling. With real measurements: a producer that ignores false growing the heap to 182 MB, pipe() leaving the source file open, a 505 MB Bookstore order export streamed with 147 MB RSS, and the same job taking 993 MB in memory.
A Bare HTTP Server with node:http: Request/Response, Hand-Rolled Routing and Why You Need a Framework
node:http is the lowest layer of HTTP in Node: reading the body with a size limit, routing, validation and error handling are your job, and a single error in an async handler can take the process down. With real output: the crashing server, a JSON body read as a stream with 413/415/422 responses, a hand-rolled router, a worker stopped when the client leaves, listen() binding to ::, headersTimeout answering slowloris with 408, and a connection left open after a 413.
Getting Started with Express 5: Routing, the Middleware Chain and What Changed from Express 4
Express 5 forwards errors from async handlers to the error middleware by itself, moves path syntax to path-to-regexp 8 and leaves req.body undefined when the body was not parsed. With real output: Express 4 crashing on the same error, old paths that now fail at startup, the three-parameter error handler trap, moving the Bookstore API to Express, a listen that silently swallows EADDRINUSE, and NODE_ENV=production not changing speed for a JSON API.
Building the Bookstore REST API with Express: Routers, Validation with zod, Async Error Handling and Problem Details
A solid REST API with Express separates its layers, validates every request with a zod schema and derives the type from it, turns errors into RFC 9457 Problem Details in one middleware, and keeps money as whole numbers of kuruş instead of floats. With real output: an order total of 1339.1999999999998, a float sum drifting by 5 kuruş over 20 million lines, validation returning six errors in one response, the same Problem Details contract as ASP.NET Core, paging, and z.toJSONSchema.