diff --git a/workflows/Create Dynamic API Gateway with HTTP Router and Workflow Orchestration-9165/create_dynamic_api_gateway_with_http_router_and_workflow_orchestration.json b/workflows/Create Dynamic API Gateway with HTTP Router and Workflow Orchestration-9165/create_dynamic_api_gateway_with_http_router_and_workflow_orchestration.json new file mode 100644 index 000000000..953f35e82 --- /dev/null +++ b/workflows/Create Dynamic API Gateway with HTTP Router and Workflow Orchestration-9165/create_dynamic_api_gateway_with_http_router_and_workflow_orchestration.json @@ -0,0 +1 @@ +{"meta":{"instanceId":"2af7d602b9c59038b521d73a8d986df022306cc34b7d66460334237434b5f4ad"},"nodes":[{"id":"e6445f6f-1087-4e51-b8e5-ddb99456f868","name":"HEAD","type":"n8n-nodes-base.set","position":[-336,1440],"parameters":{"mode":"raw","options":{},"jsonOutput":"{\n \"method\": \"HEAD\"\n}\n"},"typeVersion":3.4},{"id":"bb48dadb-e960-4e34-bb51-f0a0528c0369","name":"Execute Workflow","type":"n8n-nodes-base.executeWorkflow","onError":"continueErrorOutput","position":[896,672],"parameters":{"mode":"each","options":{"waitForSubWorkflow":true},"workflowId":{"__rl":true,"mode":"id","value":"={{ $('Resolve').item.json.workflowIdToExecute }}","cachedResultUrl":"/workflow/=%7B%7B%20$('Resolve').item.json.workflowIdToExecute%20%7D%7D"},"workflowInputs":{"value":{},"schema":[],"mappingMode":"defineBelow","matchingColumns":[],"attemptToConvertTypes":false,"convertFieldsToString":true}},"typeVersion":1.3},{"id":"050ed363-ea07-4301-a53b-28c5c6328857","name":"Success","type":"n8n-nodes-base.respondToWebhook","position":[1088,672],"parameters":{"options":{"responseCode":200},"respondWith":"json","responseBody":"={{ $json }}"},"typeVersion":1.4},{"id":"1659fc43-7139-49c8-a5a8-e1557dc89bae","name":"Aggregate Method Branches","type":"n8n-nodes-base.set","position":[-128,672],"parameters":{"mode":"raw","options":{},"jsonOutput":"={{ $json }}"},"typeVersion":3.4},{"id":"3bdd165b-3ce6-4292-b696-bfc99a984ee3","name":"PUT","type":"n8n-nodes-base.set","position":[-336,1280],"parameters":{"mode":"raw","options":{},"jsonOutput":"{\n \"method\": \"PUT\"\n}\n"},"typeVersion":3.4},{"id":"3aeaa1f4-9b71-46da-ae4f-277a356c1dc2","name":"PATCH","type":"n8n-nodes-base.set","position":[-336,1120],"parameters":{"mode":"raw","options":{},"jsonOutput":"{\n \"method\": \"PATCH\"\n}\n"},"typeVersion":3.4},{"id":"56b8e967-39b4-43d4-b610-77e009227d07","name":"DELETE","type":"n8n-nodes-base.set","position":[-336,960],"parameters":{"mode":"raw","options":{},"jsonOutput":"{\n \"method\": \"DELETE\"\n}\n"},"typeVersion":3.4},{"id":"6e369b93-0468-4cb3-8198-09bcd21a5b17","name":"POST","type":"n8n-nodes-base.set","position":[-336,816],"parameters":{"mode":"raw","options":{},"jsonOutput":"{\n \"method\": \"POST\"\n}\n"},"typeVersion":3.4},{"id":"cb22c4e7-1a77-455d-a4c8-d6be32d6136e","name":"GET","type":"n8n-nodes-base.set","position":[-336,672],"parameters":{"mode":"raw","options":{},"jsonOutput":"{\n \"method\": \"GET\"\n}\n"},"typeVersion":3.4},{"id":"42dcf978-3dbc-40bc-b514-a0fb5c01f5df","name":"Routes Config","type":"n8n-nodes-base.set","position":[288,672],"parameters":{"mode":"raw","options":{},"jsonOutput":"{\n \"routes\": [\n {\n \"action\": \"doSomething\",\n \"allowedMethods\": [\"GET\"],\n \"subflowId\": -1\n },\n {\n \"action\": \"doSomethingElse\",\n \"allowedMethods\": [\"GET\", \"POST\"],\n \"subflowId\": -1\n },\n {\n \"action\": \"deleteSomething\",\n \"allowedMethods\": [\"DELETE\"],\n \"subflowId\": -1\n }\n ]\n}\n"},"typeVersion":3.4},{"id":"91b1d0a9-431f-4806-b291-fd76973301d7","name":"Query param exists?","type":"n8n-nodes-base.if","position":[64,672],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"d010b9f7-91c1-45b8-8f57-0dd17c4db7d2","operator":{"type":"string","operation":"exists","singleValue":true},"leftValue":"={{ $('Universal Receiver').item.json.query?.action }}","rightValue":""}]}},"typeVersion":2.2},{"id":"4a9798ab-c963-4e42-9a0b-8d8e9f2ac2ed","name":"Universal Receiver","type":"n8n-nodes-base.webhook","position":[-544,672],"webhookId":"a4696443-7af7-4183-8158-e2036fab13ef","parameters":{"path":"universalReceiver","options":{},"httpMethod":["GET","POST","DELETE","PATCH","PUT","HEAD"],"responseMode":"responseNode","multipleMethods":true},"typeVersion":2.1},{"id":"116a35d5-68cc-478c-8630-4f9a36bdba36","name":"Sticky: Method detection","type":"n8n-nodes-base.stickyNote","position":[-336,496],"parameters":{"color":7,"width":308,"height":152,"content":"Method branches: Each branch hardcodes the HTTP method because n8n’s Webhook node does not expose the method as a property directly. We aggregate these branches so other nodes can reference a single `method` value."},"typeVersion":1},{"id":"1664cfd5-d8fb-4417-a99e-43e7ccf7568b","name":"Sticky: Routes config","type":"n8n-nodes-base.stickyNote","position":[208,496],"parameters":{"color":7,"width":228,"height":152,"content":"Route configuration: Map action names to subflow IDs and allowed HTTP methods. You can override this with other methods for receiving data, such as database queries or similar."},"typeVersion":1},{"id":"b95edefc-f4cf-4696-8d09-5271233f1d38","name":"Sticky: Resolver","type":"n8n-nodes-base.stickyNote","position":[464,496],"parameters":{"color":7,"width":180,"height":152,"content":"Resolve Route: Reads the query action and method, checks the config and decides whether to run a subflow. "},"typeVersion":1},{"id":"784a1ad8-d454-41fe-ab3b-801e81cce923","name":"Sticky: Execute & respond","type":"n8n-nodes-base.stickyNote","position":[880,496],"parameters":{"color":7,"width":308,"height":152,"content":"Execution & Response: Executes the target subflow when valid and returns its output."},"typeVersion":1},{"id":"c712a136-97f8-49fe-b01a-77ed001fe10f","name":"[Error] Required query param missing","type":"n8n-nodes-base.respondToWebhook","position":[288,848],"parameters":{"options":{"responseCode":400},"respondWith":"json","responseBody":"{\n \"error\": \"Required information not provided!\"\n}"},"typeVersion":1.4},{"id":"d10087c4-96d7-4df6-9918-81b95a95cd6c","name":"Resolve","type":"n8n-nodes-base.code","position":[496,672],"parameters":{"mode":"runOnceForEachItem","jsCode":"// inputs\nconst method = $('Aggregate Method Branches').item.json.method;\nconst action = $(\"Universal Receiver\").item.json.query?.action;\nconst cfg = $(\"Routes Config\").item.json;\nconst routes = Array.isArray(cfg?.routes) ? cfg.routes : [];\nconst body = $(\"Universal Receiver\").item.json.query?.body || null;\n\n// Try to find a matching route (action + method)\nconst routeByAction = routes.find(r => String(r.action || '').toLowerCase() === String(action || '').toLowerCase());\nconst routeByActionAndMethod = routes.find(r => {\n const actionFound = String(r.action || '').toLowerCase() === String(action || '').toLowerCase();\n const methodAllowed = Array.isArray(r.allowedMethods) && r.allowedMethods.includes(method);\n return (actionFound && methodAllowed)\n});\n\nlet error = null;\nlet status = 200;\nlet allowHeader = null;\nlet workflowId = null;\n\nif (!action) {\n error = 'MISSING_ACTION';\n status = 400;\n} else if (!routeByAction) {\n error = 'ROUTE_NOT_FOUND';\n status = 404;\n} else if (!routeByActionAndMethod) {\n error = 'METHOD_NOT_ALLOWED';\n status = 405;\n allowHeader = Array.isArray(routeByAction.allowedMethods) ? routeByAction.allowedMethods.join(', ') : '';\n} else {\n workflowId = routeByActionAndMethod.subflowId ?? null;\n if (workflowId == null) {\n error = 'MISSING_SUBFLOW_ID';\n status = 500;\n }\n}\n\nreturn {\n action,\n method,\n error,\n status,\n allow: allowHeader,\n workflowIdToExecute: workflowId,\n body\n};"},"typeVersion":2},{"id":"80b31830-b665-4f01-a2d6-4a08779e3ebb","name":"Route is OK?","type":"n8n-nodes-base.if","position":[688,672],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"d010b9f7-91c1-45b8-8f57-0dd17c4db7d2","operator":{"type":"number","operation":"exists","singleValue":true},"leftValue":"={{ $('Resolve').item.json.workflowIdToExecute }}","rightValue":""}]}},"typeVersion":2.2},{"id":"99fb75ed-9d41-4396-b4e6-ecead6e005d9","name":"Status = 405?","type":"n8n-nodes-base.if","position":[896,992],"parameters":{"options":{},"conditions":{"options":{"version":2,"leftValue":"","caseSensitive":true,"typeValidation":"strict"},"combinator":"and","conditions":[{"id":"9928d071-7d13-4d05-9843-9f2f1b00b60e","operator":{"type":"number","operation":"equals"},"leftValue":"={{ $('Resolve').item.json.status }}","rightValue":405}]}},"typeVersion":2.2},{"id":"c1882d8f-bfd1-482c-9f52-113c6111868f","name":"[Error] Method Not Allowed","type":"n8n-nodes-base.respondToWebhook","position":[1088,992],"parameters":{"options":{"responseCode":405,"responseHeaders":{"entries":[{"name":"Allow","value":"={{ $('Resolve').item.json.allow || '' }}"}]}},"respondWith":"json","responseBody":"={\n \"error\": \"{{ $('Resolve').item.json.error }}\"\n}"},"typeVersion":1},{"id":"6325c215-2175-4703-8746-f18e5e8dd208","name":"Error - Not OK","type":"n8n-nodes-base.respondToWebhook","position":[1088,1168],"parameters":{"options":{"responseCode":"={{ $('Resolve').item.json.status || 400 }}"},"respondWith":"json","responseBody":"={ \"error\": \"{{ $('Resolve').item.json.error || 'Bad request' }}\" }"},"typeVersion":1},{"id":"b4f491b6-ea8b-470c-ad4b-d1c8922ae479","name":"Error - Subflow","type":"n8n-nodes-base.respondToWebhook","position":[1088,832],"parameters":{"options":{"responseCode":500},"respondWith":"json","responseBody":"{\n \"error\": \"Unexpected error in the executed subflow!\"\n}"},"typeVersion":1},{"id":"2fe4296a-f97f-4dce-b49e-983a25720dd8","name":"Sticky: Method detection1","type":"n8n-nodes-base.stickyNote","position":[-1104,496],"parameters":{"color":7,"width":500,"height":520,"content":"This flow acts as a centralized API router using a single webhook URL to receive incoming HTTP requests with different methods (GET, POST, DELETE, PATCH, PUT, HEAD).\n\n- The flow separates and hardcodes each HTTP method into dedicated branches (Set nodes for GET, POST, etc.) since the webhook node doesn't expose the method directly. These branches are then consolidated in \"Aggregate Method Branches\" for easier reference.\n- It checks if the required query parameter action exists. If missing, it returns an error response.\n- The `Routes Config` node defines a list of routes mapping the action parameter and allowed HTTP methods to specific subflow IDs. This allows controlling which workflow to execute based on the request.\n- The `Resolve` code node matches the incoming action and method against the configured routes, returns error codes like 404 or 405 if no route/method matches, or identifies the subflow to execute.\n- Based on this result, the flow either executes the matched subflow or returns an appropriate error message and status code (400, 404, 405, 500).\n- After executing a subflow, it returns the subflow result as the response. If the subflow fails, a generic error is sent back.\n- This setup provides a flexible and maintainable way to route multiple API actions via a single webhook endpoint, with centralized management of routes, error handling, and response management."},"typeVersion":1}],"pinData":{},"connections":{"GET":{"main":[[{"node":"Aggregate Method Branches","type":"main","index":0}]]},"PUT":{"main":[[{"node":"Aggregate Method Branches","type":"main","index":0}]]},"HEAD":{"main":[[{"node":"Aggregate Method Branches","type":"main","index":0}]]},"POST":{"main":[[{"node":"Aggregate Method Branches","type":"main","index":0}]]},"PATCH":{"main":[[{"node":"Aggregate Method Branches","type":"main","index":0}]]},"DELETE":{"main":[[{"node":"Aggregate Method Branches","type":"main","index":0}]]},"Resolve":{"main":[[{"node":"Route is OK?","type":"main","index":0}]]},"Route is OK?":{"main":[[{"node":"Execute Workflow","type":"main","index":0}],[{"node":"Status = 405?","type":"main","index":0}]]},"Routes Config":{"main":[[{"node":"Resolve","type":"main","index":0}]]},"Status = 405?":{"main":[[{"node":"[Error] Method Not Allowed","type":"main","index":0}],[{"node":"Error - Not OK","type":"main","index":0}]]},"Execute Workflow":{"main":[[{"node":"Success","type":"main","index":0}],[{"node":"Error - Subflow","type":"main","index":0}]]},"Universal Receiver":{"main":[[{"node":"GET","type":"main","index":0}],[{"node":"POST","type":"main","index":0}],[{"node":"DELETE","type":"main","index":0}],[{"node":"PATCH","type":"main","index":0}],[{"node":"PUT","type":"main","index":0}],[{"node":"HEAD","type":"main","index":0}]]},"Query param exists?":{"main":[[{"node":"Routes Config","type":"main","index":0}],[{"node":"[Error] Required query param missing","type":"main","index":0}]]},"Aggregate Method Branches":{"main":[[{"node":"Query param exists?","type":"main","index":0}]]}}} \ No newline at end of file