mjdeving
← writing

Low-code is not the opposite of code

The move from low-code to full-code is not a migration plan. It is a ladder, and every rung is optional.

Abstraction has always made software easier to build. Machine code gave way to programming languages; command lines gave way to graphical interfaces and visual canvases. AI changes the cost curve again: you no longer need to write every line by hand to own the code underneath.

This does not make low-code obsolete. It changes the point at which taking ownership becomes economical.

Start with the fastest abstraction that fits the problem. Then take back one layer only when testing, control, user experience, scale, compliance, or long-term ownership demands it. The move from low-code to full-code is not a migration plan. It is a ladder, and every rung is optional.

The five-layer model

A useful model separates five layers that a low-code platform can bundle:

  1. The backend receives requests and runs the application logic.
  2. The database stores, indexes, and retrieves data.
  3. The frontend is the interface people use.
  4. The AI layer covers model calls, LLMs, and embeddings.
  5. The infrastructure deploys, connects, and operates everything.

Implementation note: the AI-layer label is useful shorthand, not a claim that every AI responsibility belongs in one deployable component. The model provider may remain external. Prompts and tool calls still live in application logic; embeddings and retrieval touch the data layer; evaluations and failure handling belong to operations.

A low-code platform can bundle all five. Moving to code does not simply replace visual nodes with functions. It means taking responsibility for one or more layers: schemas, tests, authentication, deployment, monitoring, and recovery.

That ownership buys control and room to adapt, but it also creates work. A custom API is not automatically better than a managed workflow. It is better only when the requirements justify owning it.

Take back one layer at a time

The useful path is gradual. Every rung buys something and bills you for something.

RungWhat it buysWhat it bills from then on
Keep the bundleRunning in a dayNothing yet
Own the databaseA system of record that outlives the workflowSchema, migrations, backups
Own the frontendA surface shaped around the taskBuild, deploy, accessibility
Own the APILogic you can test before it shipsContracts, retries, traces, evals
Own the deploy stackThe choice of execution environmentAlerts, upgrades, recovery, cost

The bills accumulate. A system on the fourth rung is still paying the second one’s.

Whatever a rung does not take back stays where it is. The workflow can stay visual after the database moves; low-code can stay at the integration edge after the API moves; model services can stay external after the deploy stack moves.

This is one common sequence, not a dependency graph. Layers can be skipped or reordered: a purpose-built interface can still use a managed API and database, while an owned API can sit behind a platform interface. There is no requirement to reach the last step. The smallest ownership shape that meets the real requirement is usually the right one.

What should move the decision

Use concrete signals, not a preference for canvas or code.

  • The maintainer matters. A workflow owned by an operations team needs a surface that team can read and change. A developer-owned service can justify code, contracts, and CI.
  • Logic matters. A short integration flow fits a canvas. Shared domain rules, complex branching, and reusable modules become easier to test and review in code.
  • Testing matters. Run history is useful for diagnosis. Deterministic unit and integration tests become more important when a change can affect money, permissions, or many downstream steps.
  • Volume matters. Higher throughput, long-running jobs, queue pressure, or strict latency targets may justify a separate API or worker layer.
  • User experience matters. Forms and internal tools can stay bundled. A customer-facing product often needs an interface shaped around the task.
  • Compliance matters. Start with the required execution environment, contracts, data flows, retention, and access controls. Regulation does not choose an authoring tool for you.
  • Operations matter. Owning a layer also means owning its alerts, upgrades, backups, recovery, and cost controls.
  • Agents matter. An agent needs tool definitions, memory, evaluations, and traces. That is the part of the work that wants code, whatever surface the rest of the flow runs on.

Full code offers a higher control ceiling. It does not remove operational risk. It transfers more of that risk to you.

Read across the row, not down the column:

Low codeFull code
Hosting and maintenanceManaged for youYour responsibility, and it needs operational skill
BuildingVisual canvas, drag and dropCode in a repository
Getting startedRunning in a dayA steeper learning curve
FlexibilityBound by the nodes and their abstractionsIf you can describe it, you can build it
ControlThe vendor decides what is possibleYour code, your data, your execution environment
SecurityData passes through third partiesDepends on what you buy back

Five of those rows are one-directional. The last one is not, and it is the row people get backwards. Owning identity, credentials, and the datastore buys nothing a user can see, and the failure mode is a breach rather than a missing feature. So the common shape is to climb the ladder for the layers that differentiate and to buy the security substrate back deliberately, ending with more third parties than you started with, not fewer. What you keep is the boundary: one interface per bought service, so the provider stays replaceable. The condition that reverses this is a prescribed execution environment, which is the compliance signal above.

A stack is a set of replaceable parts

The tools below are examples, not a prescribed bundle.

For integration-heavy workflows, n8n provides a visual workflow surface, execution history, evaluations, and self-hosting options, with some capabilities varying by plan. It remains a strong fit when speed and non-developer maintenance matter more than custom behavior.

For user interfaces, React with Vite fits interactive applications. Astro fits content-first sites that need less client-side JavaScript. The interface can change without forcing a backend rewrite.

For TypeScript-native APIs, Bun provides the runtime and a built-in HTTP server. Hono is one higher-level API option with first-class TypeScript support across Bun and Cloudflare Workers. Trigger.dev tasks cover long-running background work with queues, configurable retries, and concurrency controls. Trigger.dev can run as a managed service or be self-hosted. This shape fits edge APIs, SaaS integrations, and TypeScript orchestration.

On the Python side, FastAPI uses Python type hints to convert and validate request data and generate an OpenAPI description. A deployed FastAPI application also needs an ASGI server such as Uvicorn. FastAPI’s own background-task guide points to Celery for heavier work across processes or servers. Celery sends tasks through a broker to separate workers; periodic work is scheduled separately, usually with Celery beat.

Celery overlaps with Trigger.dev at the queued background-work layer, but it is not Trigger.dev in Python. Celery is a task-queue and worker system. Trigger.dev also packages deployment, dashboard observability, scheduling, waits, and task lifecycle around the execution plane. With Celery, you provide a broker and operate workers. Add Celery beat for periodic schedules; monitoring and result storage are separate choices. Celery’s own task guide recommends idempotent task functions because messages can be redelivered. Pick one durable owner for a workflow; combine them only across an explicit component boundary.

The two shapes map role for role. The rows compare responsibilities; they do not claim the tools are equivalent.

ResponsibilityTypeScript-nativePython-native
LanguageTypeScriptPython
HTTP boundaryHono on Bun’s HTTP serverFastAPI on an ASGI server (Uvicorn)
Background workTrigger.dev tasks: queues, retries, concurrencyCelery: a queue plus separate workers
SchedulingPart of the task definitionCelery beat, configured separately
Operating modelManaged or self-hostedYou run the broker and the workers

Keep each component in one language. TypeScript fits JavaScript-native orchestration. Python fits AI, data, ML, and Python-native backends. Components communicate through typed JSON or OpenAPI contracts. For a narrow task, the boundary can be a bounded subprocess instead of a service, with typed inputs, typed outputs, and an explicit failure path.

For data, Postgres covers relational data and JSON. pgvector adds vector similarity search to the same database. Supabase can provide managed Postgres, authentication, storage, and an administrative interface when those services fit the deployment requirements.

For deployment, Docker keeps components portable. Railway is designed to reduce the work of running an application and database. Cloudflare fits edge workloads and static delivery. Hetzner offers a lower-level server option when control is worth the added operations work.

For visibility, Langfuse traces model calls and supports evaluation workflows. Sentry tracks application errors and performance. A canvas often exposes run history by default. A custom stack needs that visibility added deliberately.

Stop where the requirements stop

Low-code remains the better fit for many internal automations, integrations, and workflows maintained by non-developers. Full code earns its cost when a layer needs stronger tests, a custom interface, tighter control, independent scaling, or a specific execution environment.

The advantage of full code is its control and adaptation ceiling. The advantage of low-code is how much infrastructure and operating work you avoid owning. Start with the smallest system that fits. Take back a layer when a requirement gives you a reason.