Search code examples
node.jspostgresqlexpressnginxmime-types

NGINX not returning response as application/json in my production app, but same code works in dev (express) and stage (Cowboy) env


I have a website that I developing using express as backend and Angular as frontend.

I created an API to access a PostgreSQL db (using node-postgres) which seems to work fine locally and in my stage environment (I am using Heroku for staging), but sadly not in my production environment (a Digital Ocean droplet, a.k.a. a virtual private server).

First of all, I have this nodeJS code to access my db and send the reponse to the client as JSON:

Courses.ts

import StatusCodes from "http-status-codes";
import { Request, Response } from "express";
import { getCourses } from "../db/Queries";

const { OK } = StatusCodes;

/**
 * Get courses.
 *
 * @param req
 * @param res
 * @returns
 */
 export async function getTipsCourses(req: Request, res: Response) {
  // get courses from db
  const articles = await getCourses();

  return res.status(OK).send(articles);
}

Queries.ts

import { pool } from ".";

export async function getCourses() {
  const client = await pool.connect();
  try {
    const query = {
      text: "SELECT title, description as caption, link, 'data:image/png;base64,'|| encode(picture, 'base64') AS media_url FROM courses WHERE active IS TRUE ORDER BY id",
    };
    const { rows } = await client.query(query);

    return rows;
  } catch (err) {
    throw err;
  } finally {
    // Make sure to release the client before any error handling,
    // just in case the error handling itself throws an error.
    client.release();
  }
}

routes.index.ts

import { Router } from 'express';
import { getNews, getNewsId } from './News';
import { getTipsTeachers } from './Teachers';
import { getTipsCourses } from './Courses';

const tipsRouter = Router();
// Teacher endpoint
tipsRouter.get('/get-teachers', getTipsTeachers);
// News endpoint
tipsRouter.get('/get-news', getNews);
tipsRouter.get('/get-newsid', getNewsId);
// Courses endpoint
tipsRouter.get('/get-courses', getTipsCourses);

// Export the base-router
const baseRouter = Router();
baseRouter.use('/users', userRouter);
baseRouter.use('/instagram', igRouter);
baseRouter.use('/tips', tipsRouter);
export default baseRouter;

When I deploy my application it works in my local environment (with its local db), and in the staging environment (with its db). When I deploy in my production environment (with its db), it does not, but only for one particular call (which is the one shown above, i.e. get-courses). It does however work for other calls to the API (e.g.) for get-teachers which has exactly the same structure of db table, db fields, and code (I have checked this several times).

The error I receive is SyntaxError: Unexpected token '<', "<!doctype "... is not valid JSON.

The big difference I see is that the http response from the server in production is different from the ones in my local and stage environments. It has in particular a different Content-Type.

Http response header from production to get-courses` call

HTTP/1.1 200 OK
Server: nginx/1.18.0 (Ubuntu)
Date: Thu, 30 Jan 2025 11:53:24 GMT
**Content-Type: text/html; charset=UTF-8**
Transfer-Encoding: chunked
Connection: keep-alive
X-DNS-Prefetch-Control: off
Expect-CT: max-age=0
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: no-referrer
X-XSS-Protection: 0
Cache-Control: public, max-age=0
Last-Modified: Thu, 30 Jan 2025 12:52:14 GMT
ETag: W/"488-194b744e830"
Content-Encoding: gzip

from stage environment

HTTP/1.1 200 OK
Server: Cowboy
Report-To: {"group":"heroku-nel","max_age":3600,"endpoints":[{"url":"https://nel.heroku.com/reports?ts=1738238636&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=PSupbABoGXIaNWwuG2y2KGC2IL81%2BCZKciJwevXfDO0%3D"}]}
Reporting-Endpoints: heroku-nel=https://nel.heroku.com/reports?ts=1738238636&sid=812dcc77-0bd0-43b1-a5f1-b25750382959&s=PSupbABoGXIaNWwuG2y2KGC2IL81%2BCZKciJwevXfDO0%3D
Nel: {"report_to":"heroku-nel","max_age":3600,"success_fraction":0.005,"failure_fraction":0.05,"response_headers":["Via"]}
Connection: keep-alive
X-Dns-Prefetch-Control: off
Expect-Ct: max-age=0
X-Frame-Options: SAMEORIGIN
Strict-Transport-Security: max-age=15552000; includeSubDomains
X-Download-Options: noopen
X-Content-Type-Options: nosniff
X-Permitted-Cross-Domain-Policies: none
Referrer-Policy: no-referrer
X-Xss-Protection: 0
**Content-Type: application/json; charset=utf-8**
Content-Length: 4006609
Etag: W/"3d22d1-3u619OT3QLjd4LJBiQnW+yFrjvg"
Date: Thu, 30 Jan 2025 12:03:56 GMT
Via: 1.1 vegur

finally, from local environment

HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
**Content-Type: application/json; charset=utf-8**
Content-Length: 4006609
ETag: W/"3d22d1-3u619OT3QLjd4LJBiQnW+yFrjvg"
Date: Thu, 30 Jan 2025 12:06:02 GMT
Connection: keep-alive

EDIT: ADDING NGINX CONFIGS

Here are my nginx configs if that might help figuring out what is causing the issue. I am also attaching my /etc/nginx/mime.types file to show you that json is listed there (and indeed, the call to other similar APIs return aplication/json response as expected and have no problems like this one).

/etc/nginx/nginx.conf

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 768;
    # multi_accept on;
}

http {

    ##
    # Basic Settings
    ##

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    # server_tokens off;

    server_names_hash_bucket_size 64;
    # server_name_in_redirect off;

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

    ##
    # SSL Settings
    ##

    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
    ssl_prefer_server_ciphers on;

    ##
    # Logging Settings
    ##

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

    ##
    # Gzip Settings
    ##

    gzip on;

    # gzip_vary on;
    # gzip_proxied any;
    # gzip_comp_level 6;
    # gzip_buffers 16 8k;
    # gzip_http_version 1.1;
    # gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

    ##
    # Virtual Host Configs
    ##

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

/etc/nginx/sites-enabled/tipsforfamilies

server {

    root /var/www/tipsforfamilies/html;
    index index.html index.htm index.nginx-debian.html;

    server_name tipsforfamilies.it;

    location / {
            proxy_pass http://localhost:3000;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
    }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate CENSORED; # managed by Certbot
    ssl_certificate_key CENSORED; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam CENSORED; # managed by Certbot



}
server {
    if ($host = www.tipsforfamilies.it) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = tipsforfamilies.it) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


        listen 80;
        listen [::]:80;

        server_name tipsforfamilies.it www.tipsforfamilies.it;
    return 404; # managed by Certbot
}
# this server will redirect all requests from www to non-www
server {

    server_name www.tipsforfamilies.it;
    listen [::]:443 ssl; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate CENSORED; # managed by Certbot
    ssl_certificate_key CENSORED; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam CENSORED; # managed by Certbot   

    return 301 https://tipsforfamilies.it$request_uri;

}

/etc/nginx/mime.types

types {
    text/html                             html htm shtml;
    text/css                              css;
    text/xml                              xml;
    image/gif                             gif;
    image/jpeg                            jpeg jpg;
    application/javascript                js;
    application/atom+xml                  atom;
    application/rss+xml                   rss;

    text/mathml                           mml;
    text/plain                            txt;
    text/vnd.sun.j2me.app-descriptor      jad;
    text/vnd.wap.wml                      wml;
    text/x-component                      htc;

    image/png                             png;
    image/tiff                            tif tiff;
    image/vnd.wap.wbmp                    wbmp;
    image/x-icon                          ico;
    image/x-jng                           jng;
    image/x-ms-bmp                        bmp;
    image/svg+xml                         svg svgz;
    image/webp                            webp;

    application/font-woff                 woff;
    application/java-archive              jar war ear;
    application/json                      json;
    application/mac-binhex40              hqx;
    application/msword                    doc;
    application/pdf                       pdf;
    application/postscript                ps eps ai;
    application/rtf                       rtf;
    application/vnd.apple.mpegurl         m3u8;
    application/vnd.ms-excel              xls;
    application/vnd.ms-fontobject         eot;
    application/vnd.ms-powerpoint         ppt;
    application/vnd.wap.wmlc              wmlc;
    application/vnd.google-earth.kml+xml  kml;
    application/vnd.google-earth.kmz      kmz;
    application/x-7z-compressed           7z;
    application/x-cocoa                   cco;
    application/x-java-archive-diff       jardiff;
    application/x-java-jnlp-file          jnlp;
    application/x-makeself                run;
    application/x-perl                    pl pm;
    application/x-pilot                   prc pdb;
    application/x-rar-compressed          rar;
    application/x-redhat-package-manager  rpm;
    application/x-sea                     sea;
    application/x-shockwave-flash         swf;
    application/x-stuffit                 sit;
    application/x-tcl                     tcl tk;
    application/x-x509-ca-cert            der pem crt;
    application/x-xpinstall               xpi;
    application/xhtml+xml                 xhtml;
    application/xspf+xml                  xspf;
    application/zip                       zip;

    application/octet-stream              bin exe dll;
    application/octet-stream              deb;
    application/octet-stream              dmg;
    application/octet-stream              iso img;
    application/octet-stream              msi msp msm;

    application/vnd.openxmlformats-officedocument.wordprocessingml.document    docx;
    application/vnd.openxmlformats-officedocument.spreadsheetml.sheet          xlsx;
    application/vnd.openxmlformats-officedocument.presentationml.presentation  pptx;

    audio/midi                            mid midi kar;
    audio/mpeg                            mp3;
    audio/ogg                             ogg;
    audio/x-m4a                           m4a;
    audio/x-realaudio                     ra;

    video/3gpp                            3gpp 3gp;
    video/mp2t                            ts;
    video/mp4                             mp4;
    video/mpeg                            mpeg mpg;
    video/quicktime                       mov;
    video/webm                            webm;
    video/x-flv                           flv;
    video/x-m4v                           m4v;
    video/x-mng                           mng;
    video/x-ms-asf                        asx asf;
    video/x-ms-wmv                        wmv;
    video/x-msvideo                       avi;
}


Solution

  • So, I have solved the problem. It had apparently nothing to do with nginx configuration. The problem is my incompetence in deploying applications. I have deployed this application more than 2 years ago, and I figured out the way I have been updating my code in production was not the best.

    It turned out that after an update of the backed express code, my usual way of deploying to production by compiling locally and overwriting the dist folder on the production server machine was not enough. It used to work for updates on the front-end side though.

    I forgot that I had my application on the production server running on a tmux session in localhost:3000 and nginx was redirecting everything on my server domain. So, to solve the problem, I had to install the needed node modules and run npm start, and everything worked as expected.

    I still need to learn more about how to deploy my full-stack application properly, but for now I am just happy I understood the issue. I hope this might help anybody in a similar situation.