I have a custom templates api that fails due to CORS in production

I built an express api to serve custom templates. It works in local dev as I am able to provide devServer proxy in vue.config.js to overcome CORS limitations.

For production I am using Docker Compose. However devServer is not valid in production and I am hoping if some one could teach me to setup a proxy from Vue to my templates API that overcomes CORS.

I have both n8n and templates-api as services in the same docker-compose.yml.

Hey @Gowthaman_Prabhu,

Is this a problem you are seeing in n8n or an issue with the templates service you have made?

@Jon

The custom templates api is reachable from within EC2 terminal using CURL get http:localhost:3000/health. But from n8n which is a service within the same compose network the same returns CORS error : corsDisabledScheme

Hey @Gowthaman_Prabhu,

So is this a service you have made? I am just trying to work out if this is an issue with n8n itself or something outside of n8n so I can try to help.

Yes a Node js Express API that serves custom templates. I have enabled CORS in express. This API is a service along with n8n in Docker Compose sharing the same compose network.

I have set the env var N8N_TEMPLATES_ENABLED=custom-templates-api:3000

When you say CORS is enabled what have you done there? It sounds like you may need more of a NodeJS support forum but I will see what I can work out with you.

I have allowed CORS in my express API. So this API-Server will not block requests originating from a different domain.

Hey @Gowthaman_Prabhu,

That is odd, If you are getting a cors error back it sounds like maybe it is blocking the requests still. Does the error in the browser show any more info?

Hey It is resolved now. The problem was with the templates host URI variable. I misunderstood it to be an internal call from n8n backend to my templatesAPI. Instead it is the browser making the call and thus had to change URI to one that is accessible via internet.

@Jon Sorry for the confusion and thank you very much…

1 Like

Hi @Gowthaman_Prabhu. Do you have a public github repo for this API? Or can you share how you managed to create the tables and workflow object?

Also @Jon may know about it, if n8n already have same schema, pattern we can follow.

@dwsouza Here is my solution for custom templates:

Create a new table in the existing db named workflows as below to store templates:

CREATE TABLE IF NOT EXISTS workflows (
  id SERIAL NOT NULL,
  name varchar NOT NULL,
  createdAt timestamp,
  user json,
  description varchar
);

Create a node js service to serve the templates:

const express = require("express");
const cors = require("cors");
const { Client } = require("pg");
require("dotenv").config();

const app = express();
const port = 3000;

const corsOptions = {
  origin: process.env.N8N_HOST_URL,
  credentials: true,
};

app.use(cors(corsOptions));

app.use(express.json());

const templatesClient = new Client({
  user: process.env.TEMPLATES_USERNAME,
  host: process.env.TEMPLATES_HOST,
  database: process.env.TEMPLATES_DATABASE,
  password: process.env.TEMPLATES_PASSWORD,
  port: process.env.TEMPLATES_PORT,
});

const lisClient = new Client({
  user: process.env.LIS_USERNAME,
  host: process.env.LIS_HOST,
  database: process.env.LIS_DATABASE,
  password: process.env.LIS_PASSWORD,
  port: process.env.LIS_PORT,
});

templatesClient.connect(function (err) {
  if (err) throw err;
  console.log("templates postgres db conn established");
});

lisClient.connect(function (err) {
  if (err) throw err;
  console.log("lis workflows postgres db conn established");
});

app.get("/health", (req, res) => {
  res.json({ status: "OK" });
});

app.get("/templates/workflows", (req, res) => {
  templatesClient.query("SELECT * FROM workflows", (err, result) => {
    if (err) {
      console.log(err.stack);
    } else {
      res.json({ workflows: result.rows });
    }
  });
});

app.get("/templates/workflows/:id", async (req, res) => {
  templatesClient.query(
    `SELECT * FROM workflows WHERE id=${req.params.id}`,
    (err, result) => {
      if (err) {
        console.log(err.stack);
      } else {
        let workflowResult = result.rows[0];
        lisClient.query(
          `SELECT nodes, connections FROM workflow_entity WHERE name = '${workflowResult.name}'`,
          (err, innerResult) => {
            if (err) {
              console.log(err.stack);
            } else {
              workflowResult.workflow = {};
              workflowResult.workflow.nodes = innerResult.rows[0].nodes;
              workflowResult.workflow.connections =
                innerResult.rows[0].connections;
              res.json({ workflow: workflowResult });
            }
          }
        );
      }
    }
  );
});

app.get("/workflows/templates/:id", async (req, res) => {
  templatesClient.query(
    `SELECT * FROM workflows WHERE id=${req.params.id}`,
    (err, result) => {
      if (err) {
        console.log(err.stack);
      } else {
        let workflowResult = result.rows[0];
        lisClient.query(
          `SELECT nodes, connections FROM workflow_entity WHERE name = '${workflowResult.name}'`,
          (err, innerResult) => {
            if (err) {
              console.log(err.stack);
            } else {
              workflowResult.workflow = {};
              workflowResult.workflow.nodes = innerResult.rows[0].nodes;
              workflowResult.workflow.connections =
                innerResult.rows[0].connections;
              res.json(workflowResult);
            }
          }
        );
      }
    }
  );
});

app.post("/template", async (req, res) => {
  const dateTime = new Date().toISOString();

  templatesClient.query(
    `INSERT INTO workflows (name, "createdAt", "user", description) 
                             VALUES ('${req.body.name}', '${dateTime}', '{"username": "[email protected]"}', '${req.body.description}')`,
    (err, result) => {
      if (err) {
        console.log(err.stack);
      } else {
        res.json({ status: `created ${req.body.name}` });
      }
    }
  );
});

app.delete("/template/:name", async (req, res) => {
  templatesClient.query(
    `DELETE FROM workflows WHERE name = '${req.params.name}'`,
    (err, result) => {
      if (err) {
        console.log(err.stack);
      } else {
        res.json({ status: `deleted ${req.params.name}` });
      }
    }
  );
});

app.listen(port, () => {
  console.log(`Templates API listening on port ${port}`);
});

I now use the same db as N8N to store this new workflows table so you can ignore the templates db connection (or in my case both the db connection env vars above point to the n8n db)

Set below env vars:

N8N_TEMPLATES_ENABLED=True
N8N_TEMPLATES_HOST={nodejs api uri}

Hi @Gowthaman_Prabhu , really appreciate the help, thanks!

In my case I’ll have a “templates database”, but it seems kind of optional some fields in GET workflows endpoints, I mean you don’t have nodes and connections there, for example.

And as I’ll have another database, I’ll have to create these columns there I think.

But it’s a good start, thank you!