🌊 Flow Engine

Simple workflow framework for Node.js backends

Replace complex controller logic with easy-to-use workflow steps. Integrates seamlessly with Express, Fastify, Koa, and any Node.js framework.

🎯 Super Easy
🔗 Framework Agnostic
📦 Common Payload
🔄 Built-in Retries

Quick Start

Installation
npm install node-workflow-engine
# or
yarn add node-workflow-engine
Basic Usage
import { createFlow } from 'node-workflow-engine';

// Create a simple workflow
const flow = createFlow()
  .setCommonPayload({ apiKey: 'your-api-key' })
  .step('validate', async (data) => {
    if (!data.email) throw new Error('Email required');
    return { isValid: true };
  })
  .step('process', async (data) => {
    // Your processing logic here
    return { processed: true };
  });

// Execute the workflow
const result = await flow.execute({ email: 'user@example.com' });
console.log(result.success); // true
console.log(result.data); // { isValid: true, processed: true }

Framework Integration

Express.js Integration
import express from 'express';
import { createFlow, expressFlow } from 'node-workflow-engine';

const app = express();
app.use(express.json());

// Create user registration flow
const userFlow = createFlow()
  .setCommonPayload({ timestamp: new Date().toISOString() })
  .step('validate', async (data) => {
    if (!data.email || !data.password) {
      throw new Error('Email and password required');
    }
    return { isValid: true };
  })
  .step('createUser', async (data) => {
    const user = { id: 1, email: data.email };
    return { user };
  })
  .step('sendEmail', async (data) => {
    console.log(`Welcome email sent to ${data.user.email}`);
    return { emailSent: true };
  });

// Use as Express middleware
app.post('/api/users', expressFlow(userFlow), (req, res) => {
  res.json({
    success: true,
    user: req.flowResult.data.user
  });
});

app.listen(3000);
Fastify Integration
import Fastify from 'fastify';
import { createFlow } from 'node-workflow-engine';

const fastify = Fastify();

const orderFlow = createFlow()
  .setCommonPayload({ currency: 'USD' })
  .step('validateOrder', async (data) => {
    if (!data.items?.length) throw new Error('No items');
    return { valid: true };
  })
  .step('calculateTotal', async (data) => {
    const total = data.items.reduce((sum, item) => sum + item.price, 0);
    return { total };
  })
  .step('processPayment', async (data) => {
    return { paymentId: 'pay_123' };
  });

fastify.post('/api/orders', async (request, reply) => {
  const result = await orderFlow.execute(request.body);
  
  if (result.success) {
    return { order: result.data };
  } else {
    reply.code(400);
    return { error: result.error };
  }
});
Koa Integration
import Koa from 'koa';
import Router from 'koa-router';
import { createFlow } from 'node-workflow-engine';

const app = new Koa();
const router = new Router();

const authFlow = createFlow()
  .step('validateCredentials', async (data) => {
    if (!data.email || !data.password) {
      throw new Error('Invalid credentials');
    }
    return { valid: true };
  })
  .step('generateToken', async (data) => {
    const token = `jwt_${Math.random().toString(36).substr(2, 9)}`;
    return { token };
  });

router.post('/api/auth', async (ctx) => {
  const result = await authFlow.execute(ctx.request.body);
  
  if (result.success) {
    ctx.body = { token: result.data.token };
  } else {
    ctx.status = 401;
    ctx.body = { error: result.error };
  }
});
Standalone Usage
import { createFlow } from 'node-workflow-engine';

// Create a simple workflow
const workflow = createFlow()
  .setCommonPayload({
    environment: 'production',
    version: '1.0.0'
  })
  .step('step1', async (data) => {
    console.log('Executing step 1');
    return { step1Result: 'completed' };
  })
  .step('step2', async (data) => {
    console.log('Executing step 2');
    return { step2Result: 'completed' };
  });

// Execute the workflow
const result = await workflow.execute({
  input: 'test data'
});

console.log('Workflow result:', result);

Advanced Features

🔧

Middleware Support

Add authentication, logging, and other middleware that runs before each step.

flow.use(async (data, context) => {
  // Authentication middleware
  if (!data.apiKey) throw new Error('Unauthorized');
  context.metadata.userId = 'user_123';
});
🔄

Retry Logic & Timeouts

Built-in retry with exponential backoff and configurable timeouts.

flow.step('externalAPI', async (data) => {
  const response = await fetch('https://api.example.com/data');
  return { data: await response.json() };
}, { retries: 3, timeout: 10000 });
🔗

Step Dependencies

Define dependencies between steps to control execution order.

flow.step('fetchOrders', async (data) => {
  return { orders: await getOrders(data.user.id) };
}, { dependencies: ['fetchUser'] });
📊

Execution Tracking

Know exactly which steps executed, execution time, and detailed metadata.

const result = await flow.execute(inputData);
console.log(result.steps);        // ['validate', 'process', 'save']
console.log(result.executionTime); // 245
console.log(result.metadata);     // { requestId: 'exec_123' }

Real-World Examples

E-commerce Order Processing

Complete Order Flow
const orderFlow = createFlow()
  .setCommonPayload({ 
    currency: 'USD',
    taxRate: 0.08,
    shippingRate: 5.99
  })
  .step('validateOrder', async (data) => {
    if (!data.items?.length) throw new Error('No items in order');
    if (!data.customerId) throw new Error('Customer ID required');
    return { orderValid: true };
  })
  .step('checkInventory', async (data) => {
    for (const item of data.items) {
      const available = await checkStock(item.productId);
      if (available < item.quantity) {
        throw new Error(`Insufficient stock for ${item.productId}`);
      }
    }
    return { inventoryChecked: true };
  })
  .step('calculatePricing', async (data) => {
    const subtotal = data.items.reduce((sum, item) => sum + item.price, 0);
    const tax = subtotal * data.taxRate;
    const shipping = subtotal > 50 ? 0 : data.shippingRate;
    const total = subtotal + tax + shipping;
    return { subtotal, tax, shipping, total };
  })
  .step('processPayment', async (data) => {
    const payment = await stripe.charges.create({
      amount: Math.round(data.total * 100),
      currency: data.currency,
      source: data.paymentToken
    });
    return { paymentId: payment.id };
  })
  .step('createOrder', async (data) => {
    const order = await db.orders.create({
      customerId: data.customerId,
      items: data.items,
      total: data.total,
      paymentId: data.paymentId,
      status: 'confirmed'
    });
    return { order };
  })
  .step('sendConfirmation', async (data) => {
    await emailService.send({
      to: data.customer.email,
      template: 'order-confirmation',
      data: { order: data.order }
    });
    return { emailSent: true };
  });

// Use in Express route
app.post('/api/orders', expressFlow(orderFlow), (req, res) => {
  res.json({ order: req.flowResult.data.order });
});

API Reference

createFlow()

Creates a new workflow instance.

const flow = createFlow();

.setCommonPayload(payload)

Sets data available to all steps.

flow.setCommonPayload({ apiKey: 'your-key', version: '1.0' });

.use(middleware)

Adds middleware that runs before each step.

flow.use(async (data, context) => {
  console.log(`Executing step with data:`, data);
});

.step(id, handler, options?)

Adds a step to the workflow.

flow.step('process', async (data) => {
  return { processed: true };
}, { retries: 3, timeout: 5000 });

.execute(inputData)

Executes the workflow with input data.

const result = await flow.execute({ email: 'user@example.com' });