Let's talk

Backend

Hire Node.js Developers

Node engineers who run services in production and have been on call for the ones they wrote.

Node earns its place where a service is mostly waiting — on a database, a payment gateway, a device, another API — and where sharing types with the front end removes a whole class of integration bugs. Sazinga Comply runs on Fastify with Postgres, Rentals on Express and Sequelize, Factory on Node and TypeScript alongside Angular, so the operational lessons here are first-hand rather than borrowed.

The hard problems in a Node service are almost never about Node. They are about what happens when the same message arrives twice, when a third party answers slowly, and when two processes believe they are the only one running.

Idempotency, written as a claim rather than a check

The pattern we now use everywhere for anything that must happen once is to make the update itself the lock. Instead of reading a row, deciding it is unpaid and then writing, the write carries the condition — update the row where its status is not already paid — and only the caller that sees one affected row is allowed to continue.

That rule exists because of a real incident. A payment provider sent two different events for a single payment, they arrived 434 milliseconds apart, both handlers loaded the row while it still said “sent”, both passed a read-then-write guard, and both wrote to the ledger. The customer was credited twice for one payment. A unique constraint on the provider’s payment identifier is the backstop; the conditional update is what actually prevents it.

Read-then-act is not a concurrency control, however careful the reading is.

A webhook is a prompt to go and look

The second rule we hold to is that nothing is written on the strength of an inbound payload. A webhook says something may have changed; the service then asks the provider what the truth is. Providers document this themselves — one gives you five seconds to answer and retries three times over roughly twenty minutes before abandoning the message, and states that deduplication is the merchant’s problem.

The same scepticism applies to the browser. A redirect back to your site carrying a success parameter is timing information, not evidence: it can be lost, replayed, or typed by hand. It is a reason to refresh a screen and never a reason to record a payment.

Where a provider offers no webhook at all, a poll on a short cadence is the honest answer, and it needs an age gate so it does not race the thing it is reconciling.

Signatures, raw bodies and the parser that quietly breaks them

Any provider that signs its payloads signs the exact bytes it sent. A JSON body parser that has already run has destroyed those bytes — key order and whitespace do not survive a round trip — so signature verification will fail on every genuine message and pass on none.

The fix is to capture the raw buffer, and to do it only on the routes that need it, so no other endpoint pays the memory cost of holding its body twice. A related trap: not every provider sends JSON. One of ours sends successful and failed payment notifications as form-encoded data and only its refund and dispute events as JSON, so the raw capture has to cover both parsers.

The operational details that only come from running one

Scheduled jobs arm when the module is imported, not when your start function is called. That is correct on a deployed server and dangerous on a laptop: an engineer pointing a local API at a real database to look at something quietly becomes a second writer alongside the live instance. A single environment switch that wraps every schedule, defaulting to enabled so deployments are unaffected, costs nothing and prevents a genuinely bad day.

Error handlers deserve the same attention. Ours redacts authorisation headers and any body field named like a credential, because failed sign-ins and OTP checks land in the error handler with the credentials still in the request body. And it must be registered last, or the framework’s default handler answers every structured error with a generic 500.

One more, from our own contact service: when the database is unavailable the submission is written to disk exactly as it was before the database existed, logged loudly, and reconciled later by an idempotent job. We proved it by breaking the database on purpose and watching the visitor still receive a success. A fallback path that has never been exercised is a comment, not a fallback.

Where Node is the wrong choice

CPU-bound work — the event loop has one thread and a long synchronous computation stops everything else. Workloads needing hard real-time guarantees or tight memory ceilings. And teams that will not maintain a dependency policy, because the ecosystem’s convenience is also how a small service ends up with a supply chain it cannot audit.

What we interview for

Ask about failure. What happened the last time a queue backed up, how they found a memory leak, what their retry policy did to a downstream system. Engineers who have only built endpoints will describe frameworks. Engineers who have kept a service alive will describe backpressure, idempotency keys and the dashboard they wished they had built earlier.

Then the one that is hardest to fake: your handler runs twice for the same event. Walk me through everything that is now wrong, and what you would change so it cannot be.

Teams are built for companies in the United States and the Gulf — the UAE, Saudi Arabia, Qatar, Kuwait, Bahrain and Oman. The engineers are in Pune, which matters mostly for the clock. Dubai is ninety minutes behind us and Riyadh two and a half hours, so a Gulf team shares almost the whole working day. New York is nine and a half hours behind, so American engagements run on a written handover and one fixed overlap window rather than on a standing call — a real constraint, and better stated than discovered.

What these engineers do

  • Express, Fastify and NestJS services in TypeScript with typed request contracts
  • PostgreSQL access via Prisma, Sequelize or plain SQL, with migrations under review
  • Queues and background jobs in BullMQ, with idempotency and retry semantics designed
  • Structured logging, OpenTelemetry tracing and alerting that maps to real failure modes
  • Diagnosing event loop lag, memory growth and slow queries in a running system

Delivered AI-first

AI assistance is used to generate route handlers from an OpenAPI or schema definition, to write validation layers, repository code and integration tests, and to carry out framework migrations such as Express to Fastify one route at a time. Anything touching authentication, database transactions or money is written and reviewed with the same care as before. Review discipline is unchanged. The measurable effect is throughput per engineer, not fewer reviews.

Tell us what the Node.js work is.

Roughly what it involves, the seniority you need, and when it has to start. We will say what it takes to staff it, or say honestly that we are not the right people for it.

A person reads every enquiry and replies within one working day.