Skip to content

Engineering choices

What we build with, and why each choice was made

A logo wall tells you nothing. What matters is the reasoning behind a stack — when it is the right fit, when it is not, and what it costs to leave. This page states both.

Selection criteria

How a technology gets chosen

Applied before any stack decision, including when a client already has a preference.

01

Boring where it does not differentiate

Novel technology is spent where it creates advantage. Everywhere else, mature and well-documented wins — because that is what your team will be maintaining.

02

Maintainability is a selection criterion

A stack your team cannot hire for or reason about is the wrong stack, however elegant. We will recommend one we do not personally prefer when it fits your organisation better.

03

Managed services until they stop paying

Managed databases, queues and identity are cheaper than operating your own until scale or a specific constraint changes the arithmetic. That point is measured, not guessed.

04

Every dependency is a liability

Each library adds update burden, security surface and a licence position. We check maintenance status, release cadence and licence before adding one, and prefer the standard library where it suffices.

05

Decisions get written down

Significant choices are recorded with the alternatives considered and the reasoning, so the team inheriting the system knows which assumptions would justify revisiting them.

06

Exit cost counts

We prefer portable formats, open protocols and infrastructure as code, so that changing supplier or provider later is a project rather than a rebuild.

The full framework is published as ADR-001 in our engineering playbook (opens in a new tab), and our current greenfield recommendations — with what we would avoid — are in the technology radar (opens in a new tab).

The stack

Grouped by what it is actually for

Web and interface

Chosen for per-route control over rendering, which is what governs load time, crawlability and hosting cost more than any other decision.

  • Next.jsApp Router; rendering strategy chosen per route
  • ReactServer components by default, client only where interaction requires it
  • TypeScriptStrict mode; types are part of the specification
  • Tailwind CSSTokenised design system rather than ad-hoc styling
  • Headless CMSSo content changes do not require a deployment

Services and APIs

Selected per workload and per team. The deciding question is which stack your people can still maintain in three years, not which is fashionable.

  • .NETLong-lived business systems with strong typing and tooling
  • Node.jsShares language and types with the front end
  • PythonData, machine learning and automation workloads
  • REST and GraphQLVersioned, documented contracts either way
  • OpenAPIContract first, so clients and tests generate from one source

Data and storage

Relational by default. Most products described as needing a specialised store need a well-indexed relational schema and a clear model.

  • PostgreSQLDefault choice; tenancy isolation chosen per product — Upkilo uses schema-per-tenant (ADR-004)
  • SQL ServerWhere an existing estate or licensing makes it the right fit
  • RedisCaching, queues and rate limiting
  • dbtModelled analytics layer with tests on assumptions
  • Time-series storesTelemetry and IoT ingestion at volume

Cloud and platform

Everything is defined as code so environments are reproducible and changes are reviewable. We hold no reseller position with any cloud provider.

  • AzureCommon where Microsoft identity and licensing already exist
  • AWSBreadth of managed services and regional coverage
  • TerraformEnvironments as reviewable, reproducible code
  • Docker and KubernetesUsed when workload genuinely warrants orchestration
  • GitHub ActionsBuild, test, scan and deploy with promotion and rollback
  • OpenTelemetryVendor-neutral metrics, logs and traces

Mobile

Shared codebase where the product is data and forms; native where it depends on platform capability or sustained performance.

  • React NativeOne codebase, consistent behaviour across platforms
  • ExpoBuild and release tooling that keeps shipping routine
  • Swift and KotlinNative modules where the platform demands them
  • SQLiteLocal persistence for offline-first behaviour

AI and emerging technology

Evaluation harness before model selection. The simplest approach that passes the quality bar wins, and cost is modelled before build.

  • Hosted LLM APIsWith documented data-retention and residency positions
  • Vector searchRetrieval with citations back to source passages
  • PyTorch and scikit-learnPredictive and forecasting models
  • MQTT and edge runtimesIoT collection that tolerates network loss
  • Permissioned ledgersOnly where the trust model genuinely requires one

A note on this website

This site is built with the same stack described above: Next.js with the App Router, React server components, TypeScript in strict mode and a tokenised Tailwind design system. Scroll animation is driven by a single IntersectionObserver and CSS transitions rather than an animation library, so motion across the site costs almost no JavaScript. Structured data, metadata and the sitemap are all generated from the same typed content objects the pages render, which is why the markup cannot drift from what you see.

It is a reasonable sample of how we work: the constraints we accept, the things we refuse to fake, and the standard we hold a build to.

Considering a stack decision?

Build, buy or configure; rebuild or remediate; one cloud or two. These decisions are worth an independent opinion before they are made, not after.

Prefer email? hello@kornspire.com