I’m developing a translation API for a Chrome extension using Express and Netlify Functions. However, I’m encountering issues with CORS when making requests from the browser. The preflight OPTIONS request fails, and I’m stuck trying to resolve the issue.
Here is my server
import express from "express";
import cors from "cors";
import { getTranslation } from "./translator.js";
import serverless from "serverless-http";
const app = express();
const router = express.Router();
app.use(express.json());
app.use(
cors({
origin: "*",
methods: ["POST", "OPTIONS"],
allowedHeaders: ["Content-Type"],
})
);
router.options("/translate", cors(), (req, res) => {
res.sendStatus(204);
});
router.get("/", (req, res) => {
res.send("This is a translator app for DragWords chrome extension.");
});
router.post("/translate", async (req, res) => {
const body = req.body;
const { sourceText, targetLang } = body;
try {
const { translatedText } = await getTranslation({
text: sourceText,
targetLang: targetLang,
});
res.status(200).json({ translation: translatedText, serverError: null });
} catch (err) {
console.error(err);
res.status(500).json({ translation: null, serverError: err });
}
});
app.use("/.netlify/functions/api", router);
export const handler = serverless(app);
and here is my netlify.toml
[build]
publish = "dist"
functions = "functions"
[context.production.environment]
DEEPL_API_KEY = "my api key"
[[headers]]
for = "/.netlify/functions/api/*"
[headers.values]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "POST, OPTIONS"
Access-Control-Allow-Headers = "Content-Type"
I ran this server on my localhost:8888 and I sent a request to the server from "https://ricki-lee.medium.com" but there is still a CORS error.
https://ricki-lee.medium.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
here is my client code in the content.js file which gets translation from the server
async function getTranslation(sourceText) {
try {
const defaultTargetLang = await getDefaultTargetlang();
const response = await fetch("http://localhost:8888/translate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
sourceText: sourceText,
targetLang: defaultTargetLang,
}),
});
const { translation, serverError } = await response.json();
if (serverError !== null) {
throw new Error(serverError);
}
return translation;
} catch (err) {
console.error(err);
return null;
}
}
I set Access-Control-Allow-Origin = "*" but it seems it doesn't work
I also tested the OPTIONS request using curl, which returned the following error:
curl -X OPTIONS http://localhost:8888/translate -H "Origin: https://ricki-lee.medium.com"
Method Not Allowed
How can I configure my Express server or Netlify setup to handle the preflight OPTIONS request properly and resolve the CORS error?
1 Answer 1
There are three problems with your code.
Wrong service path
You have registered the router that handles the /translate request on the /.netlify/functions/api path, but you invoke it as http://localhost:8888/translate. Shouldn't that be http://localhost:8888/.netlify/functions/api/translate?
Incorrect CORS configuration
If you want to allow any origin, set origin: true, not origin: "*". But as jub0bs commented, this should be avoided and is clearly unnecessary in your case, because you use only one origin.
Also, you need not specify any special handling for OPTIONS, because the cors handles preflights automatically. So what you need is just:
app.use(
cors({
origin: "https://ricki-lee.medium.com",
methods: ["POST"],
allowedHeaders: ["Content-Type"],
})
);
and no router.options("/translate", ...).
Private network access
The origin of your CORS request is in the public cloud whereas the URL that it calls is in your private network. In this situation, the preflight request contains
Access-Control-Request-Private-Network: true
and its response must contain
Access-Control-Allow-Private-Network: true
(see also https://developer.chrome.com/blog/private-network-access-preflight).
But the cors package does not set this response header. You can set it yourself with the following Node.js code:
app.use(
cors({
origin: "https://ricki-lee.medium.com",
methods: ["POST"],
allowedHeaders: ["Content-Type"],
preflightContinue: true
})
)
.options("*", function(req, res) {
if (res.get("Access-Control-Allow-Origin"))
res.set("Access-Control-Allow-Private-Network", "true");
res.end();
});
Lastly, I do not understand why you need the [[headers]] configuration in netlify.toml. The CORS-relevant headers should already be set by the express.js app.
3 Comments
origin to true. There's never a good reason to do that. In particular, you don't want to allow any origin in conjunction with private-network access; see the warning in developer.chrome.com/blog/private-network-access-preflight/….