Is there really no way to make HTTP calls when testing an ionic React app with "ionic serve"

When developing an ionic React app and using ionic serve to preview the app in a browser, all non-GET HTTP calls fail due to CORS issues. From my research I learned that this is expected behaviour due to cross origin requests from my localhost to an API server.

When running the app inside an emulator or on a physical device the Capacitor HTTP plugin solves those issues, but HTTP calls still won’t work when the app is running locally and viewed in a browser.

The only viable option to overcome this issue seems to be a proxy as described in this ionic tutorial. However, this does not seem to work anymore, since any proxy setup in ionic.config.json will simply be ignored and HTTP calls to the proxy endpoint will just result in a 404 error.

This effectively means that there is no option to use the ionic serve command during app development or to preview a functional web app altogether.

Am I missing something here, or is this just the (very frustrating) state of affairs right now?
Thanks in advance for any help!

You would have to configure your API server to allow CORS. This is what I’ve done and don’t use the Capacitor HTTP plugin at all. Otherwise, yes, a proxy server is the only option testing from a web browser.

Here are the addresses I allow:

'allowed_origins' => [
    'http://192.168.86.63:8100',        // Local development
    'http://localhost:8100',            // Local development
    'capacitor://localhost',            // iOS
    'http://localhost',                 // Android
],
2 Likes

Hey @twestrick

Thanks for your reply! Sadly i do not control the server myself.

Using a proxy would be great! Have you ever set one up successfully? Everything I can find regarding this topic is at least four years old and does not seem to work with more modern versions of ionic.

A proxy isn’t Ionic specific. This might be a good tutorial with Node - Build a Node.js Proxy Server in Under 10 minutes!.

Another option would be with Nginx.

2 Likes

Yes. You’re completely right. But there seems to have been support for somewhat “built-in” proxy solutions with ionic as described here.
This would be very nice, because i wouldn’t have to start the proxy server separately before running ionic serve.

Anyways, thanks a lot for your tips! Maybe I’ll use a separate proxy server for now and create a npm command that starts both, dev server AND proxy server.

My guess is that is no longer supported as it is not mentioned here - CORS Errors: Cross-Origin Resource Sharing - Ionic Documentation.

Your path seems like a good one with one command to start both.

1 Like

Good call! I#m trying to fiddle around with http-proxy-middleware and nodes own HTTP module at the moment. No luck so far, but I’ll keep trying.

There are 2 plugins that I have used with Angular apps to bypass CORs issues. It probably works with React as well:

1 Like

@timmkuehle was wondering how to handle it when running the app in the web browser. There is no way around CORS there other than a proxy server. They are using CapacitorHTTP for native :grin:

1 Like

oh my mistake, had to re-read the question. :sweat_smile:

1 Like

Thanks for your reply! I tried CapacitorHTTP and it does only work when running the App on a physical device or in an emulator/simulator. I don’t really know if it has worked in browsers at some point, but at least for newer ionic version it won’t do the job.

Puuh. That was a good days work, but I came up with a solution:

I wrote a small proxy server script, located in scripts/webAppDevProxy/index.js:

import http from "http";
import { devProxyHost, devProxyPort } from "./constants.js";

http
	.createServer((req, res) => {
		// Get target URL from request URL "path"
		const targetUrl = req.url.replace(/^\//, "");

		console.log(`incoming request ➜ URL: ${targetUrl} | Method: ${req.method}`);

		// Setting headers
		res.setHeader("Access-Control-Allow-Origin", "*");
		res.setHeader(
			"Access-Control-Allow-Headers",
			"Content-Type, Authorization"
		);

		// Handle preflight requests:
		if (req.method === "OPTIONS") {
			res.writeHead(200);
			res.end();
			return;
		}

		// Parse request body:
		req.body = "";
		req.on("data", (chunk) => {
			req.body += chunk;
		});

		// Forward the request
		req.on("end", async () => {
			try {
				// Fetch target URL with data from proxy request
				const response = await fetch(targetUrl, {
					method: req.method,
					headers: {
						"Content-Type":
							req.headers.get("content-type") || "application/json",
					},
					body: req.body,
				});

				// Get content type of response
				const resContentType =
					response.headers.get("content-type") || "application/json";

				// Set content type hedaer of response
				res.setHeader("Content-Type", resContentType);

				console.log(`Resolving request ➜ Status: ${response.status}`);

				// Resolve request to proxy with data from forwarded request:
				res.writeHead(response.status);
				res.end(JSON.stringify(await response.json()));
			} catch (error) {
				console.log(`Resolving request ➜ Status: 500`);

				// Set content type hedaer of response
				res.setHeader("Content-Type", "application/json");

				// Resolve rewuest to proxy server with error
				res.writeHead(500);
				res.end(
					JSON.stringify({
						error: {
							code: 500,
							message: "500: Internal Server Error",
						},
					})
				);
			}
		});
	})
	.listen(devProxyPort, devProxyHost, () => {
		console.log(
			`Web App Dev Proxy listening at http://${devProxyHost}:${devProxyPort}`
		);
	})
	.on("error", (err) => {
		console.log(`Error while setting up server: ${err.message}`);
	});

This basically takes any request, to the proxy server, forwards it to the target URL and forwards the response back to the app. (As a good proxy does. :grin:)

The proxy is started, using a simple npm runcommand in package.json:

{
	scripts: {
		"startDevProxy": "node scripts/webAppDevProxy"
	}
}

Within the app, i defined a constant, that i can use to discern wether the app is currently running locally as a web app or not:

import { Capacitor } from "@capacitor/core";
const ENV = import.meta.env;

const isLocalWebApp = !Capacitor.isNativePlatform() && ENV.MODE === "development";

Now, whenever I’m doing a tech request in the app, I can adjust my URLs accordingly:

// @ts-ignore
import { devProxyUrl } from "../../scripts/webAppDevProxy/constants.js";

const urlToFetch =
	(isLocalWebApp ? `${devProxyUrl}/` : "") + "https://api.example.com";

const response = fetch(urlToFetch, {
	method: "POST",
	headers: {
		"Content-Type": "application/json",
	},
	body: {
		data: "Body data",
	},
});

The “real” code is a bit more complex and structured into multiple files. Mostly, because I do a real preflight check for allowed origins and my logs are a lot more verbose. I can piece together a small GitHub repo with a brief explanation on how to use the proxy if anyone is interested.

Thanks for your help guys, cheers! :blush:

2 Likes