All articles

Inside OpenArca: Architecture, Stack and Design Decisions

P
Piotr Tomczak · Visio Lab / OpenArca
| | 13 min read

OpenArca was designed with one principle: keep architecture simple enough for real self-hosting. Every technology choice, every structural decision, and every deliberate omission follows from that idea.


Why architecture matters in open-source workflow tools

Most open-source projects don’t fail because of missing features. They fail because the architecture makes it too hard for people to deploy, understand, or contribute to the codebase.

A self-hosted tool that requires a Kubernetes cluster, three databases, and a message queue isn’t really self-hosted in any practical sense. It’s technically self-hostable — if you have the infrastructure team to support it. For the small developer teams OpenArca is built for, that distinction matters enormously.

The same applies to contributor experience. An open-source project with a clean, predictable architecture attracts contributions. One with layers of abstraction, custom frameworks, and undocumented internal conventions creates a wall that only the most determined developers will climb.

OpenArca’s architecture is shaped by these two constraints: it needs to be trivially deployable and immediately understandable. Everything else follows from there.


Core technology stack

Frontend: React 18, Vite, React Router, dnd-kit

The frontend is a React 18 single-page application built with Vite as the build tool and dev server. Routing is handled by React Router. Drag-and-drop interactions — used for Kanban board manipulation and TODO reordering — are powered by dnd-kit.

These choices are deliberately mainstream. React is the most widely understood frontend framework in the JavaScript ecosystem. Vite has become the standard build tool for new React projects, offering fast development feedback loops and straightforward configuration. React Router is the default routing solution most React developers already know.

dnd-kit deserves a specific mention because drag-and-drop is central to OpenArca’s interaction model. Developers reorder their TODO lists by dragging tasks. Tickets move across Kanban columns by dragging cards. dnd-kit was chosen for its accessibility support, composable API, and performance characteristics — it handles the kind of list-based drag-and-drop that workflow tools demand without the overhead of more general-purpose solutions.

There’s no custom component framework, no proprietary design system, and no unusual state management pattern. A React developer cloning the repository for the first time should be able to navigate the codebase within minutes, not hours.

Backend: Node.js 20, Express, SQLite, Zod, JWT

The backend is a Node.js 20 application running on Express. Data lives in SQLite via better-sqlite3. Request validation uses Zod schemas. Authentication is handled through JWT tokens combined with OTP-based passwordless login.

Express was chosen over newer alternatives like Fastify or Hono for a simple reason: it’s the HTTP framework that the widest number of Node.js developers already understand. When a contributor opens the route files, they see familiar patterns — middleware chains, request handlers, response objects. There’s no learning curve beyond the application’s own business logic.

Zod handles validation at the API boundary. Every incoming request is validated against a typed schema before it reaches business logic. This provides runtime type safety, clear error messages for API consumers, and documentation-as-code for the request format. It also prevents an entire class of bugs where invalid data slips into the database and causes problems downstream.

JWT tokens manage session state. Combined with the OTP login flow, this creates an authentication system that’s stateless on the server side — there’s no session store to manage, no Redis dependency for token storage, and no complexity around session synchronization in multi-instance deployments.

Supporting services: Docker Compose, Mailpit, Multer

The deployment target is Docker Compose. The entire stack — frontend, backend, and any supporting services — runs from a single docker-compose up command. There’s no orchestration layer, no service mesh, and no external dependency that needs to be provisioned separately.

Mailpit provides local email testing during development. When the OTP login flow sends an authentication code, Mailpit catches it locally so developers can test the full login experience without configuring an external email service. In production, this is replaced by whatever SMTP provider the team uses.

Multer handles file uploads — attachments on tickets, images, documents. It’s configured with sensible size limits and file type restrictions as part of the security-by-default approach.


Why SQLite?

This is the decision that surprises people the most, so it deserves a full explanation.

Most workflow tools — even those targeting small teams — default to PostgreSQL or MySQL. The reasoning is usually some combination of “it scales better,” “it’s more robust,” and “it’s what serious applications use.” OpenArca deliberately goes against this convention by using SQLite as its primary database.

The reasoning is practical, not ideological.

Zero operational overhead. SQLite is an embedded database. It runs as part of the application process, reading and writing to a single file on disk. There’s no database server to install, configure, monitor, or update. There’s no connection pooling to tune, no authentication to set up, no network configuration between the app and the database. For a self-hosted tool, this eliminates an entire category of deployment complexity.

Trivial backups. Backing up an SQLite database means copying a file. No pg_dump, no binary log management, no point-in-time recovery configuration. For a team of five developers self-hosting their workflow tool, “copy the file to your backup location” is the right level of complexity.

Perfect fit for the concurrency model. SQLite handles the read/write patterns of a small-to-medium team workflow tool without any issues. A team of fifteen developers isn’t generating thousands of concurrent writes per second. They’re creating tickets, updating statuses, reordering tasks — operations that SQLite handles comfortably with WAL mode enabled.

Local development parity. When a contributor clones the repo and runs the development server, they get the exact same database engine that runs in production. There’s no “works on my machine” gap caused by database version differences, extension availability, or configuration mismatches.

The trade-off is clear: SQLite won’t scale to thousands of concurrent users or massive datasets. That’s fine. OpenArca is designed for teams of 2–15 developers. For that scale, SQLite is not a compromise — it’s the optimal choice. If a team outgrows SQLite, they’ve also likely outgrown the workflow patterns OpenArca is designed for, and the migration path to PostgreSQL is well-documented and straightforward.


Why Node + React?

The technology choice for the core stack wasn’t about which framework is technically “best” — it was about optimizing for the largest possible contributor pool and the most predictable developer experience.

Contributor onboarding. JavaScript and TypeScript are the most widely spoken languages in the web development world. A full-stack Node.js + React application can be understood and contributed to by the largest possible pool of open-source developers. Choosing a less common stack — Elixir, Rust, Go — might offer technical advantages, but it would dramatically shrink the pool of people who can contribute meaningfully without learning a new language.

Rapid iteration. Node.js and React have mature ecosystems with well-tested libraries for virtually every common need. File uploads, form validation, drag-and-drop, date handling, PDF generation — there’s a maintained, documented library for all of it. This means development time goes toward building workflow features, not reimplementing infrastructure.

Predictable developer experience. When a developer opens an OpenArca PR, they know what to expect. Express route handlers. React components with hooks. Zod schemas for validation. CSS modules or Tailwind for styling. There are no custom macros, no code generation steps, no unusual build processes that require explanation. The code reads like what it is.

This predictability is a strategic choice. Every unusual pattern in a codebase is a barrier to contribution. OpenArca minimizes those barriers by using the most widely understood tools in the most conventional ways.


Workflow-first backend design

Many applications start with generic CRUD entities — users, items, comments — and layer business logic on top through controllers and services. OpenArca inverts this pattern. The backend is designed around workflow concepts first, with data storage as an implementation detail.

The core domain models aren’t abstract “resources.” They’re specific workflow objects: ticket lifecycle states, developer execution tasks, and the synchronization logic that keeps them aligned.

A ticket in OpenArca isn’t just a row in a database with a status field. It’s an entity with a defined lifecycle — a set of valid state transitions, ownership rules, and side effects that trigger when it moves from one state to another. When a ticket moves from “Ready” to “In Progress,” the system doesn’t just update a column. It evaluates what else needs to happen: does the developer’s TODO need to update? Does the Kanban view need to reflect a new active task? Are there notifications to send?

Developer execution tasks are modeled separately from tickets because they represent a different concern. A ticket is a unit of work from the team’s perspective. A task is a unit of execution from the developer’s perspective. The synchronization logic between them — which is one of OpenArca’s defining features — is explicitly modeled in the backend, not left as an implicit behavior that emerges from UI interactions.

This approach means that the business logic is discoverable by reading the code. A contributor looking at the ticket state machine can understand exactly what happens when work moves through the system. There’s no need to trace through layers of generic middleware to understand the workflow.


Security decisions

Security in OpenArca isn’t a module bolted onto the side. It’s embedded in the core application flows from the ground up.

Role-based access control governs what each user can see and do. Permissions are checked at the API layer, not just in the UI. This means that even if someone crafts a direct API request, the authorization rules still apply. Roles are intentionally simple — there’s no elaborate hierarchy of permission levels that requires documentation to understand.

Ownership validation ensures that users can only modify resources they have legitimate access to. Every mutation endpoint verifies that the requesting user has the right relationship to the resource being changed. This prevents the class of vulnerabilities where a valid authenticated user can access or modify another user’s data by manipulating request parameters.

Upload limits are configured at the infrastructure level. File size restrictions, allowed file types, and storage quotas are enforced by Multer before uploaded content reaches the application logic. This is a defense-in-depth approach — even if the application logic had a bug, the upload middleware would still enforce the constraints.

OTP passwordless login removes passwords from the system entirely. No password hashing configuration to get wrong. No password reset flows to secure. No credential database to protect against breaches. Users authenticate by receiving a one-time code via email, which generates a JWT for session management. This dramatically simplifies the security surface while providing a login experience that’s actually more convenient than traditional passwords.

The philosophy is that security defaults should be correct out of the box. A team deploying OpenArca shouldn’t need to read a security hardening guide to have a reasonably secure installation. The defaults should be safe, and the team should only need to make decisions about things that are genuinely team-specific — like which email provider to use for OTP delivery.


Design philosophy: what OpenArca deliberately avoids

Understanding what’s not in the architecture is as important as understanding what is.

No microservices. The entire application runs as a single process. For the scale OpenArca targets, microservices would add deployment complexity, operational overhead, and debugging difficulty without providing meaningful benefits. A monolithic architecture with clear internal boundaries is simpler to deploy, simpler to debug, and simpler to reason about.

No heavy orchestration. There’s no Kubernetes, no service mesh, no container orchestration beyond Docker Compose. A team should be able to run OpenArca on a single VPS, a NAS, or a spare machine under someone’s desk. The infrastructure requirements are intentionally minimal.

No ORM. Database queries use better-sqlite3 directly with parameterized SQL. This keeps the data layer transparent — you can read a query and understand exactly what it does without tracing through an ORM’s abstraction layers. For a codebase that prioritizes contributor readability, this directness matters.

No premature abstraction. The codebase resists the urge to create generic frameworks for things that are currently used in only one place. If a pattern appears once, it’s implemented directly. If it appears three times, it gets extracted. This keeps the code concrete and grounded in actual use cases rather than hypothetical future requirements.

The underlying principle is that complexity should be earned, not assumed. Every layer of abstraction, every additional service, every configuration option is a cost. OpenArca only pays that cost when the benefit is clear and immediate.


Summary

OpenArca’s architecture is a set of deliberate trade-offs optimized for two things: frictionless self-hosting and contributor accessibility. SQLite over PostgreSQL because operational simplicity matters more than theoretical scale. Node + React because contributor reach matters more than technical novelty. A monolithic design because deployment simplicity matters more than architectural fashion.

Every choice serves the same goal: a workflow tool that a small team can deploy in minutes, a new contributor can understand in an afternoon, and the project can maintain without accumulating accidental complexity.

The architecture isn’t clever. It’s intentionally ordinary — and that’s precisely what makes it work.


Complexity should be earned, not assumed. OpenArca earns its simplicity by choosing the right tool for the actual problem, not the theoretical one.

Try OpenArca — free and self-hosted

Open source under AGPL-3.0. Deploy with Docker in minutes.

View on GitHub