Web Development with Node.js

LV 02

Daniel Khan / daniel@khan.io / University of Applied Sciences Hagenberg / KWM

The Node Package Manager npm

Important Commands

# Important commands

# Update npm itself
$ npm install -g npm@latest

# Initialize a project by creating a package.json file
$ npm init

## Try: npm init

# Install a package. Use -g to install globally
# --save / --save-dev adds the package to package.json, -g installes globally
$ npm install [-g] [--save | --save-dev] <package name> 

## Try: npm install --save express
## Try: npm install --save-dev mocha

# Update a package
$ npm update <package name>

# Uninstall a package
# --save / --save-dev also removes it from package.json
$ npm uninstall [--save | --save-dev] <package name>

## Try: npm uninstall --save-dev mocha

# List all dependencies / installed packages
$ npm [-g] ls
## Try: npm ls and npm ls -g

# Scan for security problems
$ npm audit
## Try: npm audit

package.json

{
  "name": "myaweseomepackage",
  "version": "1.0.0",
  "description": "This is a package that is awesome.",
  "main": "index.js",
  "scripts": {
    "start": "node server.js",
    "test": "echo \"Error: no test specified\" && exit 1",
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.3",
  },
  "devDependencies": {
    "mocha": "^5.1.1",
  }
}

git: Push to version control!

Semantic versioning

// package.json

// Matches 4.16.2, 4.16.3, 4.16.4, ...
"dependencies": {
  "express": "~4.16.2", 
}

// Matches 4.16.x, 4.17.x, 4.18.x, ...
"dependencies": {
  "express": "^4.16.2", 
}

// Matches 4.x.x, 5.x.x, 6.x.x, ...
"dependencies": {
  "express": "*", 
}

package-lock.json

{
  "name": "myaweseomepackage",
  "version": "1.0.0",
  "lockfileVersion": 1,
  "requires": true,
  "dependencies": {
    "accepts": {
      "version": "1.3.5",
      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
      "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
      "requires": {
        "mime-types": "~2.1.18",
        "negotiator": "0.6.1"
      }
    },
    "array-flatten": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
    },
    "balanced-match": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
      "dev": true
    },
    
    // ...

}

git: Push to version control!

Express.js

Vanilla JSON Server

const http = require('http');
const url = require('url');
const portNumber = process.argv[2];

http.createServer(function (req, res) {
    var urlObject = url.parse(req.url, true), 
    pathname = urlObject.pathname, 
    startTime = urlObject.query.iso, result;

    if (pathname === '/api/unixtime') {
        result = getUnixTimeStamp(startTime);
    } else if (pathname === '/api/parsetime') {
        result = getTimeObj(startTime);
    }
    if (result) {
        res.writeHead(200, { 'Content-type': 'application/json' });
        res.end(JSON.stringify(result));
    } else {
        res.writeHead(404);
        res.end();
    }
}).listen(portNumber)

function getUnixTimeStamp(startTime) {
    return {
        unixtime: getTimeStamp(startTime)
    };
}

function getTimeStamp(startTime) {
    return Date.parse(startTime);
}

function getTimeObj(startTime) {
    var date = new Date(getTimeStamp(startTime));
    return {
        hour: date.getHours(),
        minute: date.getMinutes(),
        second: date.getSeconds()
    };
}
const express = require('express');
const app = express();
const portNumber = process.argv[2];

function getUnixTimeStamp(startTime) {
    return {
        unixtime: getTimeStamp(startTime)
    };
}

function getTimeStamp(startTime) {
    return Date.parse(startTime);
}

function getTimeObj(startTime) {
    var date = new Date(getTimeStamp(startTime));
    return {
        hour: date.getHours(),
        minute: date.getMinutes(),
        second: date.getSeconds()
    };
}

app.get('/api/unixtime', (req, res) => {
	const startTime = req.query.iso;
	const result = getUnixTimeStamp(startTime);
	return res.json(result);
});

app.get('/api/parsetime', (req, res) => {
	const startTime = req.query.iso;
	const result = getTimeObj(startTime);
	return res.json(result);
});

app.listen(portNumber, () => console.log(`Example app listening on port ${portNumber}!`));

Express Version

Express Basics

Routing

// respond with "hello world" when a GET request is made to the homepage
app.get('/', function (req, res) {
  res.send('hello world')
});

// POST method route
app.post('/', function (req, res) {
  res.send('POST request to the homepage')
});

// Called for every route
app.all('/secret', function (req, res, next) {
  console.log('Accessing the secret section ...')
  next(); // pass control to the next handler
});

// Will match abcd
app.get('/abcd', function (req, res) {
  res.send('abcd');
})

// This route path will match /abe and /abcde.
app.get('/ab(cd)?e', function (req, res) {
  res.send('abcd');
})

Route Handlers

Parameters

const express = require('express');
const app = express();


// Path that takes an user id
app.get('/user/:userId', function (req, res) {
  res.send(`You requested user ${req.params.userId}`)
});

app.get('/user/:userId/posts/:postId?', function (req, res) {

  if (req.params.postId) {
    res.send(`You requested post ${req.params.postId} for user ${req.params.userId}`)
  } else {
    res.send(`You requested all posts for user ${req.params.userId}`)
  }
});

Middleware

const myLogger = function (req, res, next) {
  console.log('LOGGED')
  next();
}

app.use(myLogger);

Express Router

const routes = require('./routes');
app.use('/', routes));

In app.js

const express = require('express');
const router = express.Router();

router.use('/frontend', require('./frontend'));
router.use('/admin', require('./admin'));
router.use('/user', require('./user'));
router.use('/protected', require('./protected'));

module.exports = router;

In ./routes.index.js

const express = require('express');
const router = express.Router();

// Matches for GET /frontend
router.get('/', (req, res) => {
  res.send('Hello from frontend!');
});

module.exports = router;

In ./routes/frontend/index.js

Application & Request Lifecycle

const express = require('express');
const app = express();
const portNumber = process.argv[2];

app.use((req, res, next) => {
  if (!app.locals.count) {
    app.locals.count = 1;
  } else {
    app.locals.count++;
  }
  return next();
});

app.use((req, res, next) => {
  req.requestTime = Date.now();
  return next(); // Important!
});

app.use((req, res, next) => {
  console.log(`Request #${app.locals.count} for ${req.url} at ${req.requestTime}`);
  return next(); // Important!
});

// Stop

app.get('/',
  (req, res, next) => {
    app.locals.count % 2 == 0 ? 
        next() : 
        res.status(400).json({ 'result': 'You shall not pass' });
  },
  (req, res) => {
    res.json({ 'result': 'Welcome friend!' });
  }
);

// Stop

app.get('/podbay', (req, res, next) => {
  return next('Sorry Dave, I can\'t not do that');
});

// Stop

app.get('/podbay', (req, res, next) => {
  return next('Sorry Dave, I can\'t not do that');
  //  return next(new Error('Sorry Dave, I can\'t not do that'));
});

// Error handler
app.use( (err, req, res, next) => {
  console.error(err);
  res.status(500).send('Something broke!')
})

app.listen(portNumber, () => 
    console.log(`Example app listening on port ${portNumber}!`));

Ending a Request

Let's create a first sample application

// Install express generator globally
$ npm install express-generator -g

// Create the express application in a folder 'express-sample'
$ express --view=ejs express-sample

// Install all dependencies
$ npm install
$ npm install --save-dev eslint
$ ./node_modules/eslint/bin/eslint --init 

# airbnb
# no React
# JavaScript

# Install Visual Studio Code eslint module

# Fix all errors

Enable Linting

$ npm install --save-dev mocha chai chai-http

# Create directory 'tests'

Set-Up Tests

"scripts": {
  "start": "node ./bin/www",
  "test": "eslint . --ext .js && mocha --recursive tests/"
},

package.json

const chai = require('chai');
const chaiHttp = require('chai-http');

chai.use(chaiHttp);
chai.should(); // Function!!!

const { expect } = chai; // Reference!!

const server = require('../../app');

describe('GET /', () => {
  it('should GET all the books', (done) => {
    chai.request(server)
      .get('/')
      .end((err, res) => {
        res.should.have.status(200);
        expect(res.text).to.be.a('string');
        expect(res.text).to.contain('Express');
        done();
      });
  });
});

tests/routes/index.js

Set-Up MongoDb

# Install Docker

$ docker run --name mongo-testserver -d -p 28018:27017 mongo

# MongoDb is now available on localhost port 28018

# Install Mongo 3T

$ npm install --save mongoose
//Import the mongoose module
var mongoose = require('mongoose');

//Set up default mongoose connection
var mongoDB = 'mongodb://127.0.0.1/my_database';
mongoose.connect(mongoDB);
// Get Mongoose to use the global promise library
mongoose.Promise = global.Promise;
//Get the default connection
var db = mongoose.connection;

//Bind connection to error event (to get notification of connection errors)
db.on('error', console.error.bind(console, 'MongoDB connection error:'));

KWM18 LV02

By Daniel Khan

KWM18 LV02

  • 839