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;
}
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.