Where Node.js fits and where it does not
Node.js is built around a non-blocking event loop, which makes it efficient for I/O-heavy workloads: REST and GraphQL APIs, real-time chat and location updates, webhook processing and backend-for-frontend layers for mobile apps. Using TypeScript on both client and server also reduces handoff friction for small and mid-sized teams.
CPU-heavy work such as video transcoding, large numerical jobs or ML training is a poorer fit for the main event loop. We move that work to worker threads, separate services in Go or Python, or managed cloud services, and keep Node.js as the orchestrating API.
| Strong fit | Use with care | Better elsewhere |
|---|---|---|
| Mobile and web APIs | Image resizing in worker threads | Video transcoding pipelines |
| Real-time messaging and presence | PDF and report generation via queues | Model training and heavy data science |
| Webhooks and third-party integrations | Large CSV imports with streaming | Low-level systems programming |
| Backend-for-frontend aggregation | Long-running scheduled jobs | Hard real-time control systems |
Frameworks and project structure
We run production services on Active or Maintenance LTS releases of Node.js and plan upgrades before a release reaches end of life. Framework choice depends on scope rather than habit.
- NestJS for larger domains that benefit from modules, dependency injection and consistent conventions across a team.
- Fastify for lean services where throughput and schema-based validation matter most.
- Express only when extending an existing codebase; we do not start new projects on it by default.
- ORM and data access with Prisma or Drizzle on PostgreSQL, with versioned migrations checked into the repo.
- Validation with Zod or JSON Schema at every boundary, generating OpenAPI documentation from the same definitions.
Queues, events and real-time features
Anything that does not need to finish inside the user's request goes to a queue. We commonly use BullMQ on Redis for application jobs, and Amazon SQS or RabbitMQ when the infrastructure calls for it. Jobs are idempotent, retried with backoff and sent to a dead-letter queue after repeated failures, so a flaky email provider never corrupts data.
Real-time features such as chat, driver tracking or live order status use WebSockets or server-sent events, scaled horizontally through Redis pub/sub. These patterns show up often in on-demand app development, where dispatch and location updates must reach many clients within seconds.
Security and compliance considerations
- Authentication with short-lived access tokens, rotating refresh tokens and OAuth/OIDC providers where appropriate.
- Authorization checks at the service layer, tested against the OWASP API Security Top 10 risks such as broken object-level authorization.
- Secrets in a managed vault, never in environment files committed to the repo.
- Lockfiles, automated dependency updates and vulnerability scanning to manage npm supply-chain risk.
- Audit logging and encryption at rest for regulated data, designed with HIPAA, PCI DSS or SOC 2 requirements in mind when the product needs them.
For payment and banking products, our fintech app development page covers additional controls such as idempotent payment APIs and reconciliation jobs.
Testing, performance and observability
Services get unit tests with Vitest or the built-in node:test runner, integration tests against a real PostgreSQL instance in containers, and HTTP tests with Supertest. Where mobile and backend teams release independently, contract tests make sure an API change cannot silently break an app version still installed on users' phones.
Before launch we load test critical endpoints with k6, then watch event loop delay, p95 latency, database query time and queue depth in production through OpenTelemetry. Most performance problems we find are N+1 queries, missing indexes or synchronous work that belongs in a queue, not Node.js itself.
Data modeling and database practices
PostgreSQL is our default database because it handles relational data, JSON documents, full-text search and geospatial queries in one system. Schema changes go through reviewed migrations that are safe to run while the previous app version is still serving traffic, which matters when older mobile builds stay installed for months.
- Transactions and the outbox pattern so a database write and the event that follows it cannot get out of sync.
- Indexes driven by real query plans, reviewed with EXPLAIN before and after each feature that adds a hot query.
- Redis caching for expensive reads with explicit expiry and invalidation, never as the only copy of important data.
- Tenant isolation through tenant keys on every table or row-level security policies for multi-tenant products.
Route optimization, shipment tracking and inventory sync are common examples where these patterns carry real weight; our logistics app development work relies on them heavily.
How Node.js connects to the rest of our stack
Node.js is the default backend for the clients we build in React for the web and React Native for mobile, and it works equally well behind Flutter, Swift and Kotlin apps through a typed OpenAPI contract. We deploy on containers, serverless functions or managed platforms depending on traffic patterns and the client's cloud.
For SaaS products, multi-tenant data models, billing integrations and role-based access are part of the backend design; our SaaS app development service describes how we approach those decisions. Backend effort typically accounts for a substantial share of overall app cost, which our app development cost guide breaks down by feature area.