Node JS with Express leaking memory - node.js

I am new to Node JS. I have a Node JS + Express app that is causing a Docker container to repeatedly stop with exit code 137 - out of memory error every 5-10 minutes.
The app could not be more simple serving up static html, css, js and images
const express = require('express')
const app = express()
const port = 8080
app.use(express.static('public'))
app.get('/', (req, res) => {
res.sendFile('index.html', { root: __dirname + "/public" } );
})
app.listen(port, () => {
console.log(`web app listening at http://localhost:${port}`)
})
Directory structure
--root
server.js
--public
index.html
--css
--js
--img
The host is an ECS EC2 instance - T2 small with 2Gb memory. The container task allocation 1024 MiB of memory.
Dockerfile
# from nodes 12 image
FROM node:12
MAINTAINER coco
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
COPY package*.json ./
RUN npm install
# Bundle app source - copy all files/folders in current directory
COPY . .
# Specify ports
EXPOSE 8080
# Run the app
CMD [ "node", "server.js" ]
What about this Node app is causing a memory leak? Though, it may be express that's leaking. I'm using express 4.17.1

Related

How to Bootstrap Express Server together with GRPC Server on Cloud Run using Nginx

I have a NodeJS Server that houses two servers in one process.
//This function immediately starts the express server
async startExpressServer() {
const app=express();
const port = 3000;
app.listen(port, async function () {
console.log(`Express Service running on port ${port} in ${process.env.NODE_ENV} mode`);
});
}
//This function immediately starts the gRPC server
async startGRPCServer(){
let port = 4000;
grpcServer.bindAsync(`0.0.0.0:${port}`, grpc.ServerCredentials.createInsecure(),
()
=> {
grpcServer.start();
console.log(`GRPC Server running on ${port}`);
});
}
Since cloud run exposes just a single port per service instance, I attempted to proxy the ports of the above servers using nginx with the following nginx configurations in my nginx.conf file which is sitting at the root of my project:
upstream grpc-server {
server 0.0.0.0:4000;
}
server {
listen $PORT;
server_name customer-service;
location /customer{ # Send requests on this route to the grpc server
grpc_pass grpcs://grpc-server;
}
location /v1 { # Send requests on this route to the express server
proxy_pass http://0.0.0.0:3000;
}
}
And below is the script I have in my Dockerfile to start the app as a whole
FROM node:18-slim
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --only=production
COPY . ./
# server environment
FROM nginx:alpine
COPY nginx.conf /etc/nginx/conf.d/configfile.template
CMD sh -c "envsubst '\$PORT' < /etc/nginx/conf.d/configfile.template > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;' && node server.js"
When I deployed the app to cloud run the app refuse to run. I have a very strong feeling certain configurations above are not correct, please I would appreciate any corrections and or suggestions.
Thank you.

Docker Container Not Showing Up

I am following along with this tutorial to learn how to containerize a node.js / express application I'm working on. Everything seems to be working fine, except that when I docker ps after building the container, I don't see my running container.
Node.js / express code:
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
app.get('/', (req, res) => {
res.send("Hi there")
})
app.listen(port, () => console.log("Listening On Port 3000"))
Dockerfile
FROM node:latest
WORKDIR /untitled
COPY package.json .
RUN npm install
EXPOSE 3000
COPY . ./
CMD node server.js
My working directory is "untitled" because that's the default name that Webstorm gives my project and I haven't changed it yet.
The image build seems to have worked fine
As well as the container build
But docker ps isn't showing me any containers

node express cannot GET static content

Im running a node app inside of /opt/myapp directory.
I have haproxy in front content switching on path_beg /myapp
backend server is listening on port 3000
directory structure:
/opt/myapp
index.js
package, modules
static
public
myfile.html
const express = require("express");
const path = require('path');
const app = express();
app.listen(3000, () => console.log("listening on 3000 "+__dirname+" "+process.cwd()));
app.use(express.static(__dirname+'/static/public')); //nope
//app.use(express.static('..'+'/static/public')); //nope
//app.use(express.static(path.join(__dirname, '/static/public/'))); //nope
Where __dirname outputs /opt/myapp and process.cwd() outputs /opt/myapp
I tried both concantenation and path.join with same results. Cannot GET myfile.html
curl directly on the server to http://host.com:3000 does work by returning the page,
but from browser (in front of haproxy), http://host.com/myapp/myfile.html does not work.
I suppose that I can remove the /myapp from the path in haproxy on the backend, but is there a way with express that i can account for the base directory?
This worked:
app.use('/myapp/',express.static('static/public'));

NodeJS Express Angular serving a login page entirely separate from the SPA

I am building a SPA in Angular 8. I have a multi-stage Docker image that runs ng build to build the distribution and then a simple express server is used to serve the application. (Note: The backend API is on an entirely separate express server.)
Requirements
I need to setup a login page "outside" of the SPA. The login page must be displayed if the user is not authenticated, that way the SPA is not bootstrapped until the authentication is successful (by checking a bearer token in the authorization header).
Questions
Do I need a separate Angular installation to load the login page separate from the rest of the app? Or, should I just skip Angular for the login page and build a simple express page with Pug that sends a POST to the API for authentication?
Note: I am seeking general advice on how to proceed and any examples would be very helpful as well.
Dockerfile
### Dev, QA, and Production Docker servers ###
### Stage 1: Build ###
# Base image
FROM node:12 as builder
# Set working directory
RUN mkdir -p /home/angular/app
WORKDIR /home/angular/app
# Add `/home/angular/app/node_modules/.bin` to $PATH
ENV PATH /home/angular/app/node_modules/.bin:$PATH
# Install and cache app dependencies
COPY angular/package.json /home/angular/app/package.json
RUN npm install -g #angular/cli#8 \
&& npm install
# Add app
COPY ./angular /home/angular/app
# Generate build
RUN ng build --output-path=dist
### Stage 2: Server ###
FROM node:12
USER node
# Create working directory
RUN mkdir /home/node/app
## From 'builder' stage copy over the artifacts in dist folder
COPY --from=builder --chown=node /home/angular/app/dist /home/node/app/dist
# Copy Express server code to container
COPY --chown=node ./express /home/node/app
WORKDIR /home/node/app
RUN npm install
# Expose ports
EXPOSE 4201
CMD ["npm", "start"]
Express server for Angular SPA
This server is run when the Dockerfile executes its command CMD ["npm", "start"]
const express = require('express');
const http = require('http');
const app = express();
// Set name of directory where angular distribution files are stored
const dist = 'dist';
// Set port
const port = process.env.PORT || 4201;
// Serve static assets
app.get('*.*', express.static(dist, { maxAge: '1y' }));
// Serve application paths
app.all('*', function (req, res) {
res.status(200).sendFile(`/`, { root: dist });
});
// Create server to listen for connections
const server = http.createServer(app);
server.listen(port, () => console.log("Node Express server for " + app.name + " listening on port " + port));
Angular supports multiple applications under same project. You can create separate login application using following command:
ng generate application <you-login-app-name-here>
This way you can keep only login related code in '' and other code in you main app. You can build, test or run this new app separate using following commands:
ng build <you-login-app-name-here>
ng test <you-login-app-name-here>
ng serve <you-login-app-name-here>
Angular will generate the build output in /dist/ folder which can be mapped to express route to serve file.

CRA, Node.js, nginx in Docker?

I'm starting off a new project. I currently have a strucute like this, from root folder:
/app (CRA frontend app)
/server (Node.js Express app)
Dockerfile
docker-compose.yml
My requirements is the following:
Development
Fire up Docker that creates necessary container(s)
Hot reloading for frontend React app (using CRA)
Node.js server that can serve my React app with SSR (automatically updated when editing)
Accessible via http://localhost:3000
Production
Potentially fire up Docker that creates necessary container(s)
Creates production ready version of React app
Creates production ready version of Express app
Accessible via port 80
Where I am right now is somewhere between everything. I don't know how to setup Docker the right way in order to make this whole thing work, and I don't really know how to structure my React app vs the Express app while developing. The Production part seems to be easier as soon as I know how to structure the Development part... + Nginx as a proxy for the Express app?
I'm currently building a Docker container which fires up a container where hot reloading is working etc, but I don't know how to setup the Express part so they work nicely together...?
Any help is much appreciated.
Thanks
Very broad question. Perhaps better to break it down into more direct questions. Anyway, I don't think running your dev setup in Docker is ideal. Instead build your app normally with CRA. Then deploy in Docker.
In my own projects, I have a docker container running a node server which serves the react app using SSR.
Here is the docker part. Note that your package.json should have a script named start:prod for this to work. That script then starts your app in production.
// --- Dockerfile
# Pulled from docker hub and has everything
# needed to run a node project
FROM node:alpine
ENV PORT 3000
# Navigate (cd) to the app folder in the docker container
WORKDIR /usr/src/app
# Copy all package.json / package-lock.json etc. to the root folder
# Executed on build: docker build .
COPY ./package*.json ./
RUN npm i
# copy entire project into docker container
COPY . .
# build front-end with react build scripts and store them in the build folder
RUN npm run build
EXPOSE 3000
CMD ["npm", "run", "start:prod"]
Here's the express server that will start the server.
// -- server.js
import express from "express";
import router from "./controller/index";
const app = express();
const port = 4000;
// Tell the app to use the routes above
app.use(router);
// start the app
app.listen(port, () => {
console.log(`express running on port ${port}`);
});
Here is the controller/index.js file you'll need to start up
// -- controller/index.js
import express from "express";
import path from "path";
import serverRenderer from '../middleware/renderer';
const router = express.Router();
// root (/) should always serve our server rendered page
router.use('^/$', serverRenderer());
// other static resources should just be served as they are
router.use(express.static(
path.resolve(__dirname, '..', '..', 'build'),
{ maxAge: '30d' },
));
export default router;
And finally the renderer which renders the app on the server.
// -- renderer.js
import React from "react";
import { renderToString } from "react-dom/server";
import App from "../../src/App";
const path = require("path");
const fs = require("fs");
export default () => (req, res) => {
// point to html file created by CRA's build tool
const filePath = path.resolve(__dirname, "..", "..", "build", "index.html");
fs.readFile(filePath, "utf8", (error, htmlData) => {
if (error) {
console.error("error", error);
return response.status(404).end();
}
// render the app as string
const html = renderToString(<App />);
// inject rendered app into final html and send
return res.send(
htmlData
.replace('<div id="root"></div>', `<div id="root">${html}</div>`)
);
})
}
You will need bootstrap.js to inject support for certain packages.
// -- bootstrap.js
require('ignore-styles');
require('url-loader');
require('file-loader');
require('babel-register')({
ignore: [/(node_modules)/],
presets: ['es2015', 'react-app'],
plugins: [
'syntax-dynamic-import',
'dynamic-import-node'
]
});
require("./index");
You can find the details of it all here:
https://blog.mytoori.com/react-served-by-express-running-in-docker-container

Resources