← BackExpress30 Q

Express

30 questions | entry to principal | answers written to be said out loud, not read

entry

What is Node.js and how does its event loop work?

Node is a JavaScript runtime built on Chrome's V8 engine that runs outside the browser. My code runs on a single main thread, and the event loop keeps that thread from blocking by handing off I/O work like file reads and network calls, then running my callback once the result comes back. That is why Node handles thousands of concurrent connections well even though only one thread executes my JavaScript.

What's the difference between blocking and non-blocking code in Node?

Blocking code runs synchronously and holds up the single thread until it finishes, so something like a synchronous file read or a heavy loop stalls every other request. Non-blocking code kicks off the work and lets the event loop keep serving other requests while it waits, then runs a callback when it's done. In practice that means I reach for the async version of an API and I never run CPU-heavy work directly on the main thread.

What is Express and why would you reach for it?

Express is a minimal web framework on top of Node's built-in http module. It gives me routing, middleware, and a request and response API without dictating much else, so it stays lightweight and flexible. I reach for it when I want control over the structure of an API rather than the conventions a bigger framework would impose on me.

What is Express middleware and why does order matter?

Middleware is just a function with req, res, and next that runs in the middle of the request and response. It can inspect or modify the request, end the response, or call next to pass control to the next function in the chain. Order matters because Express runs middleware in the exact sequence I register it, so something like authentication has to come before the route handler that needs it, and a logging middleware placed after the response is sent won't do anything useful.

Walk me through the request and response lifecycle in Express.

A request comes in, and Express matches it against my registered routes in order. It runs through any matching middleware, then the route handler, and each step either calls next to continue or ends the cycle by calling something like res.send or res.json. If nothing ever ends the response, the client just hangs waiting, which is a bug I've had to track down before.

How do you think about HTTP status codes in an API?

I try to be precise instead of just returning 200 or 500 for everything. 2xx means success, 400 means the client sent something invalid, 401 means they're not authenticated, 403 means they are authenticated but not allowed, 404 means the resource doesn't exist, and 500 means something broke on my end. Getting this right matters because clients, monitoring, and caching all key off the status code.

junior

What is a Buffer in Node?

A Buffer is a fixed-size chunk of raw binary data sitting outside the V8 heap. I run into it constantly with streams, file I/O, and network protocols, since data arrives as bytes before it gets decoded into a string or JSON. I have to know the encoding, usually utf-8, when I convert a Buffer to a string so I don't corrupt multi-byte characters.

CommonJS vs ES modules in Node?

CommonJS uses require and module.exports, loads synchronously, and has been the default in Node for years. ES modules use import and export, support static analysis and tree-shaking, and are the standard going forward, either in a .mjs file or a package.json with type module. The two systems don't mix cleanly, an ESM file can import CommonJS but a CommonJS file can't require an ESM package directly, so I pick one per project and I'm careful when I add a dependency that's ESM-only.

How do you handle configuration and process.env?

I keep configuration out of code and read it from process.env, things like database URLs, API keys, and the port to listen on. Locally I load a .env file with something like dotenv, but in production those variables come from the platform itself so secrets never live in the repo. I validate the shape of my config once at startup so the app fails fast with a clear error instead of crashing later with an unclear one.

How does error-handling middleware work in Express?

An error-handling middleware is any function with 4 arguments, error, req, res, and next, and Express recognizes it specifically by that arity. I put it last, after all my routes, and any handler that calls next with an error skips straight to it. That's where I log the error and send a consistent error response instead of leaking a stack trace or letting the request hang.

What are Express routers and why mount them?

A router is a mini instance of Express that groups related routes together, like everything under /users or /orders. I mount it on the app with app.use and a base path, which keeps my route files organized and lets me apply middleware, like auth, to just that group instead of the whole app.

How do you access data from the request, and what's the difference between params, query, and body?

req.params comes from the route path itself, like the id in /users/:id. req.query comes from the URL's query string, like ?sort=name. req.body comes from the request payload, usually JSON on a POST or PUT, and I need express.json middleware registered before my routes or req.body will just be undefined.

What is CORS and how do you configure it correctly?

CORS is a browser security mechanism that blocks a web page from calling an API on a different origin unless that API explicitly allows it. I configure an allowlist of the exact origins that should be able to call my API rather than reflecting any origin back, and I'm careful with credentials, since allowing credentials with a wildcard origin isn't allowed and shouldn't be worked around.

What does helmet do and why do you use it?

Helmet is Express middleware that sets a set of HTTP response headers that harden the app against common attacks, things like disabling the X-Powered-By header so I don't advertise Express, forcing HTTPS with HSTS, and setting a content security policy. It's not a complete security solution by itself, but it's a sensible default I add to basically every API.

Why do async route handlers in Express need special error handling?

Express's error handling was built before promises, so if an async handler throws or its promise rejects, Express doesn't automatically catch that and send it to my error middleware, the request just hangs or crashes the process. I wrap async handlers, either by hand with a try/catch that calls next with the error, or with a small helper, so every rejected promise still reaches my centralized error handler.

How do you test an Express API?

I use supertest with something like Jest or Vitest to make real HTTP requests against my app without actually binding to a network port, and I assert on status codes, response bodies, and headers. I test against a real or a dedicated test database for anything that touches persistence, because mocking the database usually just tests my mocks instead of catching real bugs.

senior

What is libuv and what is the thread pool for?

libuv is the C library underneath Node that implements the event loop and gives Node access to the operating system's async I/O. Some work can't be done asynchronously by the OS itself, things like file system calls, DNS lookups, and some crypto functions, so libuv offloads that to a thread pool, which defaults to 4 threads. The result still comes back to my single JavaScript thread through the event loop.

What are streams and what is backpressure?

Streams let me process data in chunks instead of loading a whole file or response into memory at once, which matters a lot for large payloads. Backpressure happens when a writable destination can't keep up with the data a readable source is producing, and if I ignore it memory usage climbs and the process can crash. I handle it by checking the return value of write and pausing the source, or more simply by just using pipe, which manages backpressure for me.

When would you use cluster or worker_threads?

cluster forks multiple copies of my whole Node process, one per CPU core, and load balances incoming connections across them, which is how I scale a stateless HTTP API across cores since Node itself is single-threaded. worker_threads is different, it runs actual JavaScript in parallel threads inside the same process, which I reach for when I have CPU-bound work like image processing or heavy computation that would otherwise block the event loop.

Where should input validation happen in an API?

I validate as early as possible, right at the edge before the request touches my business logic, using something like Zod or Joi against a schema. That way I reject bad input with a clear 400 response immediately, and everything downstream can trust the shape of the data instead of re-checking it everywhere.

JWT vs session-based authentication, how do you decide?

Sessions store state on the server, usually in a store like Redis, and the client just holds a session id in a cookie, which makes revoking access instant since I control the store. JWTs are self-contained and signed, so any server can verify one without a shared session store, which scales better across stateless services, but revoking a single token before it expires is genuinely hard. For a typical API I lean toward short-lived JWTs with a refresh token, or sessions when I need instant revocation and don't need to scale across many services.

How do you handle authorization once a user is authenticated?

Authentication tells me who the user is, authorization decides what they're allowed to do, and I keep those as separate steps. I check permissions as close to the data access as possible, not just at the route level, because a user might be allowed to hit an endpoint but not allowed to touch a specific record they don't own. I never trust a role or permission that arrives from the client, it always has to come from something I verified myself, like the token or the database.

How do you implement rate limiting on an API?

I put a rate limiter in front of the routes that need protecting, usually keyed by IP or by an authenticated user id, using something like express-rate-limit backed by Redis if I'm running more than one instance. It protects against abuse and brute-force attempts, and I return a 429 with a clear message and a Retry-After header rather than just silently dropping the request.

How do you manage database connections to Postgres from an Express app?

I use a connection pool instead of opening a new connection per request, since establishing a Postgres connection is expensive and a pool reuses a fixed set of them across requests. I size the pool based on what the database can actually handle across all my app instances combined, not just what feels generous for one instance, since too many connections can overwhelm Postgres faster than too few.

What is an N+1 query problem and how do you fix it?

It's when I run one query to get a list of records, then run one additional query per record to fetch related data, so 100 rows turns into 101 round trips to the database. I fix it by joining the data in a single query, or by batching the related lookups with something like a WHERE IN clause, so it stays a fixed number of queries regardless of how many rows come back.

staff

How do you design REST resources for an API?

I model the API around nouns, resources like users or orders, and let HTTP verbs express the action, GET to read, POST to create, PUT or PATCH to update, DELETE to remove. I nest routes when there's a real ownership relationship, like /users/:id/orders, and I keep responses consistent in shape so clients can predict what they're getting back.

How do you approach logging and request tracing in an API?

I log structured JSON instead of plain strings so it's searchable, and I attach a unique request id to every incoming request, either generated or passed through from an upstream service, and thread it through every log line for that request. That way when something goes wrong I can pull every log tied to one request across multiple services instead of guessing which lines belong together.

What do graceful shutdown and health checks mean in a production API?

Graceful shutdown means when the process gets a signal like SIGTERM, I stop accepting new connections, let in-flight requests finish, close database connections, and only then exit, so a deploy doesn't cut off requests mid-flight. Health checks are a simple endpoint the orchestrator polls to know if my instance is actually able to serve traffic, and I make sure it actually checks dependencies like the database rather than just returning 200 unconditionally.

Where would you add caching to an API?

I cache data that's expensive to compute or fetch and doesn't change on every request, things like a Redis cache in front of a slow query, or HTTP caching headers for responses that are safe to reuse across requests. The hard part isn't adding the cache, it's invalidating it correctly when the underlying data changes, so I think through that before I think about the cache itself.

principal

When would you reach for Fastify or Nest instead of Express?

I'd reach for Fastify when raw throughput and built-in schema validation matter, it's noticeably faster than Express and validates requests against a JSON schema out of the box. I'd reach for Nest when a team needs real structure and conventions, like dependency injection and modules, especially on a larger team where consistency matters more than flexibility. Express still wins for me when I want something minimal and don't want a framework making architectural decisions for me.

Fast recall

event loop = non-blocking scheduler | libuv = async i/o under Node | middleware = function with req res next | next() = passes control forward | router = scoped mini app | req.params = from the url path | req.query = from the query string | CORS = allowed cross-origin calls | helmet = security header defaults | JWT = self-contained signed token | connection pool = reused db connections | N+1 = one query per row | graceful shutdown = drain then exit | supertest = http assertions without a port

BH·Express·github.com/bunlongheng/study