Docker nginx proxy with Solid Start

Hi I am kind of new to reverse proxy. I have 3 containers frontend, backend and nginx. My nginx config
location / {
proxy_pass http://frontend:[my_frontend_port];
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/ {
proxy_pass http://backend:[my_api_port]/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location / {
proxy_pass http://frontend:[my_frontend_port];
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/ {
proxy_pass http://backend:[my_api_port]/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
I want to use it like this inside solid app:
fetch("/api/endpoint")...
fetch("/api/endpoint")...
my proxy setup:
...
server: {
proxy: {
"/api": {
target: "http://backend:[backend_port]/api",
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, "/api"),
},
},
},
...
server: {
proxy: {
"/api": {
target: "http://backend:[backend_port]/api",
changeOrigin: true,
rewrite: (path: string) => path.replace(/^\/api/, "/api"),
},
},
},
Errors I get:
GET http://localhost/api/time net::ERR_ABORTED 404 (Not Found)
GET http://localhost/api/time net::ERR_ABORTED 404 (Not Found)
It seems like it does not know about existance of those api routes. This is how i use it :
const response = await fetch("/api/time", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const response = await fetch("/api/time", {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
Can someone help me with it? It works on the server side with ssr with (http://backend:[backend_port]/api/time) but does not work on the client side.
38 Replies
chanon_s
chanon_s3mo ago
Have you tried simply
"/api": "http://backend:[backendport]/api"
"/api": "http://backend:[backendport]/api"
? relevant docs: https://vite.dev/config/server-options.html#server-proxy https://github.com/vitejs/vite/blob/main/packages/vite/src/node/server/middlewares/proxy.ts#L13 https://github.com/sagemathinc/http-proxy-3#options but in your case I think the simple oneliner above should work
Jason.json
Jason.jsonOP3mo ago
I think I did but i will try again Nope does not work
chanon_s
chanon_s3mo ago
hmm your frontend is also behind nginx what is actually running on localhost and what is remote? also, how do you start the frontend? actually thinking about it again, your nginx should already handle the /api path without needing the vite config
Jason.json
Jason.jsonOP3mo ago
Yup I thought the same. Have you ever setup something like this before? I mean maybe there is a way better solution
chanon_s
chanon_s3mo ago
yes what do you have running on local, and what is remote, and why do you need nginx? I mean, I need to know what you are trying to do just getting development environement setup or trying to setup for production
Jason.json
Jason.jsonOP3mo ago
setup for production env I have frontend, backend and nginx containers working together in docker-compose
chanon_s
chanon_s3mo ago
so if you access your frontend at website.com and you can access your backend api at website.com/api/time then any client code making a request to website.com/api/time or /api/time should also work it isn't a solidjs or solidstart issue and doesn't need the proxy config in the app.config.ts, actually maybe you should remove it and let nginx handle it. Or rather you must, cause letting /api/time go to website.com/api/time is what you want cause nginx will proxy it to the backend for you if website.com/api/time doesn't work, then it is an nginx config issue
Jason.json
Jason.jsonOP3mo ago
yeah i still testing it in my local host hmm But I use http://backend:[backendport]/api
chanon_s
chanon_s3mo ago
yeah, in localhost can also be confusing cause everything is localhost also I'm not completely sure how to reference other docker compose services inside nginx config that is in a docker compose but as first step if you have eg. nginx at localhost:3333 you have front end on port 3000 backend on port 3200 test so that localhost:3333 shows front end in your browser and localhost:3333/api/time shows backend api result in your browser if you have both of those working, then the front end js code should be able to call "/api/time"
Jason.json
Jason.jsonOP3mo ago
I mean i exposed the port only to inernal docker containers I mean the backend
chanon_s
chanon_s3mo ago
right, so when accessing the nginx port you should get the proxied output btw, I'm not sure if order of nginx blocks has an effect, maybe you should put the location /api/ block first
Jason.json
Jason.jsonOP3mo ago
when i exposed it to the host i for example: http://localhost:[backend_port]/api/helloworld I get hello world as expected
chanon_s
chanon_s3mo ago
right, next you need to get the nginx config right for it to be acessible through http://localhost:[nginxport]/api/helloworld
Jason.json
Jason.jsonOP3mo ago
I will tell ya more in a few minutes And maybe I will provide some errors
chanon_s
chanon_s3mo ago
OK, after some reasearch, you need
location /api {
proxy_pass http://backend:[backendport];
location /api {
proxy_pass http://backend:[backendport];
OR
location /api/ {
proxy_pass http://backend:[backendport]/api/;
location /api/ {
proxy_pass http://backend:[backendport]/api/;
Notice the trailing slashes carefully. otherwise your requests to localhost:[nginxport]/api/helloworld would get stripped to backend:[backendport]/helloworld
Jason.json
Jason.jsonOP3mo ago
Ok I am back so let's begin from the starting point. I am working on a frontend admin panel in local network for a company, I would like to put it into external hosting provider, but they want the database data on their office server so I choose docker for that. I made backend, db_service, frontend, nginx and backup_service containers So I have everything setup in the compose already and it works very well
const [employees, { refetch }] = createResource(
() => [query(), order()] as const,
async ([q, o]) => get_employees(q, o),
{ deferStream: true }
);
const [employees, { refetch }] = createResource(
() => [query(), order()] as const,
async ([q, o]) => get_employees(q, o),
{ deferStream: true }
);
this function works but for example this function does not work:
const employee = createAsync(
async (): Promise<EmployeeModel | void> => {
try {
const response = await fetch(
`http://backend:[backendport]/api/employees/${params.id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);

if (!response.ok) {
navigate("/employees");
}

return await response.json();
} catch (error) {
console.error(error);
navigate("/employees");
}
},
{ deferStream: true }
);
const employee = createAsync(
async (): Promise<EmployeeModel | void> => {
try {
const response = await fetch(
`http://backend:[backendport]/api/employees/${params.id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
},
}
);

if (!response.ok) {
navigate("/employees");
}

return await response.json();
} catch (error) {
console.error(error);
navigate("/employees");
}
},
{ deferStream: true }
);
I get: Failed to load resource: net::ERR_NAME_NOT_RESOLVED maybe this one above works cuz it is marked as server function? Source:
export async function get_employees(
query: string,
sort: SortOrder
): Promise<null | Array<EmployeeModel>> {
"use server";
....
export async function get_employees(
query: string,
sort: SortOrder
): Promise<null | Array<EmployeeModel>> {
"use server";
....
chanon_s
chanon_s3mo ago
how do you currently open the frontend in your browser? what does the url look like? you are trying to get the web client to fetch: http://backend:[backendport]/api/employees/${params.id} if the same url doesn't work if you input it into the address bar of your browser, then it won't work with fetch you need to config nginx to be able to access the backend from your browser or you could expose the backend directly without going through nginx whichever way, you need a url that works in your browser to access the backend api
Jason.json
Jason.jsonOP3mo ago
yup
chanon_s
chanon_s3mo ago
then when it works in your browser you can put the same looking url into the fetch
Jason.json
Jason.jsonOP3mo ago
I could show you my config
chanon_s
chanon_s3mo ago
please take a good look at this too
Jason.json
Jason.jsonOP3mo ago
my config file
server {
listen 80;
listen 443;

server_name _;

location / {
proxy_pass http://frontend:[frontendport];
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/ {
proxy_pass http://backend:[backendport]/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
listen 443;

server_name _;

location / {
proxy_pass http://frontend:[frontendport];
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}

location /api/ {
proxy_pass http://backend:[backendport]/api/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
chanon_s
chanon_s3mo ago
ok, the /api/ and proxy_pass http://backend:[backendport]/api/; should make the url correct I'm not sure about the $host part it might depend on if your backend is listening to all hosts to make sure that's not an issue have the backend listen to 0.0.0.0
Jason.json
Jason.jsonOP3mo ago
yup it listens on 0.0.0.0
chanon_s
chanon_s3mo ago
ok, now how do you open the front end in your browser please answer
Jason.json
Jason.jsonOP3mo ago
by opening localhost without any port
chanon_s
chanon_s3mo ago
ok, so now can you open http://localhost/api/helloworld ?
Jason.json
Jason.jsonOP3mo ago
nope
chanon_s
chanon_s3mo ago
what happens do you get anything in the frontend or backend logs?
Jason.json
Jason.jsonOP3mo ago
yup I get ERR_CONNECTION_REFUSED
chanon_s
chanon_s3mo ago
in which log?
Jason.json
Jason.jsonOP3mo ago
lemme check ok now i can access it I forgot that i composed it down my bad
chanon_s
chanon_s3mo ago
alright!
Jason.json
Jason.jsonOP3mo ago
No description
chanon_s
chanon_s3mo ago
so now you can change const response = await fetch( http://backend:[backendport]/api/employees/${params.id}, to const response = await fetch( http://localhost/api/employees/${params.id}, and it should work and when deploying it to production, change the localhost to whatever it is supposed to be (the same url that you access the frontend) Oh, actually, you can change it to
const response = await fetch(`/api/employees/${params.id}`)
const response = await fetch(`/api/employees/${params.id}`)
Jason.json
Jason.jsonOP3mo ago
hmm I get an error like this:
TypeError: Failed to parse URL from /api/time

at node:internal/deps/undici/undici:15445:13

at process.processTicksAndRejections (node:internal/process/task_queues:105:5)

at async U.H.deferStream (file:///app/.output/server/chunks/build/ApplicationHeader-CJVtgt6n.mjs:87:17) {

[cause]: TypeError: Invalid URL

at new URL (node:internal/url:826:25)

at new Request (node:internal/deps/undici/undici:10150:25)

at fetch (node:internal/deps/undici/undici:10920:25)

at fetch (node:internal/deps/undici/undici:15443:10)

at fetch (node:internal/bootstrap/web/exposed-window-or-worker:83:12)

at U.H.deferStream (file:///app/.output/server/chunks/build/ApplicationHeader-CJVtgt6n.mjs:87:23)

at N (file:///app/.output/server/chunks/build/ApplicationHeader-CJVtgt6n.mjs:61:49)

at file:///app/.output/server/chunks/build/ApplicationHeader-CJVtgt6n.mjs:25:30

at load (file:///app/.output/server/node_modules/solid-js/dist/server.js:565:47)

at createResource (file:///app/.output/server/node_modules/solid-js/dist/server.js:602:42) {

code: 'ERR_INVALID_URL',

input: '/api/time'

}

}
TypeError: Failed to parse URL from /api/time

at node:internal/deps/undici/undici:15445:13

at process.processTicksAndRejections (node:internal/process/task_queues:105:5)

at async U.H.deferStream (file:///app/.output/server/chunks/build/ApplicationHeader-CJVtgt6n.mjs:87:17) {

[cause]: TypeError: Invalid URL

at new URL (node:internal/url:826:25)

at new Request (node:internal/deps/undici/undici:10150:25)

at fetch (node:internal/deps/undici/undici:10920:25)

at fetch (node:internal/deps/undici/undici:15443:10)

at fetch (node:internal/bootstrap/web/exposed-window-or-worker:83:12)

at U.H.deferStream (file:///app/.output/server/chunks/build/ApplicationHeader-CJVtgt6n.mjs:87:23)

at N (file:///app/.output/server/chunks/build/ApplicationHeader-CJVtgt6n.mjs:61:49)

at file:///app/.output/server/chunks/build/ApplicationHeader-CJVtgt6n.mjs:25:30

at load (file:///app/.output/server/node_modules/solid-js/dist/server.js:565:47)

at createResource (file:///app/.output/server/node_modules/solid-js/dist/server.js:602:42) {

code: 'ERR_INVALID_URL',

input: '/api/time'

}

}
all in production build btw It does not seems to find /api route hmm strange
chanon_s
chanon_s3mo ago
Hmm.. I haven't used fetch a lot, looks like it needs absolute urls
Jason.json
Jason.jsonOP3mo ago
yeah but it would be trash to hardcode it tbh

Did you find this page helpful?