← Our services Backend systems · technically

The request ends. The responsibility does not

Backend engineering is the work of preserving meaning across boundaries: a transaction that commits once, a tenant that cannot see another tenant, a job that can be retried safely, an API that can evolve, and a failure that leaves enough evidence to recover. We choose frameworks, databases, queues and runtime topology around those guarantees — not the other way around.

The implementation map

What the system has to keep true.

These are backend responsibilities before they are technology choices. The tools show where our work has landed; each linked section explains the engineering contract behind them.

LayerWhat we use
Framework & system shapeRailsPayloadSvelteKit serverModular monolithsServices
API & contractsRESTOpenAPIGraphQLRPCWebhooks
Data architecturePostgreSQLD1 / SQLitepgvectorRedisS3 / R2
TDD & verificationMinitestRSpecVitestPlaywrightWebMock
Jobs, events & integrationsSolid QueueSidekiqBullMQCloudflare Workflows
Security & tenancyOAuth 2JWTPolicy authorizationRate limitsEncryption
Runtime topologyCloudflare WorkersECS / FargateKamalServerlessContainers
Delivery & infrastructureDockerAWS CDKMigrationsCI/CDHealth checks
Observability & recoveryStructured logsNew RelicMetricsJob dashboardsBackups
Framework & system shape

Choose the system shape before the framework.

The first backend decision is not Rails versus Node. It is where consistency has to be immediate, which work can happen later, what the team must operate, and how many independent failure domains the product can afford. We usually begin with a modular monolith because one deployable unit keeps transactions, refactoring and operational ownership legible. We separate services only when a boundary earns independent scaling, security, release cadence or runtime requirements.

What drives the choice
  • Domain depth, transaction boundaries and the rate the model will change Domain modelTransactions
  • Request, job, streaming and scheduled execution profiles HTTPWorkersSchedulers
  • Existing team fluency and the system already in production OwnershipChange cost
  • Deployment target, cold-start budget and connection model Long-runningServerlessEdge
  • The smallest architecture that can meet isolation and scale requirements Modular monolithServices
Where our work has landed

Rails gives long-lived transactional products a coherent domain model, mature migrations, policy authorization and background work in one system. Payload gives TypeScript products a typed schema, generated admin surface and local server API beside REST and GraphQL. SvelteKit on Cloudflare Workers is useful when a small backend can live close to its users and depend on platform bindings rather than long-running processes.

These are implementation choices, not agency boundaries. We can inherit another serious backend framework and apply the same decision model without first replacing it.

A service boundary has to pay for its network hop, deployment surface and new failure mode. “Microservice” is not a synonym for well-structured code.

API & contracts

Make the boundary hard to misunderstand.

An API is a compatibility promise. We specify its inputs, outputs, authentication, errors and evolution rules where they can be tested, then generate documentation and clients from the same contract. REST is the default for durable public boundaries; GraphQL, RPC and framework-local APIs are selected when their coupling and query model are explicit rather than accidental.

  • Versioned HTTP resources with stable error envelopes and status semantics RESTJSON
  • Executable request specifications that generate the published schema OpenAPIRSWAG
  • Typed clients generated from the contract instead of handwritten twice OpenAPI generators
  • Graph-shaped queries where clients genuinely control data selection GraphQL
  • In-process and RPC calls reserved for deliberately coupled services Local APIRPC
  • Webhooks signed, replay-safe and explicit about delivery acknowledgement HMACEvent IDs
Contract evolution
  • Additive changes stay backward compatible
  • Breaking changes receive a migration path and measurable adoption window
  • Authorization is tested at the endpoint, not inferred from the UI
  • Generated clients and examples are rebuilt in CI

The schema is useful only when production behaviour and the published contract can fail the same test.

Data architecture

Choose storage from the consistency model.

PostgreSQL is our default because most product data has relationships, constraints and changes that need to commit together. We add document, search, vector, cache and object stores for access patterns they serve better—not to avoid modelling the source of truth. Each additional store needs an owner, a derivation path and a repair strategy.

The data responsibilities
  • Relational source of truth with foreign keys and explicit transaction boundaries PostgreSQLActive RecordDrizzle
  • Edge-local relational storage for bounded serverless applications Cloudflare D1SQLite
  • Search and semantic retrieval derived from canonical records Elasticsearchpgvector
  • Ephemeral coordination, counters and hot reads kept out of the primary path RedisSolid Cache
  • Large encrypted objects stored outside database rows and delivered by signed access S3R2Signed URLs
Changing production data
  • Expand and contract schemas across compatible releases
  • Backfills are resumable, observable and safe to run more than once
  • Indexes are introduced with realistic query plans and lock behaviour
  • Derived stores can be rebuilt from the source of truth
  • Sensitive data has an explicit encryption and retention lifecycle

“Non-relational” is not one database category. A document, cache, vector index and object store solve different problems and fail in different ways.

TDD & verification

Drive the design through executable behaviour.

Test-driven development is most valuable at the backend boundaries where a small ambiguity becomes durable data. We write the next behaviour as an example, implement the smallest coherent change, then refactor with the contract held in place. The suite is layered so domain feedback stays fast while requests, databases, workers and deployments receive the integration evidence they need.

  • Domain rules, state transitions and service objects exercised without HTTP MinitestRSpecVitest
  • Request tests covering validation, response shape, authorization and tenancy Request specsOpenAPI
  • Integration tests against real migrations, database constraints and queue adapters PostgreSQLD1Redis
  • External failures made deterministic at the network boundary WebMockTest doubles
  • Race conditions tested where uniqueness and last-writer behaviour matter Concurrency tests
  • Deployed critical paths checked as consumers see them Playwright APISmoke tests
A release can be blocked by
  • A contract regression or undocumented response change
  • A migration that cannot roll forward safely
  • A broken tenant or authorization boundary
  • A worker that loses, duplicates or permanently hides failed work
  • A critical production smoke test failure

Coverage is evidence of execution, not evidence of the right assertions. We optimise for meaningful boundaries and failure cases.

Jobs, events & integrations

Design what happens after the response returns.

A job queue is not a reliability strategy by itself. Workers need idempotency, bounded retries, visible terminal failure and enough context to reconcile with the system of record. The same rules apply to schedules, notifications, webhooks, ingestion pipelines and third-party APIs: acknowledge only what is durable, assume delivery can repeat, and preserve a path to repair.

Execution patterns
  • Database-backed work close to a transactional Rails application Solid Queue
  • Redis-backed workers with priority, scheduling and operational dashboards SidekiqBullMQ
  • Durable multi-step serverless work with persisted progress Cloudflare Workflows
  • Scheduled sweeps that record their decision window before dispatch CronSchedulers
  • Fan-out through domain events rather than controller side effects EventsNotifiers
Failure contract
  • Every retried operation has an idempotency key or equivalent guard
  • Retries use bounded backoff and classify permanent failures
  • Poison work moves to an inspectable failed state
  • Webhook signatures and event identifiers are verified before mutation
  • Reconciliation can compare local truth with the external provider

At-least-once delivery is common. Exactly-once business effect is something the handler earns through its data model.

Security & tenancy

Make access control part of every query.

Authentication proves an identity; authorization proves that identity may perform this action on this record in this tenant. We keep those decisions on the server boundary, scope data before it is returned, rate-limit expensive or abusable paths, and record access to sensitive material. Security is implemented as testable application behaviour and reinforced by the runtime—not postponed to an infrastructure checklist.

  • Session, OAuth, JWT and API-key authentication selected by client and trust model OAuth 2JWTBetter Auth
  • Policy checks over action, role, record and tenant PunditPayload access
  • Tenant isolation applied centrally and tested against cross-tenant identifiers Scoped queriesActsAsTenant
  • Abuse constrained before expensive work or credential verification Rack::AttackRedis rate limits
  • Sensitive fields and files encrypted with rotatable key boundaries Envelope encryptionKMS
  • Administrative reads and mutations retained as attributable events Audit logsAmendments
Security gates
  • Authorization is denied by default
  • Secrets never enter public configuration or structured request logs
  • Webhook and upload capabilities expire and are scoped
  • Static analysis and dependency checks run in CI
  • Deletion and retention behaviour is explicit for regulated data

A tenant ID in a request is user input. Isolation begins with the authenticated context, not with trusting that parameter.

Runtime topology

Place each workload where its constraints fit.

Serverless and containers are runtime choices, not competing ideologies. Edge workers are excellent for bounded request work with platform storage and durable orchestration. Long-running containers are the better fit for connection pools, background workers, browser automation, media processing and workloads that need specialised binaries. We can combine both when the trust or execution boundary demands it.

Serverless when
  • Requests are stateless, bounded and benefit from global placement Cloudflare Workers
  • Storage and coordination are available as explicit platform bindings D1R2Queues
  • Durable work can be expressed as steps rather than a resident process Workflows
Containers when
  • The application owns a long-lived server or database connection pool RailsPayloadPumaNode
  • Workers need stable concurrency, memory or filesystem behaviour SidekiqBullMQ
  • Untrusted files or browser processes require isolated execution Container poolsSandboxing
  • Migrations must complete before traffic reaches the new schema Migrator task

Containerization packages a workload. It does not decide whether that workload should be long-running, independently scaled or publicly reachable.

Delivery & infrastructure

Ship the application and its dependencies together.

A backend release changes code, schema, workers, configuration and sometimes network topology. We describe infrastructure in code, build immutable artifacts, sequence migrations explicitly, and make health checks prove more than process existence. Deployment is complete only when the new version can serve traffic, its workers understand the schema, and rollback has a defined data story.

  • Repeatable images with separate server, worker and migrator entry points DockerMulti-stage builds
  • Reviewable cloud topology and permissions expressed as source AWS CDKCloudFormation
  • Edge bindings and environments rendered from validated configuration WranglerCloudflare
  • Migrations applied as an explicit release phase before dependent traffic Rails migrationsPayload migratorDrizzle
  • Health checks covering database and critical dependency readiness ReadinessLiveness
  • Small releases with traceable rollback and environment parity CI/CDKamalECS
Before traffic moves
  • Configuration contract is complete without exposing secrets
  • Database changes remain compatible with the previous application version
  • Workers and schedules are deployed in the intended order
  • Health checks and smoke tests pass against the deployed environment
  • Rollback ownership and data consequences are known

A green image build is not a green release. The system is the artifact plus the schema, bindings, workers and traffic policy around it.

Observability & recovery

Leave enough evidence to recover.

Observability starts with the questions an operator must answer: which tenant or request failed, what changed, whether the failure is spreading, and what can be retried safely. We connect structured events, service metrics, traces, job state and deployment identity so an alert points toward a decision. Recovery then turns that evidence into tested procedures for replay, rollback and restoration.

Operational signals
  • Structured logs carrying request, tenant, job and release correlation JSON logsCorrelation IDs
  • Latency, throughput, saturation and error rates by critical path MetricsService levels
  • Traces across API, database, queue and external calls OpenTelemetryNew Relic
  • Queue depth, age, retries and terminal failures visible to operators Mission ControlBull Board
  • Audit and domain events retained separately from diagnostic logs Audit trailDomain ledger
Recovery paths
  • Alerts map to a user or business impact and a named owner
  • Failed work can be inspected and replayed without duplicating effects
  • Backups and point-in-time recovery are exercised, not merely enabled
  • Derived indexes and caches have rebuild procedures
  • Runbooks record rollback, reconciliation and escalation decisions

The useful question is not “do we have logs?” It is “can the person on call decide what is safe to do next?”

Where this comes from

We operate the systems after handover.

These practices come from running multi-tenant commerce platforms, community products, AI workspaces, event discovery systems and encrypted document vaults — across Rails, Payload, PostgreSQL, Redis, AWS containers and Cloudflare's serverless runtime.

Owning those systems after launch is why the page is opinionated about executable API contracts, migrations, tenant isolation, idempotent workers, dedicated migrators, encryption boundaries and recovery evidence. They are the details that decide whether a failure becomes a brief incident or corrupted state.

What we have built
How we work

An AI-native team, with a factory behind it.

Team Foundry is our software factory — what the team uses day in, day out to deliver projects. It accelerates the work and validates it: every change arrives with the checks it passed, the session that produced it, and a person accountable for it.

How Team Foundry works
Factory

Let's find the work AI should be doing in your organization.

Whether you are exploring an idea or improving a system already in use, we’ll help you decide what is worth doing next.

Prefer to start async?

hello@thoughtfulrobots.ai

Hyderabad · Remote

Questions before you book?

Read the FAQ

Follow along

START A CONVERSATION

Tell us about the work.

A few lines about your product and where AI might belong. We usually reply within one business day.