Search code examples
node.jsdockernginxnext.jsdocker-compose

NextJS fails to call my backend node app for multi containerization of services


I am trying to learn docker. I have a next.js app and a node.js app.

In my next.js app, in page.tsx I am calling my backend api

export const revalidate = 0;

export default async function Home() {
  const response = await fetch("/api/getRandomNumber");
  const data = await response.json();
  console.log(data);

  return (
    <main className={styles.main}>
      <h1>{data.lastInfo.randomNumber}</h1>
    </main>
  );
}

In nodejs I have just created an endpoint getRandomNumber with expressjs

router.get("/getRandomNumber", async (req, res) => {
  const randNumber = `${Math.random() * 100}`;
  const info = new Info({ randomNumber: randNumber });
  await info.save();
  const lastInfo = await Info.findOne({
    where: { randomNumber: randNumber },
  });

  return res.status(200).send({ lastInfo });
});

I know next.js is a full stack framework, but here I am trying to learn how to dockerize multiple services so created separate node.js app

Below is my docker file for next.js app

FROM node:18-alpine

WORKDIR /usr/app

COPY ./package.json ./

RUN npm install

COPY ./ ./

CMD ["npm","run","dev"]

Below is my docker file for node.js app

FROM node:18-alpine

WORKDIR /usr/app

COPY ./package.json ./

RUN npm install

COPY ./ ./

CMD ["npm","run","dev"]

Next I want to use nginx so I created a folder for it and inside it I created default.conf and Dockerfile.dev

In default.conf

upstream nextapp {
    server nextapp:3000;
}

upstream nodeapp {
    server nodeapp:3001;
}

server {
    listen 80;

    location / {
        proxy_pass http://nextapp;
    }

    location /api {
        rewrite /api/(.*) /$1 break;
        proxy_pass http://nodeapp;
    }

    location /ws {
      proxy_pass http://nextapp;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
  }
}

So I am appending /api to my nodejs app via default.conf

In Dockerfile.dev

FROM nginx
COPY ./default.conf /etc/nginx/conf.d/

Then one docker-compose.yml file for next.js, nginx and node.js

version: "3"
services:
  postgres:
    image: "postgres:latest"
    environment:
      - POSTGRES_PASSWORD=postgres_password
  nginx:
    restart: always
    build:
      dockerfile: Dockerfile.dev
      context: ./nginx
    ports:
      - "3050:80"
    depends_on:
      - nodeapp
      - nextapp
  nodeapp:
    build:
      dockerfile: Dockerfile.dev
      context: ./nodeapp
    volumes:
      - /app/node_modules #don't try to override this folder
      - ./nodeapp:/app
    environment:
      - PGUSER=postgres
      - PGHOST=postgres
      - PGDATABASE=postgres
      - PGPASSWORD=postgres_password
      - PGPORT=5432
  nextapp:
    build:
      dockerfile: Dockerfile.dev
      context: ./nextapp
    volumes:
      - /app/node_modules
      - ./nextapp:/app
    environment:
      - WDS_SOCKET_PORT=0

I am able to see my node.js app on http://localhost:3050/api/getRandomNumber but in my next.js app I get error when I try to call the node.js api. If I comment out the fetch call in my next.js app it works fine on http://localhost:3050/

If I try to call http://localhost:3050/api/getRandomNumber in fetch I get error saying Error: fetch failed

enter image description here

If I try to call just /api/getRandomNumber in fetch I get error saying Failed to parse url

enter image description here

Github repo here

More screenshot

enter image description here


Solution

  • When you do fetch() in NextJS server-side, you have to use the full URL. So it should be something like fetch('http://localhost:3050/api/getRandomNumber').

    By default compose creates a network and all the containers joins to that. But if you update your code to use localhost as base URL you will get an ECONNREFUSED error. It is because localhost is not understoodable in context of docker - it points to the containers inner network.

    So what docker can understand is the service name. If you put the service name there as base url, then it will work.

    Here is the updated code:

    
    const response = await fetch("http://nginx/api/getRandomNumber", { method: 'GET' });
    
    

    Of course this is not something i'd go to production with. At least the base URL should be an env variable. And in production you wont run the containers in compose i think, normally those should be standalone EC2/S or Droplet instances.