Search code examples
node.jsvue.jsdocker-composemariadb

Docker-Compose: Vue UI does not connect to Node js Express CORS API


Edit: I checked the Log Output inside the Browser and got this message: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header has a value 'http://127.0.0.1:8888' that is not equal to the supplied origin.

2nd Edit: Added More Information at the bottom of the post

I'm probably code blind by now, so this is why I am begging for help. I am currently working on a vue app. I have a database (mariadb), a frontend (vue js) and a backend/api (node js express). Locally, it worked fine. Then I wanted to dockerize it. So I used this tutorial: https://www.bezkoder.com/docker-compose-react-nodejs-mysql/

I did everything as in the tutorial, except changes to the database (name of the db and var names) and then I started docker. I can see in my logs, that my server connects to mariadb, so that seems to work. I can also access my vue application and click around on my website as expected. Yet, when I try to give some input and send it to the database, nothing happens. I don't even see any logs about a post request in my api logs. So there seems to be a connections error between ui and api but I cannot find the mistake.

I used the tutorial and checked the var names that I changed, but there is no change.

My Folder Structure: enter image description here

My .env file

    MYSQLDB_USER=root
    MYSQLDB_ROOT_PASSWORD=root
    MYSQLDB_DATABASE=rgs
    MYSQLDB_LOCAL_PORT=3307
    MYSQLDB_DOCKER_PORT=3306

    NODE_LOCAL_PORT=6868
    NODE_DOCKER_PORT=8080

    CLIENT_ORIGIN=http://127.0.0.1:8888
    CLIENT_API_BASE_URL=http://127.0.0.1:6868/api/users

    VUE_LOCAL_PORT=8888
    VUE_DOCKER_PORT=80

My compose.yaml

  services: #individual services in isolated containers. Our application has three services
      mariadb:
        image: mariadb
        restart: unless-stopped
        env_file: ./.env
        environment:
          - MYSQL_ROOT_PASSWORD=$MYSQLDB_ROOT_PASSWORD
          - MYSQL_DATABASE=$MYSQLDB_DATABASE
        ports:
          - $MYSQLDB_LOCAL_PORT:$MYSQLDB_DOCKER_PORT
        volumes: #named volumes that keeps our data alive after restart / map volume folders
          - db:/var/lib/mysql
        networks: #facilitate communication between containers
          - backend

      api: #nodejs
        depends_on:
          - mariadb
        build: ./api
        restart: unless-stopped
        env_file: ./.env
        ports:
        - $NODE_LOCAL_PORT:$NODE_DOCKER_PORT
        environment: #provide setting using environment variables
          - DB_HOST=mariadb
          - DB_USER=$MYSQLDB_USER
          - DB_PASSWORD=$MYSQLDB_ROOT_PASSWORD
          - DB_NAME=$MYSQLDB_DATABASE
          - DB_PORT=$MYSQLDB_DOCKER_PORT
          - CLIENT_ORIGIN=$CLIENT_ORIGIN
        networks:
          - backend
          - frontend

      ui: #vue
        depends_on:
          - api #to be changed for kubernetes tests
        build:
          context: ./ui
          args: #add build arguments – environment variables accessible only during the build process
            - VUE_APP_API_BASE_URL=$CLIENT_API_BASE_URL
        ports: #You should note that the host port (LOCAL_PORT) and the container port (DOCKER_PORT) is different. Networked service-to-service communication uses the container port, and the outside uses the host port
          - $VUE_LOCAL_PORT:$VUE_DOCKER_PORT
        networks:
          - frontend

  volumes: 
    db:

  networks:
    backend:
    frontend:

My http-common.js

  import axios from "axios";

  export default axios.create({
    baseURL: process.env.VUE_APP_API_BASE_URL || "http://localhost:8080/api/users", //Die eigene URL des Clienten also mir selbst
    headers: {
      "Content-type": "application/json"
    }
  });

My VUE UI Dockerfile

    # Stage 1
    FROM node:14 as build-stage

    WORKDIR /ui
    COPY package.json .
    RUN npm install
    COPY . .

    ARG VUE_APP_API_BASE_URL
    ENV VUE_APP_API_BASE_URL=$VUE_APP_API_BASE_URL

    RUN npm run build

    # Stage 2
    FROM nginx:1.17.0-alpine

    COPY --from=build-stage /ui/dist /usr/share/nginx/html
    EXPOSE $VUE_DOCKER_PORT

    CMD nginx -g 'daemon off;'

My server Dockerfile

  # syntax=docker/dockerfile:1

  ARG NODE_VERSION=20.11.1

  FROM node:${NODE_VERSION}-alpine

  # Use production node environment by default.
  ENV NODE_ENV production

  WORKDIR /api
  #copy package.json file to the container
  COPY package.json . 
  RUN npm install
  #copies all the files inside the project directory to the container
  COPY . .
  CMD npm start

My server js

  require("dotenv").config();
  const express = require("express"); // Used to build the Rest api
  const cors = require("cors");

  const app = express();

  var corsOptions = {
    origin: process.env.CLIENT_ORIGIN || "http://localhost:8081" // Server end point - woher stammt die Anfrage, port muss stimmen
  };

  app.use(cors(corsOptions));

  // parse requests of content-type - application/json
  app.use(express.json());

  // parse requests of content-type - application/x-www-form-urlencoded
  app.use(express.urlencoded({ extended: true }));

  const db = require("./app/models");
  db.sequelize.sync()
    .then(() => {
      console.log("Synced db.");
    })
    .catch((err) => {
      console.log("Failed to sync db: " + err.message);
    });


  // FOR DEVELOPEMENT ONLY v
  /*db.sequelize.sync({ force: true }).then(() => {
    console.log("Drop and re-sync db.");
  });*/
  // FOR DEVELOPEMENT ONLY ^ 

  // simple route
  app.get("/", (req, res) => {
    res.json({ message: "Welcome to rgs." });
  });

  require("./app/routes/user.routes.js")(app);

  // set port, listen for requests
  const PORT = process.env.NODE_DOCKER_PORT || 8080;
  app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}.`);
  });

My db.config.js

    module.exports = {
      HOST: process.env.DB_HOST,
      USER: process.env.DB_USER,
      PASSWORD: process.env.DB_PASSWORD,
      DB: process.env.DB_NAME,
      port: process.env.DB_PORT,
      dialect: "mariadb",
      pool: {
        max: 5,
        min: 0,
        acquire: 30000,
        idle: 10000
      }
    }; 

Second Edit: I changed CLIENT_ORIGIN=http://172.0.0.1:8888 to CLIENT_ORIGIN=http://localhost:8888

This resulter in another error. Again, most of the pages I am calling are fine, but when I try to open the page with the input fields, now I get a resource not found error:

    Failed to load resource: the server responded with a status of 404 (Not Found)
    vue-router.mjs:3479 
 ChunkLoadError: Loading chunk 578 failed.
(error: http://localhost:8888/js/578.1a9ac476.js)
    at t.f.j (app.6e12f49f.js:1:5420)
    at app.6e12f49f.js:1:2555
    at Array.reduce (<anonymous>)
    at t.e (app.6e12f49f.js:1:2520)
    at component (app.6e12f49f.js:1:391)
    at Ue (vue-router.mjs:2101:40)
    at vue-router.mjs:3309:22

Part of my App.vue

    <script setup>
    import HeaderLogin from '@/components/AllSitesHeaderLogin.vue'
    import HeaderLoggedIn from '@/components/AllSitesHeaderLoggedIn.vue'
...
</script>

<template>
  <!-- <div v-if="!loggedIn">  -->
  <div v-if="true"> 
    <HeaderLogin></HeaderLogin>
  </div>

My AllSitesHeaderLogin

    <template>
  <header>
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/forum">Forum</router-link> |
      <router-link to="/add">Login</router-link> 
    </nav>
  </header>
</template>

<script>
export default {
  name: 'AllSitesHeaderLogin',
}
</script>

My router.js

    import { createWebHistory, createRouter } from "vue-router";

const routes = [
  { path: '/:pathMatch(.*)*', 
    component: () => import("./views/HomeView.vue") 
  },
  {
    path: "/",
    alias: "/home",
    name: "home",
    component: () => import("./views/HomeView.vue")
  },
  {
    path: "/login",
    name: "login",
    component: () => import("./views/LoginView.vue")
  },
  {
    path: "/add",
    name: "add",
    component: () => import("./views/RegisterView.vue")
  },
  {
    path: "/about",
    name: "about",
    component: () => import("./views/AboutView.vue")
  }
];

const router = createRouter({
  history: createWebHistory(),
  routes
});

export default router;

Solution

  • After changing the address from 127.0.0.1 to localhost (see my Edit notes) I had to clean my cache. This was the final step, now it works.

    Thank you, Estus Flask, because I checked my routes and also found an error there.