Next.js Discord

Discord Forum

Route Handlers in Docker container

Answered
gryt posted this in #help-forum
Open in Discord
Hi I have a nextjs application that I built using antd components. I have built a backend using FastAPI and made use of the route handlers shown in this documentation:
https://nextjs.org/docs/app/building-your-application/routing/route-handlers

Everything works fine in dev environment npm run dev however when I containerise the application using Docker. I get the error that the localhost:3000/api/upload route is not working. I have checked that the endpoint localhost:8000/upload works fine. For the upload button I am using a component from antdesign and it made use of the props element to trigger the actions. Anyone who knows. Can provide some advice. Thanks!

In @/api/upload/route.js:

export async function POST(req) {
    const formData = await req.formData();

    const res = await fetch('http://localhost:8000/upload', {
        method: 'POST',
        body: formData,
    });

    const data = await res.json();
    console.log("this is server res data", data);
    return NextResponse.json(data);
}



In client component @/components/Table.js:
    const props = {
        name: 'file',
        action: 'http://localhost:3000/api/upload',
        headers: {
            authorization: 'authorization-text',
        },
        async onChange(info) {
            if (info.file.status !== 'uploading') {
                console.log(info.file, info.fileList);
                console.log("hello world");
            }
            if (info.file.status === 'done') {
                message.success(`file uploaded successfully`);
                // console.log('##info', info.file.response.data);
                setDataSource(info.file.response.data);

            } else if (info.file.status === 'error') {
                message.error(`file upload failed.`);
            }
        },
    };

<Upload {...props} showUploadList={false}>
                <Button icon={<UploadOutlined />}>Click to Upload</Button>
</Upload>
Answered by Ray
ok try changing http://localhost:8000/upload to http://backend:8000/upload when process.env.NODE_ENV === 'production'
View full answer

40 Replies

bump
@gryt Hi I have a nextjs application that I built using antd components. I have built a backend using FastAPI and made use of the route handlers shown in this documentation: https://nextjs.org/docs/app/building-your-application/routing/route-handlers Everything works fine in dev environment `npm run dev` however when I containerise the application using Docker. I get the error that the `localhost:3000/api/upload` route is not working. I have checked that the endpoint `localhost:8000/upload` works fine. For the upload button I am using a component from antdesign and it made use of the `props` element to trigger the actions. Anyone who knows. Can provide some advice. Thanks! In @/api/upload/route.js: javascript export async function POST(req) { const formData = await req.formData(); const res = await fetch('http://localhost:8000/upload', { method: 'POST', body: formData, }); const data = await res.json(); console.log("this is server res data", data); return NextResponse.json(data); } In client component @/components/Table.js: javascript const props = { name: 'file', action: 'http://localhost:3000/api/upload', headers: { authorization: 'authorization-text', }, async onChange(info) { if (info.file.status !== 'uploading') { console.log(info.file, info.fileList); console.log("hello world"); } if (info.file.status === 'done') { message.success(`file uploaded successfully`); // console.log('##info', info.file.response.data); setDataSource(info.file.response.data); } else if (info.file.status === 'error') { message.error(`file upload failed.`); } }, }; <Upload {...props} showUploadList={false}> <Button icon={<UploadOutlined />}>Click to Upload</Button> </Upload>
what is the error you got from localhost:3000/api/upload?
@Ray what is the error you got from localhost:3000/api/upload?
from the client component, the error is this:
 ⨯ TypeError: fetch failed
    at node:internal/deps/undici/undici:12344:11
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  cause: Error: connect ECONNREFUSED 127.0.0.1:8000
      at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1595:16)
      at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
    errno: -111,
    code: 'ECONNREFUSED',
    syscall: 'connect',
    address: '127.0.0.1',
    port: 8000
  }
}


on the network side:
url: http://localhost:3000/api/upload
Status: 500 Internal Server Error
Source: NetworkAddress: ::1:3000
@Ray look like its unable to connect :8000?
Yup i am running it in docker. port 8000 is the port to my backend service
@Ray look like its unable to connect :8000?
since i am using fastapi, i was able to verified that port 8000 connection works fine as i am able to acccess the swagger UI to test the endpoints
@gryt since i am using fastapi, i was able to verified that port 8000 connection works fine as i am able to acccess the swagger UI to test the endpoints
could you connect to the container and try to curl the endpoint and see if it can connect it?
@Ray could you connect to the container and try to curl the endpoint and see if it can connect it?
as i am currently running 2 containers, one for the nextjs frontend and the other for my backend. should i access the frontend container or the backend to do this testing?
yup i deploy it with docker-compose: this is my current file:

docker-compose.yml
version: '3.8'

services:
  backend:
    container_name: backend
    build:
      context: ./backend
    command: uvicorn app:app --host 0.0.0.0 --port 8000 --reload
    ports:
      - 8000:8000
    environment:
      API_KEY: ${API_KEY}
      URI: ${URI}
      MONGO_DB: ${MONGO_DB}
    restart: unless-stopped
    networks:
      - internal-network
      - default
    #depends_on:
      #caddy:
        #condition: service_started
    
  frontend:
    container_name: frontend
    build:
      context: ./frontend/my-app
      dockerfile: Dockerfile
    restart: always
    ports:
      - 3000:3000
    networks:
      - internal-network
      - default
Answer
@gryt where should i include `process.env.NODE_ENV === 'production'` ?
like this
export async function POST(req) {
  const formData = await req.formData();

  const apiUrl = process.env.NODE_ENV === 'production' ? 'backend': 'localhost'
  const res = await fetch(`http://${apiUrl}:8000/upload`, {
      method: 'POST',
      body: formData,
  });

  const data = await res.json();
  console.log("this is server res data", data);
  return NextResponse.json(data);
}
@Ray like this ts export async function POST(req) { const formData = await req.formData(); const apiUrl = process.env.NODE_ENV === 'production' ? 'backend': 'localhost' const res = await fetch(`http://${apiUrl}:8000/upload`, { method: 'POST', body: formData, }); const data = await res.json(); console.log("this is server res data", data); return NextResponse.json(data); }
am i suppose to declare the env variable within my Dockerfile? it is currently like this:

FROM node:20-alpine AS base

# Step 1. Rebuild the source code only when needed
FROM base AS builder

WORKDIR /app

# Install dependencies based on the preferred package manager
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
# Omit --production flag for TypeScript devDependencies
RUN \
  if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
  elif [ -f package-lock.json ]; then npm ci; \
  elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \
  # Allow install without lockfile, so example works even without Node.js installed locally
  else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \
  fi
# COPY . .
COPY app ./app
COPY components ./components
COPY public ./public
COPY next.config.js .
COPY jsconfig.json .

# Environment variables must be present at build time
# https://github.com/vercel/next.js/discussions/14030
# ARG ENV_VARIABLE
# ENV ENV_VARIABLE=${ENV_VARIABLE}
# ARG NEXT_PUBLIC_ENV_VARIABLE
# ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE}

# Build Next.js based on the preferred package manager
RUN \
  if [ -f yarn.lock ]; then yarn build; \
  elif [ -f package-lock.json ]; then npm run build; \
  elif [ -f pnpm-lock.yaml ]; then pnpm build; \
  else yarn build; \
  fi
# Step 2. Production image, copy all the files and run next
FROM base AS runner

WORKDIR /app

# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# this HOSTNAME environment variable in used in server.js
ENV HOSTNAME "0.0.0.0"

CMD ["node", "server.js"]
@gryt am i suppose to declare the env variable within my Dockerfile? it is currently like this: docker FROM node:20-alpine AS base # Step 1. Rebuild the source code only when needed FROM base AS builder WORKDIR /app # Install dependencies based on the preferred package manager COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./ # Omit --production flag for TypeScript devDependencies RUN \ if [ -f yarn.lock ]; then yarn --frozen-lockfile; \ elif [ -f package-lock.json ]; then npm ci; \ elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; \ # Allow install without lockfile, so example works even without Node.js installed locally else echo "Warning: Lockfile not found. It is recommended to commit lockfiles to version control." && yarn install; \ fi # COPY . . COPY app ./app COPY components ./components COPY public ./public COPY next.config.js . COPY jsconfig.json . # Environment variables must be present at build time # https://github.com/vercel/next.js/discussions/14030 # ARG ENV_VARIABLE # ENV ENV_VARIABLE=${ENV_VARIABLE} # ARG NEXT_PUBLIC_ENV_VARIABLE # ENV NEXT_PUBLIC_ENV_VARIABLE=${NEXT_PUBLIC_ENV_VARIABLE} # Build Next.js based on the preferred package manager RUN \ if [ -f yarn.lock ]; then yarn build; \ elif [ -f package-lock.json ]; then npm run build; \ elif [ -f pnpm-lock.yaml ]; then pnpm build; \ else yarn build; \ fi # Step 2. Production image, copy all the files and run next FROM base AS runner WORKDIR /app # Don't run production as root RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs USER nextjs COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static # this HOSTNAME environment variable in used in server.js ENV HOSTNAME "0.0.0.0" CMD ["node", "server.js"]
no need, next will do it
@Ray no need, next will do it
i see, i am trying to rebuild the containers now, will try it out
let me know if it works
@Ray let me know if it works
Nope it doesnt.
   â–² Next.js 14.0.4
   - Local:        http://localhost:3000
   - Network:      http://0.0.0.0:3000

 ✓ Ready in 51ms
 ⨯ TypeError: fetch failed
    at node:internal/deps/undici/undici:12344:11
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  cause: Error: connect ECONNREFUSED 172.22.0.3:8000
      at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1595:16)
      at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
    errno: -111,
    code: 'ECONNREFUSED',
    syscall: 'connect',
    address: '172.22.0.3',
    port: 8000
  }
}
this are the errors when i tried to curl

/app $ curl localhost:8000/
curl: (7) Failed to connect to localhost port 8000 after 1 ms: Couldn't connect to server
/app $ curl backend:8000/
curl: (7) Failed to connect to backend port 8000 after 7 ms: Couldn't connect to server
@Ray try curl backend:8000
yup i tried both, localhost:8000 and backend:8000
which mean the containers cannot communicate each other
@Ray which mean the containers cannot communicate each other
i see, i have tried to add an internal network for both of them
@Ray is this full code on docker-compose?
this is the full code:

version: '3.8'

services:
  backend:
    container_name: backend
    build:
      context: ./backend
    command: uvicorn app:app --host 0.0.0.0 --port 8000 --reload
    ports:
      - 8000:8000
    environment:
      API_KEY: ${API_KEY}
      URI: ${URI}
      MONGO_DB: ${MONGO_DB}
    restart: unless-stopped
    networks:
      - internal-network
      - default
    #depends_on:
      #caddy:
        #condition: service_started
    
  frontend:
    container_name: frontend
    build:
      context: ./frontend/my-app
      dockerfile: Dockerfile
    restart: always
    ports:
      - 3000:3000
    networks:
      - internal-network
      - default

networks:
  internal-network:
    name: internal-network
    driver: bridge
version: '3.8'

services:
  backend:
    container_name: backend
    build:
      context: ./backend
    command: uvicorn app:app --host 0.0.0.0 --port 8000 --reload
    ports:
      - 8000:8000
    environment:
      API_KEY: ${API_KEY}
      URI: ${URI}
      MONGO_DB: ${MONGO_DB}
    restart: unless-stopped
    networks:
      - internal-network
      - app-network
    #depends_on:
      #caddy:
        #condition: service_started
    
  frontend:
    container_name: frontend
    build:
      context: ./frontend/my-app
      dockerfile: Dockerfile
    restart: always
    ports:
      - 3000:3000
    networks:
      - internal-network
      - app-network

networks:
  internal-network:
    name: internal-network
    driver: bridge
  app-network:
    external: true
   
try this
Hey roy, apologizes. Earlier i realized my backend wasnt running properly due to a small bug within the scripts. I managed to fixed it and i have tried curl backend:8000 and it is responding with a health_check message i set. After that i tried to test the application fully and it worked.

The solution was to change it to my backend container service and it worked. Thank you so much for that
By the way, would you recommend using route handlers for production? It works fine if i just make a direct http request from the client component. Instead of routing it through a /api route through the server side in nextjs
i see. does server action has the same concept as route handlers? for context, i am very new to nextjs.
@gryt i see. does server action has the same concept as route handlers? for context, i am very new to nextjs.
basically, you defined an async function which return the result and next will create the route handler for you
and you just execute the function in the client component
so you get typesafe for the input and output
if you use typescript
I see, alright i will look into it some other day. Also, since i am new to frontend dev, i am struggling with grasping the concept of css. I have an idea of how i want to design my app on figma, but i could not really execute in code. are there any templates you would recommend me using? or i could just hop on using tailwind now