Overview slides of Node.js development. Covers also some syntax of EcmaScript 2015/2016/2017/2018/2019/2020 like await/async with promises. Example code how to build RESTful API connecting to MySQL, Sequelize ORM and MongoDB
I/O, Working with npm, Integrating tools with Visual Studio Code, Linting, Node.js modules, Async and Promises • Node.js and building Web Server • Simple web server, express and body-parser modules, RESTful API • Database Connection • Database modules, MySQL/Sequelize ORM/MongoDB 2
environment • https://nodejs.org/en/ • Executes JS without the browser using V8 • Let's you develop server-side scripting with JavaScript • "JavaScript everywhere" 4
installed by npm • V8: JavaScript engine by Google and implemented in C++. Same engine can be found from Chrome browser. • Compiles JS to machine code • libuv: C library that provides asynchronous features • Other c/c++ components: c-ares, crypto, http-parser, zlib.. (networking, compressing, encrypting) • Bindings: Glue that binds JavaScript code with C++ - code. Bindings exposes libraries written in C/C++ to JavaScript 5
focus on asynchronous I/O • https://github.com/libuv/libuv • Features • Asynchronous TCP and UDP sockets • Asynchronous DNS resolution • Asynchronous file and file system operations • File system events • Node.js provides bindings for the libuv so you can use these with JavaScript 6
one thread, called the main thread • One thread has a event loop that accepts incoming requests • Each new request causes a JavaScript function to fire which can do stuff in non-blocking way • *But Node.js has also a lot of C++ - code.. and C++ has access to threads • JS -> Synchronous C++ // All run in the main thread • JS -> Asynchronous C++ // Can spawn a new thread! 7
Node.js tend to be asynchronous • They do not block the main thread • function(parameters, callback) • When async module has done it's job, it calls the callback function • Control is returned to event loop • The loop decides which function was scheduled for execution 8
extension for easier running of node (and other) apps • Debugging Node apps is supported by default! • Install ESLint for linting • Install Rest Client for testing 16
be used to install various extensions and packages • You can install packages • local (specific to your app) • ./node_modules • global (can be used everywhere) • /usr/local/lib/node_modules • When creating a node project, usually you will have a file package.json describing the project and it's dependencies 17
won’t accept any breaking changes. • I will accept new features if they’re not breaking. • I will accept any fixes if they’re not breaking. • To express this • Package version can be ^1.3.5 24
won’t accept any breaking changes. • I don’t need new features • I will accept any fixes if they’re not breaking. • To express this • Package version can be ~1.3.5 • And to give exact version, just use 1.3.5 25
control the nested dependencies? • The package-lock.json is created for you and contains all the dependencies (dependency tree) • If package.json accept version changes in different time using npm install can result in different dependency versions (might be a problem) • The lock file locks all the versions. • When using npm install it will install exactly the same versions than in the lock file. • To update, use npm update 28
JavaScript • To install • npm install --save-dev eslint • ESLint requires a configuration file • To create one, use • ./node_modules/eslint/bin/eslint.js --init • You can answer to questions about style guide or take popular style guides 30
call that has another async call (that has a async call..) it can be tedious to implement • Promises (ES2015) can help this a bit • When Node.js was implement Promises were not part of the ECMAScript so there are different approaches to this • In Util.promisify you can promisify a any callback function with result and error • So wrapping the "old" APIs with the Util.promisify can help a bit 41
{ if (error) { console.error('Error while reading config file'); } else { try { const obj = JSON.parse(text); console.log(JSON.stringify(obj)); } catch (e) { console.error('Invalid JSON in file'); } } } ); 45 Async function for reading a file Notice two different approaches for error handling
const promiseReadFile = util.promisify(fs.readFile); promiseReadFile('mytest.json') .then(function (text) { // const obj = JSON.parse(text); console.log(JSON.stringify(obj)); }) .catch(function (error) { // // File read error or JSON SyntaxError console.error('An error occurred', error); }); 46 util.promisify is Node 8 feature Now we can do the same with promises. Notice that one catch for every exception or error
object with the given value return Promise.resolve(1); } f1().then((integer) => console.log(integer)); // 1 async function f2() { return 1 } f2().then((integer) => console.log(integer)) 49
object with the given value return Promise.resolve(1); } f1().then((integer) => console.log(integer)); // 1 async function f2() { return 1 } f2().then((integer) => console.log(integer)) 50 Will return a promise!
promiseFunction(resolve, reject) { setTimeout(() => resolve(text), secs) } return new Promise(promiseFunction) } async function doIt() { let text = await delay(1000, 'hello') console.log(text) } console.log('call async function') doIt() console.log('async function has been called') Waits until the result. Execution still continues in other parts of the app (async) 52 Output: call async function async function has been called hello
setTimeout(() => resolve(text), secs) } return new Promise(promiseFunction) } async function doIt() { let text1 = await delay(1000, 'hello') let text2 = await delay(1000, text1 + ' world') return text2 // Promise.resolve(text2) } doIt().then((result) => console.log(result)) Returns a promise! 53 Order here is guaranteed! Two delay promises are run in particular order. Both are run in async
setTimeout(() => resolve(text), secs) } return new Promise(promiseFunction) } delay(1000, 'hello’) .then((text1) => delay(1000, text1 + ' world’)) .then(text2 => console.log(text2)) Can be a bit hard to read.. 54
.then((characterJson) => fetch(characterJson.films[0])) .then((httpResp2) => httpResp2.json()) .then((filmJson) => console.log(filmJson)) 55 Can be a bit hard to read..
this.y = y; this.color = color; } } class Rectangle extends Shape { constructor (x, y, color, width, height) { super(x, y, color); this.width = width; this.height = height; } } class Circle extends Shape { constructor (x, y, color, radius) { super(x, y, color); this.radius = radius; } } let circle = new Circle(0,0,"red",5); console.log(circle); A lot nicer syntax for creating inheritance! 66
this.y = y; this.color = color; } } class Rectangle extends Shape { constructor (x, y, color, width, height) { super(x, y, color); this.width = width; this.height = height; } } class Circle extends Shape { constructor (x, y, color, radius) { super(x, y, color); this.radius = radius; } } Shape.prototype.hello = "world"; let circle = new Circle(0,0,"red",5); console.log(circle.hello); But it is syntactical sugar!! 67
CommonJS Modules (NodeJS) 3. ES2015 official module (React) • In EcmaScript 2015 for the first time it's built into language. • In node 8.5.0 you can use these as ”experimental”. In Node 10.0 LTS the flag should be removed • node --experimental-modules index.js • It's possible to compile ES6 Modules to AMD or CommonJS 69
specifying ecosystem for JS outside of Browser • Was started by Mozilla engineer, inital name ServerJS • CommonJS described a lot of specifications, including modules • This module specification is implemented in NodeJS • require to include modules • exports to make things available 74
const doIt2 = () => { console.log(this) } var _this = this const doIt2 = function () { console.log(_this) } doIt1() doIt2() console.log(this) 87 The arrow syntax uses this from “global” scope “global” scope here has different meaning for this than inside of a function..
(exports, require, module, __filename, __dirname) { // Your code }); var module = {exports:{}} context.apply(module.exports, [module.exports, require, module, "FILE_NAME", "DIR_NAME"]); 89 In Node, your whole code is inside of a wrapper function! Calling the wrapper function and defining the meaning of this
server (http.Server) and provide a callback function const server = http.createServer((request, response) => { response.end('Path: ' + request.url) }) // Start the server in port 8080 server.listen(8080, () => console.log(`Server listening in port ${server.address().port}`)) http.IncomingMessage http.ServerResponse 97
response - objects • Can • Execute code • Make changes to request and response objects • End the req/res cycle • Call the nest middleware function • Different types of middleware • App level • Router level • Error handling • ... 102
behaves like middleware, so you can use it with app.use() • To create router, use • let router = express.Router() • Then you can add http method routes to the object • router.get('/router1', (req, res) => { res.send('router 1') }) • And add it as a middleware • app.use('/root', router) 104
string into regular expression • https://www.npmjs.com/package/path-to-regexp • It will make easier to create regexp objects, especially if they have parameters • For example, a string containing '/[1-9]+' is transformed into a regex object /^\/[1-9]+$/ 111
= express.Router() users.get('/:myVariable([1-9]+)', (req, res) => { res.send(`Getting user ${req.params.myVariable}`) }) module.exports = users 113 Create a new variable containing the stuff in url Use req – object to access the variable
let users = express.Router() const database = new Set([new User(1, 'jack smith'), new User(2, 'tina jackson')]) users.get('/:urlId([1-9]+)', (req, res) => { let userObject = {} const urlId = Number(req.params.urlId) database.forEach((user) => { if (user.id === urlId) { userObject = user } }) res.send(JSON.stringify(userObject)) }) module.exports = users Creating "database" Importing the class If user with given id is found Sends a string response 116
text/html; charset=utf-8 Content-Length: 28 ETag: W/"1c-VJ4vRdnoG3woR9lnRtdA+3Qm+l4" Date: Thu, 16 Aug 2018 09:35:57 GMT Connection: keep-alive {"id":1,"name":"jack smith"} Well this is not right... 117
Express, http post body parser is prebuilt and to be used as middleware app.use(express.json()) • This will take the HTTP POST Body and transform it to json and can be read from request object users.post('/', (req, res) => { let user = req.body database.add(user) res.send(user) }) 120
just a matter of installing Node.js db driver • For each database you will have different driver • MySQL • MongoDB • PostgreSQL ... • Installing the driver, for example mysql • npm install mysql 122
= express.Router() const crudRepository = require('../database/crudrepository.js') users.get('/', (req, res) => { crudRepository.findAll((result) => res.send(result)) }) users.post('/', (req, res) => { let user = req.body crudRepository.save(user, (result) => res.send(result)) }) module.exports = users 127 The router here is unaware of the database. CrudRepository contains basic function for finding and saving
host: "localhost", dialect: "mariadb", define: { timestamps: false, }, }); 133 By default, Sequelize automatically adds the fields createdAt and updatedAt to your table. You can disable this feature.
Create database • use locationsdb • Insert into locationsdb a new collection with a document • db.locationscollection.insert({longitude: 60, latitude: 60}) • Select * • db.locationscollection.find() • Select the document with longitude = 60 • db.locationscollection.find({longitude: 60}) • To see which db is active • db 143