Workflow systems are everywhere in modern applications - from simple approval processes to complex multi-step automations. While there are excellent commercial solutions like Temporal, Airflow, and n8n, sometimes you need something tailored to your specific needs. In this series, I'll walk you through building a complete workflow system from scratch using ASP.NET Core, HTMX, Alpine.js, and Hangfire.
By the end of this series, you'll have a fully functional workflow system with:
Before diving in, let's address the elephant in the room: why not use an existing solution?
When to Build Your Own:
When to Use Existing Tools:
For this series, we're choosing to build our own because it gives us complete flexibility and is an excellent learning opportunity.
Our workflow system will support several key concepts:
Nodes are the building blocks of workflows. Each node represents a discrete action or decision point. Examples:
A workflow is a directed graph of connected nodes. Data flows from one node to another through connections, with each node processing and transforming the data as needed.
The engine that interprets the workflow definition and executes nodes in the correct order, handling:
A web-based UI where users can:
Using Hangfire, we'll implement:
Here's what we'll be using:
Our system will be organized into several projects:
Mostlylucid.Workflow/
├── Mostlylucid.Workflow.Engine/ # Core workflow execution engine
│ ├── Models/ # Node, Workflow, Connection models
│ ├── Execution/ # Workflow executor and runtime
│ └── Nodes/ # Built-in node implementations
├── Mostlylucid.Workflow.Shared/ # Shared models and DTOs
├── Mostlylucid.Workflow.DbContext/ # Entity Framework context
└── Mostlylucid/ # Main web app (existing)
├── Controllers/WorkflowController.cs
├── Services/WorkflowService.cs
└── Views/Workflow/ # Workflow UI views
1. JSON-Based Node Definitions Nodes will be defined in JSON, making them easy to serialize, version, and share:
{
"id": "node-1",
"type": "HttpRequest",
"name": "Fetch User Data",
"inputs": {
"url": "https://api.example.com/users/{{userId}}",
"method": "GET",
"headers": {
"Authorization": "Bearer {{apiToken}}"
}
},
"outputs": {
"response": "{{result.body}}",
"statusCode": "{{result.statusCode}}"
},
"conditions": {
"onSuccess": "next-node-id",
"onError": "error-handler-node-id"
}
}
2. Graph-Based Execution Workflows are directed acyclic graphs (DAGs). The execution engine will:
3. State Persistence Every workflow execution will be tracked:
4. Extensibility The node system will be plugin-based:
To follow along with this series, you should have:
In the next post, we'll dive straight into building the core workflow engine. We'll create the models, implement the execution engine, and write our first workflow that can be executed programmatically.
This is going to be a fun journey! We'll build something genuinely useful while learning about graph algorithms, background processing, and modern web development patterns.
Building a workflow system from scratch is an ambitious project, but it's also incredibly rewarding. You'll gain deep insights into how modern automation platforms work, and you'll have a system that's perfectly tailored to your needs.
In Part 2, we'll start coding! We'll set up the project structure and build the core workflow engine that can execute our node-based workflows.
Stay tuned!
© 2025 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.