Node.js Development: APIs, Real-Time Apps and Honest Trade-offs

How we build APIs, real-time features and integrations on Node.js with TypeScript, which frameworks we choose and why, and the cases where we recommend another stack.

An event loop ring with tasks circling it, I/O resources around it, and one heavy block moved off onto a side track.

You need a server that handles lots of concurrent work well: API calls from web and mobile clients, open WebSocket connections, webhooks from payment providers, streams of events from devices. Node.js development is a strong fit for that kind of I/O-heavy system, and with TypeScript it lets one team work in one language from the browser to the database.

Good Node.js work is more than choosing a framework. It means strict TypeScript with runtime validation at the edges, an event loop that is never blocked, real-time features that keep working once there is a second server, a dependency tree you have reason to trust, and the honesty to pick another tool when Node is not the right one.

What we build with Node.js

  • REST and GraphQL APIs for web products and mobile apps, including backends for React Native apps, where sharing TypeScript types and validation schemas between app and server means fewer integration bugs.
  • Real-time features: live dashboards and statistics, chat, notifications, delivery tracking and collaborative editing.
  • Integration layers connecting payment providers, CRMs, ERPs, e-commerce platforms and internal systems, with webhook handling, retries and data mapping.
  • E-commerce services: carts, checkout orchestration, inventory sync and order events.
  • Admin panels, content tools, and plugins that extend existing platforms.
  • Server-side rendering for frontend frameworks such as Next.js and Angular, which run on Node on the server.

Why Node.js suits I/O-heavy work, and where TypeScript fits

Node runs your JavaScript on a single main thread with an event loop. While one request waits for the database or another API, the thread serves others, so a single process can hold many concurrent connections with little overhead. That is why Node does well with APIs, proxies and real-time servers, which spend most of their time waiting.

One shared template shape projected into ports on a browser, a phone and a server, with a mismatched token turned away.

The same design is its main weakness. CPU-heavy work on that thread, such as parsing a huge file, resizing images in pure JavaScript, or a regular expression that backtracks badly, stalls every other request. We move heavy work into worker threads, background jobs or separate services, and monitor event loop delay in production so a blocked loop shows up on a dashboard before it shows up in support tickets.

Because each process has one main thread, on a multi-core machine you run several processes, usually as container replicas behind a load balancer, which also makes rolling deployments straightforward. Unhandled promise rejections crash the process by default, and we treat that as a feature: the orchestrator restarts it, and the error reaches the tracker with its context instead of leaving the service in an unknown state.

We write Node services in strict TypeScript. Types catch mistakes at compile time, but they vanish at runtime, so everything that crosses the boundary (request bodies, webhooks, queue messages, environment variables) is validated with a schema library such as Zod or TypeBox. In a monorepo, those schemas are shared with the web and mobile clients, so a renamed field breaks the build instead of the app.

Recent Node.js releases can run TypeScript files directly by stripping the types, which is handy for scripts and tooling. Services still get a full type check in CI.

Frameworks: Express, Fastify, NestJS and Hono

Framework Strengths Trade-offs
Express Minimal, known to every Node developer, huge middleware ecosystem Few conventions, so structure depends on team discipline; older codebases often need modernizing
Fastify Fast, with schema-based validation and serialization, a clean plugin system and built-in structured logging Smaller ecosystem than Express; plugin encapsulation takes some getting used to
NestJS Opinionated modules, dependency injection, guards and pipes; holds up well with large teams More abstraction and boilerplate, and heavy reliance on decorators
Hono Small, built on Web standard APIs, runs on Node, Bun, Deno and edge platforms Younger ecosystem, with fewer built-in pieces for large applications

Our rule of thumb: Fastify for lean, high-throughput APIs; NestJS when a larger team needs enforced structure, which will feel familiar to anyone who knows Angular; Hono for edge functions and small services; Express when extending an existing Express codebase.

For data access we use Prisma or Drizzle for most work and plain SQL for complex reporting, and we always read the queries an ORM generates. Background jobs usually run on BullMQ with Redis. When GraphQL is the better fit for your clients, we add depth and cost limits and use persisted queries in production, so a single expensive query cannot take the service down.

Real-time features that survive production

Real-time demos are easy; real-time systems are not. The first decision is the transport. Server-Sent Events are simpler when updates flow one way, as with live dashboards, notifications or progress bars: they run over plain HTTP and the browser reconnects on its own. WebSockets are needed for two-way traffic such as chat, multiplayer interactions and collaborative editing, and Socket.IO adds rooms, acknowledgments and reconnection on top.

Two servers with many long-lived client connections, linked by a pub/sub hub that relays a message between them.

The hard parts arrive with the second server. A message published on one instance has to reach clients connected to another, so we add a pub/sub layer, usually Redis, and configure the load balancer for long-lived connections.

Connections must be authenticated, clients must resume after a dropped connection without losing or duplicating messages, and slow consumers must not exhaust server memory. For collaborative editing, conflict-free replicated data types such as Yjs merge concurrent edits far more reliably than hand-written locking.

How we run Node.js development projects

  1. Discovery. Beyond features, we map the load profile: concurrent users and connections, peak patterns, external APIs and their rate limits, and anything CPU-heavy that should stay off the event loop.
  2. Contract first. The API is described in OpenAPI or shared TypeScript schemas before implementation, so clients can be built in parallel.
  3. Build. Features ship as vertical slices, with unit tests on Node’s built-in test runner or Vitest, integration tests against real databases in containers, and code review on every change.
  4. QA and hardening. QA testers exercise the API directly and through the clients. We load-test at expected peaks and review dependencies: committed lockfiles, reproducible installs, vetted new packages, restricted install scripts and alerts for known vulnerabilities. Supply-chain attacks on npm packages are a real threat, not a theoretical one.
  5. Launch. Services run in containers on an LTS release of Node, with structured logging through Pino, OpenTelemetry tracing, health checks and graceful shutdown, so deployments do not drop requests.
  6. Iterate. Dashboards, error tracking and regular written reports drive the next priorities.

A typical team pairs Node.js engineers with a business analyst and QA testers, plus frontend or mobile engineers when we build the clients too. For the wider picture of API design, data modeling and security, see our backend development guide.

When Node.js is the wrong tool

We like Node, but it is not the answer to every server-side problem:

  • CPU-bound workloads such as video transcoding, large-scale numerical work or machine learning. Node can call native libraries well, but for sustained heavy computation Go or Rust fit better, and Python is the practical choice for machine learning because that is where its ecosystem lives.
  • CRUD-heavy business applications with standard admin screens, auth and reporting, where a batteries-included framework ships faster; Laravel is often the quicker route.
  • Organizations standardized on .NET or Java, where a lone Node service becomes the odd one out for operations, security reviews and hiring.
  • Teams with nobody fluent in JavaScript or TypeScript who will maintain the code in-house. The best stack is one your people can own.

Frequently asked questions

Is Node.js fast enough for large applications?

For I/O-heavy work, yes. It handles high concurrency with modest resources and scales horizontally by adding instances. Its limits show up in sustained CPU work, which belongs off the main thread or in another service.

JavaScript or TypeScript?

TypeScript in strict mode, for anything that will outlive a prototype. The compile-time checks and shared types repay the setup quickly.

What about Bun or Deno?

Both are credible runtimes. For long-lived client systems we default to Node.js for its mature ecosystem and predictable long-term support, and we favor libraries that keep other runtimes open as an option later.

Serverless functions or long-running servers?

Serverless suits spiky, event-driven work such as webhooks, scheduled jobs and file processing. Long-running servers suit steady traffic, WebSockets and anything that holds connections open. With serverless, plan for cold starts and database connection limits, which a connection pooler usually solves.

Can you take over our existing Node.js codebase?

Yes. We start with an audit of dependencies, Node version, test coverage, error handling and event loop health, then fix the risks in order of impact while features keep shipping.

If you are planning an API, a real-time feature or a Node.js service that has outgrown its first version, tell us about your product. We will tell you honestly whether Node is the right tool, and what we would build with it if it is.