ExpressJS

Introduction

  • It is a framework that allows user to focus on developing business logic instead of typing boilerplate code for defining route and handling request and response

  • It is all about middleware that the incoming request will go through all the funnels and then send the response back to client side

  • The default query string parser is using qs library

const express = require("express");
const path = require("path");
const app = express();

// define middleware to parse body of request

// usally used for rest api, as the content-type is mainly application/json
// for parsing the request body as json
app.use(express.json());

// used to parse form data, as the content type is mainly 
// application/x-www-form-urlencoded
app.use(express.urlencoded({extended: false});

// override the query parser setting, the default value is "extended"
// however, the default depth of qs is 5, so needed to be increased when needed
app.set('query parser', (queryString) => {
    const qs = require('qs');
    return qs.parse(queryString, {
      depth: 100, // Increase depth limit from default 5
      arrayLimit: 100, // Optional: increase array items limit
      parameterLimit: 2000, // Optional: increase parameter count limit
    });
  });

// server static file, such as image
app.use(express.static(path.join("__dirname","public")));
// defining middleware globally
app.use((req,res,next)=>{
  console.log("middleware 1");
  // go to next middleware
  next();
});

app.use((req,res,next)=>{
  console.log("middleware 2");
  next();
});

// only applicable to "/halo" path
app.use("/halo",(req,res,next)=>{
  console.log("halo");
});

// only applicable to "/halo" path
app.use("/halo/:id",(req,res,next)=>{
  console.log("halo",req.params.id);
});

app.use("/halo",(req,res,next)=>{
  console.log("halo");
});

// only applicable for post method and path "/post"
app.post("/post", (req,res,next)=>{
  console.log(req.body);
});

// The code behind is to create http server, ...
app.listen(8080, function () { 
  console.log("Server Start "); 
});

Separating Routes

  • Express Router allows to divide paths into multiple files

// app.js

// Filtering the path to go into middleware
app.use("/auth", require("./routes/auth"));
app.use("/user", require("./routes/user"));

app.use((req,res)=>{
    res.send("Page not found");
});
// routes/user.js
const express = require("express");
const router = express.Router();

// only get methods and exact match to /user/test is applicable to this middleware
router.get("/test", function (req, res, next) {
  res.send("test");
});

module.exports = router;

Error Handling

// Define error middleware
app.use((error, req , res , next) =>{
        console.log(error.StatusCode);
        // ...
})

app.get("/test",(req,res,next)=>{
    try{
        // ...
    } catch (err){
        // automatically go to error middleware
        const error = new Error(err);
        error.StatusCode = 500;
        return next(error);
    }
})

Template Engine

  • The template engine is used to understand the certain syntax, and scan the html-ish template and then replace the placeholder from server

  • Finally, it will generate the html file


// Define which template template is using
app.set('view engine','ejs');
// Define the directory of template file, default: view
app.set('views','views');


// Automatically set the data to the view for every request
app.use(function(req,res,next){
     res.locals.success_msg = "...";
     res.locals.user_msg = "...";
     next();
});

app.get("/test", (req,res,next)=>{
   // output as test.html from test.ejs in view file
   res.render('test', {
        layout: null,
        username_msg: forgotusername
   }
});
<body>
  <center>
    <h1 style="font-size:40px">Hi!! <%=username_msg%></h1>
    <h2 style="font-size:30px">Reset Password</h2>
    Here is the link to reset your password:
    <a href="http://127.0.0.1:5000/users/resetpw?username=<%=username_msg%>&email=<%=email_msg%>">Reset</a>
  </center>
</body>
const session = require('express-session');
const MongoDBStore = require('connect-mongodb-session')(session);
const app = express();
const MONGODB_URI =
  'mongodb+srv://maximilian:9u4biljMQc4jjqbe@cluster0-ntrwp.mongodb.net/shop';
  
// Define the collection that store sessions  
const store = new MongoDBStore({
  uri: MONGODB_URI,
  collection: 'sessions'
});

// Define the session middleware
// Automatically parse the related cookie and find the related session information
// Automatically generate cookie, if the related cookie doesn't existed
app.use(
  session({
   // used to encrypt the session id, 
   // to make sure that only server can decrypt the id
    secret: 'my secret',
    // Prevent update session on each request
    resave: false,
    saveUninitialized: false,
    // Define the source of session storage, default is to using memory
    store: store,
    // The related cookies setting
    cookies:{
     // ...
    }
  })
);

// Login
// Set the session information and save into document
req.session.isLoggedIn = true;
req.session.user = user;
req.session.save(err => {
  console.log(err);
  res.redirect('/');
});

// Logout
// Delete the session from the storage
// Even the cookie contains session id, the related session will not be found
req.session.destroy(err => {
    console.log(err);
    res.redirect('/');
 });

Flash message

  • In order to provide user feedback while redirecting the response, flash message will be used

  • The message will be stored into session temporarily, when message has been used, the message will be pop up from the session

const flash = require('connect-flash');

app.get('/logout', function (req, res) {
  // add the message into storage
  req.flash('success_msg', 'You are successfully logged out');
  res.redirect('/users/login');
});

app.get("/login", (req,res) =>{ 
  //  get back the message from session and pop out
  let message = req.flash('error');
  if (message.length > 0) {
    message = message[0];
  } else {
    message = null;
  }
  res.render('auth/login', {
    path: '/login',
    pageTitle: 'Login',
    errorMessage: message
  });
})

CSRF Token

const csrf = require('csurf');
const csrfProtection = csrf();

// check the non-get route to see whether the csrf token is existed on request or not
app.use(csrfProtection);

app.use((req, res, next) => {
 // pass csrf token into view
  res.locals.csrfToken = req.csrfToken();
  next();
});
<form class="login-form" action="/login" method="POST">
    <input type="hidden" name="_csrf" value="<%= csrfToken %>">
</form>

File Handling

Upload

const multer = require('multer');

// To parse the request which type is multpart/form-data and contain file binary data
// define the storage location and stream the binary data to destination automatically
// decide which file type that will be processed
// set to req.file automatically
const fileStorage = multer.diskStorage({
  destination: (req, file, cb) => {
    cb(null, 'images');
  },
  filename: (req, file, cb) => {
    cb(null, new Date().toISOString() + '-' + file.originalname);
  }
});

// pass to the multer middleware to save the image first
app.use(
  multer({ storage: fileStorage, fileFilter: fileFilter }).single('image')
);

// serve the static file 
app.use('/images', express.static(path.join(__dirname, 'images')));

app.post("/image", (req,res,next) => {
  const title = req.body.title;
  // handle the file data which is returned from multer middleware
  const image = req.file;
  const imageUrl = image.path;
  // ...
});

Download

app.get("/download", (req,res,next) =>{
     const invoiceName = 'invoice-' + orderId + '.pdf';
     const invoicePath = path.join('data', 'invoices', invoiceName);
     // create stream to get file chunk data one by one
     const file = fs.createReadStream(invoicePath);
     
     res.setHeader('Content-Type', 'application/pdf');
     // to notify the browser that it is used to download file with specific name
     // to show save as dialog
      res.setHeader(
        'Content-Disposition',
        'attachment; filename="' + invoiceName + '"'
      );
     // res itself is a writeable stream, and forward the chunks of data 
     // from read stream, finally the all the buffers will be concat 
     // and form the final file on the browser
     file.pipe(res);
})

Deployment

Pre-action

  • Get the ubuntu server from Vultr / DigitalOcean / AWS

  • Change Password :sudo passwd

  • Install nodejs and update its version:

curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs
  • Install mysql :

sudo apt-get update
sudo apt-get install mysql-server
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'your-password';
  • Install forever / pm2

npm install -g forever

Https

  • Register the domain in goDaddy or other

  • Link to your ip address

  • Add Certbot PPA

sudo apt-get update &&
sudo apt-get install software-properties-common &&
sudo add-apt-repository universe &&
sudo add-apt-repository ppa:certbot/certbot &&
sudo apt-get update
  • Install Certbot

sudo apt-get install certbot
  • Register to obtain the key, cert and ca

sudo certbot certonly --standalone
  • Read the key ,cert and ca into server.js in production and be https

const https = require("https");
const fs = require("fs");
const IS_PROD = process.env.NODE_ENV === "production";
const http = require("http");
if (IS_PROD) {
  server = https.createServer(
    {
      key: fs.readFileSync(
        "/etc/letsencrypt/live/friendchats.xyz/privkey.pem",
        "utf8"
      ),
      cert: fs.readFileSync(
        "/etc/letsencrypt/live/friendchats.xyz/cert.pem",
        "utf8"
      ),
      ca: fs.readFileSync(
        "/etc/letsencrypt/live/friendchats.xyz/chain.pem",
        "utf8"
      ),
    },
    app
  );
} else {
  server = http.createServer(app);
}

server.listen(process.env.PORT, function () {
  console.log("Server Start ");
});

Forever

  • Install forever to execute the nodejs express program

npm install forever -g 
  • Execute npm command by using forever

forever start -c "npm run start:prod" ./
  • Find the uid by using:

forever list
  • Stop the program by using uid

forever stop {uid}
  • Find the uid who is using the port (e.g 8080)

lsof -i :8080
  • Kill the process by using uid

kill -9 uid

Last updated

Was this helpful?