Hire Node.js developers
Node.js runs JavaScript on the server, and hiring for it means hiring for an understanding of the event loop rather than for familiarity with a framework.
What Node.js actually is
Node.js is a runtime that executes JavaScript outside a browser. It is stewarded by the OpenJS Foundation and built on the same engine that powers Chrome, with a standard library for the things a server needs and a package ecosystem that is the largest of any language. Its defining characteristic is that it runs your code on a single thread and handles input and output without blocking that thread.
That design is why Node.js is good at some workloads and poor at others, and the distinction is the most important thing to get right before you hire. A service that spends its time waiting on databases, other services and network calls is exactly what the model was built for. A service that spends its time doing arithmetic on large data structures is not, because while that arithmetic runs nothing else on the thread can happen, including answering other requests.
The second thing to understand is how much of a Node.js codebase is not Node.js. The runtime is small; the application is mostly packages. That makes delivery fast and makes dependency management a real operational concern rather than an afterthought. When we assess Node.js developers, how they think about their dependency tree is as informative as how they write a route handler.
The part that separates seniors from mid-levels
Everything rests on the event loop. One thread runs your JavaScript; the runtime hands slow work to the operating system and calls you back when it finishes. The practical consequence is that any synchronous work you do occupies the whole process. A developer who has operated a Node.js service in production knows this in their hands: they can name the synchronous call that once took down a service, usually a large JSON parse, a synchronous file read, or a regular expression that backtracked catastrophically on hostile input.
Concurrency and parallelism come apart here in a way that catches people out. Node.js gives you a great deal of concurrency on one thread and no parallelism at all without extra processes or worker threads. Scaling means running several processes behind a load balancer, which in turn means no useful state can live in the process. Developers whose experience is entirely single-instance write code that holds sessions, caches or counters in memory, and that code fails the moment it is scaled.
Error handling is the third area that separates levels, because Node.js gives you several ways to get it wrong. An unhandled rejection, an error thrown inside a callback, an error event on a stream with no listener, and an exception in an async function that nothing awaits all behave differently. Senior developers have opinions here formed by incidents: they await everything they start, they attach handlers to streams, and they treat a process-level handler as a place to log and exit rather than as a way to keep running.
Where Node.js is used
The label “Node.js developer” covers several jobs that share a technology and little else. These are the settings the work usually turns up in, and the one you are hiring into should shape the whole process, because the judgement each demands is different.
API services
REST and GraphQL services in front of databases and third-party systems, which is the workload the runtime suits best.
Backend for frontend
A thin server-side layer aggregating several internal services for one client application, often owned by the front-end team.
Real-time systems
Chat, notifications, collaborative editing and live dashboards over websockets, where many idle connections are cheap.
Server-side rendering
The runtime underneath Next.js, Nuxt and similar frameworks, where the same language spans both sides.
Build tooling and internal CLIs
Developer tooling, code generators and deployment scripts, where the ecosystem is the main attraction.
Integration and automation
Glue services moving data between systems on a schedule or in response to events, typically deployed as small functions.
If a candidate's experience sits in a different row of that list from the work you have, that is not a reason to reject them, but it is the thing to probe. Ask what would be different about their approach in your setting. Someone who can answer that has transferable judgement. Someone who says it would be much the same has probably not thought about it.
Which Node.js versions are still supported
Node.js runs a predictable release cadence with alternating long-term support lines, so the version a team is on is usually a good indicator of how actively the codebase is maintained.
The table is generated from public release data rather than written by hand, so it states what is supported today rather than what was true when any given article was published. Of the 8 most recent release lines, 4 are still maintained and 4 have passed their published end-of-life date. That distinction is the practical one when you read a job specification: a requirement written against a line that is now out of support tells you the specification is older than the codebase it describes, and it is worth asking which of the two the new developer will actually work in.
| Release | Released | End of life | Status | Latest patch |
|---|---|---|---|---|
| 26 (Upcoming LTS) | 2026-05-05 | 2029-04-30 | Maintained | 26.10.0 (2026-09-22) |
| 25 | 2025-10-15 | 2026-06-01 | End of life | 25.9.0 (2026-04-01) |
| 24 (LTS) | 2025-05-06 | 2028-04-30 | Maintained, long-term support | 24.21.0 (2026-09-08) |
| 23 | 2024-10-16 | 2025-06-01 | End of life | 23.11.1 (2025-05-14) |
| 22 (LTS) | 2024-04-24 | 2027-04-30 | Maintained, long-term support | 22.23.3 (2026-09-23) |
| 21 | 2023-10-17 | 2024-06-01 | End of life | 21.7.3 (2024-04-10) |
| 20 (LTS) | 2023-04-18 | 2026-04-30 | Maintained, long-term support | 20.20.2 (2026-03-24) |
| 19 | 2022-10-18 | 2023-06-01 | End of life | 19.9.0 (2023-04-10) |
Source: endoflife.date public release data, read 2026-09-25.
Two questions follow from this table in an interview. The first is which line the candidate has most recently shipped against, because someone whose last production work was on an unsupported line has not had to deal with the changes since. The second is how they have handled an upgrade. Version migrations are where you see whether a developer reads release notes, writes characterisation tests before changing anything, and knows how to stage a rollout, or whether they upgrade in place on a Friday and hope.
The toolchain around it
Nobody hires for Node.js alone. The surrounding tools are where most of the day-to-day work happens, and a gap in any of them costs more time than a gap in the core library. This is the set that turns up most often on real job specifications alongside it.
- TypeScript
- Now standard in commercial Node.js work, particularly where types are shared with a front end.
- Express, Fastify or NestJS
- HTTP layer. Express is the most common and the least opinionated; NestJS imposes a structure that larger teams often want.
- Prisma, Drizzle or Knex
- Database access, from fully typed clients to thin query builders.
- PostgreSQL
- The most common data store behind Node.js services.
- Redis
- Caching, rate limiting, and the shared state that cannot live in a process being scaled horizontally.
- BullMQ or a managed queue
- Background work moved off the request path, which is how CPU-bound tasks are handled properly.
- Vitest or Jest
- Testing, with integration tests against a real database rather than mocks wherever possible.
- OpenTelemetry
- Tracing and metrics. Single-threaded services need event loop lag monitored specifically.
Related skills that frequently appear on the same specification: GraphQL, React, Next.js, TypeScript, AWS.
What to test in an interview
These are the topics that separate candidates in practice. Each one is given with why it discriminates, what a strong answer sounds like, and the response that should make you slow down. None of them requires a whiteboard.
The event loop and blocking
The foundation of the runtime, and the difference between someone who has operated a service and someone who has only written one.
- Strong answer: Explains that synchronous work blocks every request, names a specific incident, and moves heavy work to a queue or a worker.
- Warning sign: Believes async automatically means parallel, or has never considered what blocking would do.
Scaling and process state
The most common reason a service that worked on one instance fails on three.
- Strong answer: Keeps processes stateless, puts sessions and caches in Redis, and knows why in-memory rate limiting is wrong behind a load balancer.
- Warning sign: Stores sessions or caches in module scope and has not thought about a second instance.
Error handling across async boundaries
Node.js offers several distinct ways to lose an error silently.
- Strong answer: Awaits everything started, handles stream error events, and treats an unhandled rejection as a reason to exit rather than continue.
- Warning sign: Uses a global handler to keep the process alive after an unknown error.
Dependency management and supply chain
A Node.js application is mostly other people's code, and that is an operational risk.
- Strong answer: Commits a lockfile, audits regularly, is deliberate about adding dependencies, and can describe a package they removed.
- Warning sign: Adds packages freely and has never looked at what a dependency pulls in.
Streams and large payloads
The difference between a service that handles a large file and one that runs out of memory.
- Strong answer: Streams rather than buffering, understands backpressure, and can explain what happens when a consumer is slower than a producer.
- Warning sign: Reads whole files into memory as a default and has not hit the limit yet.
Observability in a single-threaded runtime
Standard metrics miss the failure mode that matters most here.
- Strong answer: Monitors event loop lag specifically, alongside latency percentiles and memory, and can describe diagnosing a slow service.
- Warning sign: Monitors CPU and memory only, which look fine while the loop is blocked.
When Node.js is the wrong choice
The clearest test of whether someone has judgement or a preference.
- Strong answer: Names CPU-bound work honestly and describes moving it elsewhere or to a queue.
- Warning sign: Cannot think of a case, or claims worker threads solve everything.
Warning signs in a Node.js codebase
The fastest way to read a candidate is to ask what they have found wrong in code they inherited. These are the patterns that come up most often, what they cost, and what fixing them looks like. A developer who recognises three or four of these from their own experience is worth more than one who can recite the documentation.
CPU-bound work on the request path
- What you see: Image processing, large parses, report generation or cryptography inside a handler.
- What it costs: Every other request waits. Under load the service stops answering entirely while appearing healthy on CPU metrics.
- The fix: Move it to a queue with separate workers, or to a service in a runtime built for it.
State in the process
- What you see: Sessions, caches or counters held in module-level variables.
- What it costs: Works on one instance, breaks silently on two, with users losing sessions on alternate requests.
- The fix: Move shared state to Redis or the database. Treat every process as disposable.
Floating promises
- What you see: Async functions called without being awaited or explicitly handled.
- What it costs: Errors vanish, ordering assumptions break, and the process can exit with work in flight.
- The fix: Enable the linter rule for it and treat every violation as a bug rather than a style issue.
Buffering what should be streamed
- What you see: Whole uploads or query results read into memory before processing.
- What it costs: Memory scales with payload size, and the process is one large request away from being killed.
- The fix: Stream end to end and respect backpressure. Test with a file larger than you expect.
Dependency sprawl
- What you see: Hundreds of direct dependencies, several doing the same job, added over years.
- What it costs: Slow installs, a wide vulnerability surface, and upgrades that nobody dares attempt.
- The fix: Audit what is actually imported, remove what is not, and require a reason for each new addition.
Keeping the process alive after an unknown error
- What you see: A global uncaught exception handler that logs and continues.
- What it costs: The process runs on in an undefined state, producing corrupt behaviour that is far harder to diagnose than a restart.
- The fix: Log, flush, exit, and let the supervisor restart. Fix the specific error rather than suppressing the class.
What each level can own
Job titles are not comparable between companies, so it is more useful to describe levels by what a person can be left to own without supervision. These are the boundaries we use when we assess a Node.js developer.
- Junior
- Writes route handlers and database queries in an established service. Needs review on async correctness and error handling.
- Mid-level
- Owns a service or a substantial area of one, including its tests and its queue workers. Understands why the process must be stateless.
- Senior
- Owns service boundaries, the data access approach, the background-work architecture and the observability that makes incidents diagnosable. Can say when Node.js is the wrong tool.
- Staff
- Owns the contracts between services, the dependency and upgrade policy, and the performance envelope, including the decision to move a workload off the runtime.
How the work is usually scoped
Team shape follows the kind of work, not the headcount you happen to have budget for. These are the shapes that come up most often and the constraint that actually governs each one.
New API service
- Usual team: One to two developers, senior-led for the first weeks.
- What governs it: The data access pattern and the error contract set early are the expensive things to change later.
Backend for frontend
- Usual team: One developer, often inside the front-end team.
- What governs it: Scoped by the number of upstream services aggregated, not by endpoints exposed.
Monolith decomposition
- Usual team: Two developers plus someone who knows the existing system.
- What governs it: Governed by how entangled the data is. Splitting code is easy; splitting a database is the actual project.
Performance and stability remediation
- Usual team: One senior developer, time-boxed.
- What governs it: Begins with event loop lag and latency percentiles. Usually resolves to two or three blocking calls.
Dependency and runtime upgrade
- Usual team: One developer.
- What governs it: Scoped by how far behind the codebase is and whether it has tests. Several major versions behind with no tests is a project, not a task.
Migration work you may actually be hiring for
A large share of Node.js work is not new development. It is moving an existing system from one state to another while it stays in service. These are the migrations that come up most often, and each one asks for a different kind of experience from the person you hire.
JavaScript to TypeScript
- Why teams do it: Type safety across service boundaries and shared contracts with the front end.
- What to watch: Migrate file by file. The highest value is at request and response boundaries, so start there rather than with internal utilities.
Express to Fastify or NestJS
- Why teams do it: Built-in schema validation and throughput in the first case, imposed structure for a growing team in the second.
- What to watch: Middleware rarely ports cleanly. Run both behind one router and move routes in groups, keeping the old path live until traffic has moved.
A single process with in-memory state to horizontally scaled instances
- Why teams do it: Availability and load. Usually forced rather than chosen.
- What to watch: Every piece of in-process state is a bug waiting for the second instance. Find sessions, caches, counters, timers and locks before switching, not after.
Synchronous request processing to a queue and workers
- Why teams do it: Removing CPU-bound and slow third-party work from the request path, which is the single largest stability win available.
- What to watch: Retries, idempotency and dead-letter handling are the project. A queue without a plan for repeated failures moves the problem rather than solving it.
Migration work rewards a different temperament from greenfield work. The useful question in an interview is not whether someone has done the specific migration you face, but whether they have ever run one incrementally: behind a flag, with both paths live, and with a way back. Developers who have only done big-bang cutovers tend to propose them again.
What a good brief for this role contains
Most of the time lost in hiring a Node.js developer is lost before anyone is interviewed, in the gap between what the brief says and what the team actually needs. These are the points that, for this technology specifically, change who the right candidate is. A brief that answers them can be matched in days. One that does not produces a shortlist that looks reasonable and converts badly.
- What the service actually does: whether it waits on other systems or computes, since that decides whether the runtime fits at all.
- How many instances it runs on now, and whether anything is currently held in process memory.
- Which HTTP framework and data access layer are in use, because the structure they impose differs a lot.
- Whether background work exists and how it is run, since queue and worker experience is a distinct skill.
- Which Node.js version the codebase targets, and whether upgrading is part of the work.
- Whether the person is expected to operate the service as well as build it, including being on call.
If you cannot answer some of these yet, that is normal and it is still worth writing down which ones are open. An unknown that is named can be worked around. An unknown that is papered over in a job specification turns into a rejected shortlist and a restart four weeks later.
What the US market pays for this work
Node.js work is counted by the US Bureau of Labor Statistics under Software Developers. That classification is broader than the technology itself, so treat the figures as the shape of the market a Node.js developer is hired into rather than as a rate card for the skill. Across the United States the Bureau counts 1,687,890 people in this occupation, with a median annual wage of $135,980.
The spread matters more than the midpoint. The 90th percentile is about 2.6 times the 10th, which is a wide band for a single occupation and tells you that the title on its own carries very little pricing information. Two people described as a Node.js developer can sit at $82,460 and $214,670 in the same national dataset. When a budget is set from a median without asking which end of that range the work actually needs, the hire that follows is usually the wrong one in one direction or the other.
Related classifications are worth reading alongside it, because teams hiring for Node.js frequently end up recruiting against these titles too:
| Occupation | Employed | 25th percentile | Median | 75th percentile | 90th percentile |
|---|---|---|---|---|---|
| Software Developers | 1,687,890 | $105,210 | $135,980 | $171,980 | $214,670 |
| Computer Programmers | 92,230 | $75,850 | $100,390 | $130,680 | $160,460 |
Source: BLS Occupational Employment and Wage Statistics, May 2025. Figures cover all US employers and are not FuturByte rates.
These are employer-side wage figures for people on a US payroll. They exclude employer taxes, benefits, recruitment cost and the months a seat sits empty, all of which are real and none of which appear in a salary line. The useful way to read the table is as the cost of the alternative you are comparing against, not as a number to match.
How US metro markets compare for this role
The same job is priced very differently across the country. Ranked by median annual wage for Software Developers, the gap between the highest and lowest of the 28 metro areas covered here is a factor of about 1.7. San Jose sits at the top with a median of $213,110; Pittsburgh sits at the bottom with $124,500. A budget built from a national median will be wrong in both of those markets, in opposite directions.
| Metro area | Employed | Median wage | vs US median | Location quotient |
|---|---|---|---|---|
| San Jose, CA | 87,350 | $213,110 | +57% | 7.09 |
| San Francisco, CA | 69,030 | $186,640 | +37% | 2.68 |
| Seattle, WA | 92,770 | $167,280 | +23% | 4.10 |
| New York, NY | 121,000 | $166,830 | +23% | 1.17 |
| Boston, MA | 42,310 | $166,090 | +22% | 1.44 |
| San Diego, CA | 20,610 | $163,270 | +20% | 1.23 |
| Los Angeles, CA | 55,540 | $160,920 | +18% | 0.82 |
| Portland, OR | 18,260 | $156,000 | +15% | 1.39 |
| Washington, D.C. | 69,060 | $154,930 | +14% | 2.03 |
| Baltimore, MD | 16,850 | $138,900 | +2% | 1.14 |
| Denver, CO | 27,010 | $137,610 | +1% | 1.55 |
| Charlotte, NC | 20,820 | $135,920 | 0% | 1.41 |
| Chicago, IL | 40,370 | $134,380 | -1% | 0.82 |
| Austin, TX | 31,960 | $134,120 | -1% | 2.28 |
| Dallas-Fort Worth, TX | 67,030 | $133,290 | -2% | 1.52 |
| Philadelphia, PA | 28,480 | $133,040 | -2% | 0.91 |
| Atlanta, GA | 36,300 | $132,960 | -2% | 1.16 |
| Raleigh, NC | 12,580 | $132,770 | -2% | 1.56 |
| Miami, FL | 18,900 | $132,650 | -2% | 0.62 |
| Phoenix, AZ | 29,380 | $131,750 | -3% | 1.14 |
| Minneapolis-St. Paul, MN | 27,410 | $130,920 | -4% | 1.29 |
| Detroit, MI | 24,870 | $130,760 | -4% | 1.20 |
| Tampa, FL | 14,230 | $130,450 | -4% | 0.91 |
| Orlando, FL | 13,440 | $129,620 | -5% | 0.88 |
| Salt Lake City, UT | 19,040 | $129,600 | -5% | 2.12 |
| Houston, TX | 22,940 | $129,440 | -5% | 0.64 |
| Kansas City, MO | 12,160 | $124,990 | -8% | 1.02 |
| Pittsburgh, PA | 10,320 | $124,500 | -8% | 0.85 |
Location quotient compares how concentrated this occupation is in the metro against the national average. A value above 1 means the metro has more of this work than its size would predict.
The location quotient column is the more useful one for hiring. A high median tells you what a role costs; a high quotient tells you whether the people exist. San Jose, San Francisco, Seattle, Washington, D.C., Denver, Austin each have a quotient of 1.5 or above, meaning the work is concentrated there well beyond what the size of the local economy would predict. Those are the markets where a search is likely to be quick and competitive at the same time, and where a counter-offer is most likely to take a candidate off the table late in the process.
The opposite case is worth planning for too. In a metro with a low quotient, the total pool is small even when wages look reasonable, so the realistic options are to widen the search radius, accept a longer time to hire, or bring the capability in from outside the local market entirely. That last option is what most teams are weighing when they come to us.
Hiring risks worth naming
Every one of these has produced a bad hire somewhere. They are written down so that the process tests for them deliberately rather than discovering them in month three.
Front-end developer treated as a back-end hire. Shared language hides genuinely different skills. Test data modelling, transactions and failure handling explicitly.
No production operating experience. Ask about an incident they diagnosed. Blocking and memory problems only teach you under real load.
Single-instance habits. Ask what changes when the service runs on three instances. In-memory state is the answer to watch for.
Framework familiarity without runtime understanding. Ask about the event loop directly. Express experience does not imply it.
Hiring Node.js developers by metro area
Wages for this occupation vary more between US metro areas than most budget models assume. Each page below sets out the published employment and wage figures for that market, how it compares with the national picture, and what the local industry mix means for the kind of Node.js developer who will be available.
- New York, NY $166,830 median
- Seattle, WA $167,280 median
- San Jose, CA $213,110 median
- Washington, D.C. $154,930 median
- San Francisco, CA $186,640 median
- Dallas-Fort Worth, TX $133,290 median
- Los Angeles, CA $160,920 median
- Boston, MA $166,090 median
- Chicago, IL $134,380 median
- Atlanta, GA $132,960 median
- Austin, TX $134,120 median
- Phoenix, AZ $131,750 median
- Philadelphia, PA $133,040 median
- Minneapolis-St. Paul, MN $130,920 median
- Denver, CO $137,610 median
- Detroit, MI $130,760 median
- Houston, TX $129,440 median
- Charlotte, NC $135,920 median
- San Diego, CA $163,270 median
- Salt Lake City, UT $129,600 median
- Miami, FL $132,650 median
- Portland, OR $156,000 median
- Baltimore, MD $138,900 median
- Tampa, FL $130,450 median
- Orlando, FL $129,620 median
- Raleigh, NC $132,770 median
- Kansas City, MO $124,990 median
- Pittsburgh, PA $124,500 median
Frequently asked questions
Is Node.js suitable for a serious production back end?
For input and output bound work, which is most web back ends, yes, and it is used at very large scale. The question is not whether it is serious but whether your workload fits the model. Services that wait on databases and other services are a good fit. Services that do heavy computation on the request path are not, and the fix there is a queue rather than a different framework.
Can our front-end developers write our back end?
They can write code that runs, and the shared language genuinely lowers the barrier. What does not transfer is data modelling, transaction handling, failure behaviour and operating a service under load. Those are the skills that decide whether a back end survives its second year, and they should be tested for directly rather than assumed.
Should we use Express, Fastify or NestJS?
Express if you want minimal structure and the largest pool of familiar developers. Fastify if request throughput matters and you want validation built in. NestJS if the team is large enough that an imposed structure is worth the learning curve. All three are defensible, and the choice matters far less than the data access and error handling patterns underneath.
How do we handle CPU-heavy work in Node.js?
Move it off the request path. A queue with separate worker processes handles most cases, and it has the side benefit of making the work retryable and observable. Worker threads help for some in-process cases. If the workload is overwhelmingly computational, the honest answer is that another runtime will serve you better, and a good Node.js developer will tell you so.
How worried should we be about the dependency tree?
Enough to have a policy rather than enough to avoid packages. Commit lockfiles, audit on a schedule, keep upgrades continuous rather than annual, and require a reason before adding a dependency that does something small. The risk is real, and it is manageable with ordinary discipline.
Which Node.js version should we be on?
An active long-term support line, and not one that has passed its end-of-life date. The release table on this page is generated from public data so it reflects what is supported now. Running past end of life means no security patches, and it is one of the few technical facts that is genuinely non-negotiable.
Why does our Node.js service slow down under load when CPU looks fine?
Almost always something blocking the event loop. CPU graphs look unremarkable while every request queues behind one synchronous operation. The diagnostic is event loop lag, which most teams are not measuring, and the cause is usually a large parse, a synchronous file operation or a regular expression meeting hostile input.
Does using Node.js on both sides actually save money?
It saves some context switching and lets you share types and validation schemas, which is a genuine reduction in integration bugs. It does not mean any front-end developer can own the back end. The saving is real and smaller than it is usually claimed to be.