angulardockernginxdjango-rest-framework

Angular does not retrieve objects nor messages from django endpoints on docker


Trying to deploy Django Rest Framework and Angular with Docker.

I have 3 containers: django, db and angular (with nginx on it)

The deployment is completed and I can open localhost:80 and see my angular components.

However, the component service is calling to http://localhost:8000/api/sensores/ to retrieve all the objects in the db (I made sure there is one object there, which I created from django shell, and reviewed it on the db container) and it is getting an error. Two logs appear on Chrome console:

Error al obtener sensores: ct {headers: e, status: 0, statusText: 'Unknown Error', url: 'http://localhost:8000/api/sensores/', ok: false, …}error: ProgressEvent {isTrusted: true, lengthComputable: false, loaded: 0, total: 0, type: 'error', …}headers: e {normalizedNames: Map(0), lazyUpdate: null, headers: Map(0)}message: "Http failure response for http://localhost:8000/api/sensores/: 0 Unknown Error"name: "HttpErrorResponse"ok: falsestatus: 0statusText: "Unknown Error"url: "http://localhost:8000/api/sensores/"[[Prototype]]: zn ... polyfills-FFHMD2TL.js:1

GET http://localhost:8000/api/sensores/ net::ERR_CONNECTION_REFUSED

From exec console in my angular-nginx container, I can make this:

curl http://api:8000/api/sensores/

And get the object I'm looking for, but angular can't do it itself.

Here my docker-compose.yml:

version: '3.8'

services:
  angular:
    container_name: angular-nginx
    build: ./hello
      # context: ./hello
    ports:
      - "80:80"
    volumes:
      - ./hello/nginx.conf:/etc/nginx/nginx.conf:ro  
      - ./hello/dist/hello/browser:/usr/share/nginx/html
    depends_on:
      - db

  api:
    build:
      context: ./django
    command: gunicorn helloDocker.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - ./django:/app
    expose:
      - "8000"
    environment:
      - DEBUG=False
      - DJANGO_SECRET_KEY=your_secret_key
      - DATABASE_URL=postgres://myuser:mypassword@localhost:5432/vituclim
    depends_on:
      - db

  # Servicio de base de datos
  db:
    container_name: postgre
    image: postgres:latest
    environment:
      POSTGRES_DB: vituclim
      POSTGRES_USER: myuser
      POSTGRES_PASSWORD: mypassword
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:

This is my nginx.conf, in angular project folder:

user nginx;
worker_processes auto; 
pid /var/run/nginx.pid; 

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types; 
    default_type  application/octet-stream; 

    access_log  /var/log/nginx/access.log;

    server {
        listen 80;

        server_name localhost;

        location / {
            root /usr/share/nginx/html;  
            index index.html;            

            try_files $uri $uri/ /index.html;
        }

        location /api/ {
            proxy_pass http://api:8000/;o
            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;
        }

    }
}

For the last config for /api/ directions, it doesn't work with or without it.

I expect to call the component service and retrieve a list of objects (Sensor) to show on the HTML. The TS & HTML of the component are fine, as the problem comes from the service. This is the service component:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Sensor } from './sensor';

@Injectable({
  providedIn: 'root'
})
export class HelloWorldService {

  private nombreContenedorDjango = 'api'

  private apiUrl = 'http://'+this.nombreContenedorDjango+':8000/api'; // URL del backend

  constructor(private http: HttpClient) {}

  getMessage(): Observable<{message: string}> {
    return this.http.get<{message: string}>(`${this.apiUrl}/mensaje/`);
  }

  getSensores(): Observable<Sensor[]> {
    return this.http.get<any>(`${this.apiUrl}/sensores/`);
  }
}

I suppose the problem comes from angular, or nginx configuration. But I can't see it clearly.


Solution

  • I have already answered your question in the comments, but unfortunately, it seems you did not understand what I was trying to explain.

    Your Angular code, once built, is executed in the user's browser's JavaScript VM. When you make a request to http://localhost/api/sensores/ from your JavaScript code, you are effectively pointing to the user's computer. Naturally, there is no server running there to respond.

    When you make a request to http://api:8000/api/sensores/, you are asking the user's browser to resolve the api domain name. However, this domain is resolvable only via Docker's internal name resolution system, which is obviously won't be used by the user's browser/OS.

    Instead, you should make a relative request like /api/sensores/ (as already mentioned here). The user's browser will automatically use the current web page's domain (or IP address) and request scheme (HTTP/HTTPS) to form the full URL:

    export class HelloWorldService {
    
      private apiUrl = '/api/'; // URL del backend
    
      constructor(private http: HttpClient) {}
    
      getMessage(): Observable<{message: string}> {
        return this.http.get<{message: string}>(`${this.apiUrl}mensaje/`);
      }
    
      getSensores(): Observable<Sensor[]> {
        return this.http.get<any>(`${this.apiUrl}sensores/`);
      }
    }
    

    This request will then be received by your frontend angular-nginx container and routed by nginx to your api container using the following configuration block:

    location /api/ {
        proxy_pass http://api:8000; # Do not use the trailing slash here!
        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;
    }
    

    As also noted here, it is common to define an environment variable (e.g., API_URL) with a value like http://localhost:8000/api/ for development and /api/ for production (see this answer for the example). You can then use it in your code as follows:

    import {environment} from '../../environments/environment';
    
    ...
    
    export class HelloWorldService {
    
      private apiUrl = environment.API_URL;
      ...
    
    }