Im trying to publish my first GraphQl project on a VPN, using docker-compose
It consists on a webapp, running on nodejs, and a GraphQl API, also running on nodejs, with Apollo Express and Prisma
The idea is to have the app and the API running on different containers and use a nginx container to proxy pass the requests to the right container (/ goes to the webapp, /api goes to the API)
I got it working, seems to be fine, but it needs to run on https. So Ive set up a letsencrypt certificate and set it on nginx and is working too, except for one thing: subscriptions
If I try to connect to the websocket using ws://mydomain/api, its refused cause the app is running on https. But if I try to connect on wss://mydomain/api, I get:
WebSocket connection to 'wss://mydomain/api' failed: Error during WebSocket handshake: Unexpected response code: 400
I read a lot of docs and tutorials and it seems to me Im doing right, but it just wont work and I dont know what to try anymore
Here is the relevant docker-compose.yml code:
version: "3"
services:
api:
build:
context: ./bin/api
container_name: 'node10-api'
restart: 'always'
entrypoint: ["sh", "-c"]
command: ["yarn && yarn prisma deploy && yarn prisma generate && yarn start"]
restart: always
ports:
- "8383:8383"
links:
- prisma
volumes:
- /local/api:/api
app:
build:
context: ./bin/app
container_name: 'node12-app'
restart: 'always'
entrypoint: ["sh", "-c"]
command: ["yarn && yarn build && yarn express-start"]
restart: always
ports:
- "3000:3000"
links:
- api
volumes:
- /local/app:/app
nginx:
container_name: 'nginx'
restart: always
image: nginx:1.15-alpine
ports:
- '80:80'
- '443:443'
volumes:
- ./data/nginx:/etc/nginx/conf.d
- ./data/certbot/conf:/etc/letsencrypt
- ./data/certbot/www:/var/www/certbot
- ./www:/etc/nginx/html
And here is the nginx conf:
upstream app {
ip_hash;
server app:3000;
}
upstream api {
server api:8383;
}
server {
listen 80;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name mydomain;
ssl_certificate /etc/letsencrypt/live/mydomain/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mydomain/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
location / {
proxy_pass http://app;
}
location /api {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://api;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
}
And finally, the server initialization:
const app = express();
app.use(cookieParser());
app.use(process.env.URL_BASE_PATH + '/' + process.env.UPLOAD_URL_DIR, express.static(process.env.UPLOAD_PATH));
app.use(process.env.URL_BASE_PATH + '/assets', express.static('assets'));
app.use(process.env.URL_BASE_PATH + '/etc', router);
app.use(createLocaleMiddleware());
app.use(helmet());
app.disable('x-powered-by');
console.log(process.env.URL_BASE_PATH);
if(process.env.URL_BASE_PATH === '')server.applyMiddleware({app, cors:corsOptions});
else server.applyMiddleware({app, cors:corsOptions, path:process.env.URL_BASE_PATH});
const httpServer = http.createServer(app);
server.installSubscriptionHandlers(httpServer);
//STARTING
httpServer.listen({port: process.env.SERVER_PORT}, () => {
console.log(`🚀 Server ready`)
}
);
Where server is an ApolloServer
Everything works but the wss connection: the app can connect to the api using https://mydomain/api normally, and regular ws connection works too, if I run the app on http
Is just wss that I cant get to work
Any clues? What am I doing wrong here?
I found my own solution: the docker/nginx configs were right, but Apollo was expecting the websocket connection on wss://mydomain/graphql, even though the graphql server is running on https://mydomain/api
I failed to find a way to change that, so I added this to the nginx conf:
location ^~/graphql {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Frame-Options SAMEORIGIN;
proxy_pass http://api;
}
And it finally worked