├── .gitignore ├── .vscode └── settings.json ├── 9781430265955.jpg ├── 9781484230381.jpg ├── chapter1 ├── chapter1.md └── media │ ├── Homebrew Install On Request Events #1- node with 736,243 events (4.31% of total) #2- git with 354,671 events (2.08% of total).png │ ├── image1.png │ ├── image10.png │ ├── image11.png │ ├── image12.png │ ├── image13.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ ├── image5.png │ ├── image6.png │ ├── image7.png │ ├── image8.png │ └── image9.png ├── chapter10 ├── chapter10.md └── media │ ├── image1.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ ├── image5.png │ └── image6.png ├── chapter11 ├── chapter11.md └── media │ ├── image1.png │ ├── image2.png │ ├── image3.png │ └── image4.png ├── chapter12 ├── chapter12.md └── media │ └── modulecounts.png ├── chapter13 ├── chapter13.md └── media │ ├── Click-on-ADVANCED.png │ ├── Click-on-Proceed.png │ ├── localhost-cert.png │ ├── localhost-request.png │ ├── push-alert.png │ ├── push-inspect.png │ └── yahoo.png ├── chapter14 └── chapter14.md ├── chapter15 ├── chapter15.md └── media │ ├── WhatIsDocker_2_VMs_0-2_2.png │ ├── WhatIsDocker_3_Containers_2_0.png │ ├── aws-ec2.png │ ├── aws-ecs-1.png │ ├── aws-ecs-10-2.png │ ├── aws-ecs-10.png │ ├── aws-ecs-11.png │ ├── aws-ecs-12.png │ ├── aws-ecs-13.png │ ├── aws-ecs-14.png │ ├── aws-ecs-15.png │ ├── aws-ecs-16.png │ ├── aws-ecs-2.png │ ├── aws-ecs-3.png │ ├── aws-ecs-4.png │ ├── aws-ecs-5.png │ ├── aws-ecs-6.png │ ├── aws-ecs-7.png │ ├── aws-ecs-8.png │ ├── aws-ecs-9.png │ ├── aws-ecs-console-1.png │ ├── aws-ecs-console-2.png │ ├── aws-ecs-console-3.png │ ├── aws-ecs-console-4.png │ ├── docker-ee.png │ ├── docker-engine-mac.png │ ├── docker-images.png │ ├── docker-running.png │ ├── hello-world.png │ └── node-npm.png ├── chapter16 ├── chapter16.md └── media │ ├── serverless-1.png │ ├── serverless-db.png │ ├── serverless-postman.png │ ├── serverless-web-console-1.gif │ └── serverless-web-console-2.gif ├── chapter2 ├── chapter2.md └── media │ ├── image10.png │ ├── image11.png │ ├── image12.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ ├── image5.png │ ├── image6.png │ ├── image7.png │ ├── image8.png │ └── image9.png ├── chapter3 ├── chapter3.md └── media │ ├── image1.png │ ├── image2.png │ ├── image3.png │ └── image4.png ├── chapter4 ├── chapter4.md └── media │ ├── Thumbs.db │ ├── image1.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ ├── image5.png │ ├── image6.png │ ├── image7.png │ └── image8.png ├── chapter5 ├── chapter5.md └── media │ ├── Thumbs.db │ ├── image1.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ ├── image5.png │ └── image6.png ├── chapter6 ├── chapter6.md └── media │ ├── image1.png │ ├── jwt-1.png │ ├── jwt-2.png │ ├── jwt-3.png │ ├── jwt-4.png │ ├── jwt-5.png │ └── jwt-6.png ├── chapter7 ├── chapter7.md └── media │ └── image1.png ├── chapter8 ├── chapter8.md └── media │ ├── Thumbs.db │ ├── image1.png │ ├── image2.png │ └── image3.png ├── chapter9 ├── chapter9.md └── media │ ├── Thumbs.db │ ├── image1.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ └── image5.png ├── code ├── ch1 │ ├── buf.js │ ├── class.js │ ├── hello-debug.js │ └── hello.js ├── ch10 │ ├── domains │ │ ├── app.js │ │ ├── package.json │ │ ├── public │ │ │ └── stylesheets │ │ │ │ └── style.css │ │ ├── routes │ │ │ └── index.js │ │ └── views │ │ │ ├── index.jade │ │ │ └── layout.jade │ ├── examples │ │ ├── cluster.js │ │ ├── envvar.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── repl.js │ └── grunt-example │ │ ├── Gruntfile.js │ │ ├── build │ │ ├── grunt-example.js │ │ └── grunt-example.min.js │ │ ├── package.json │ │ └── source │ │ ├── admin.js │ │ ├── factorial.coffee │ │ ├── grunt-example.js │ │ └── index.coffee ├── ch13 │ ├── http2-express │ │ ├── app.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── server.crt │ │ └── server.key │ ├── http2-push │ │ ├── server.crt │ │ ├── server.js │ │ └── server.key │ └── http2 │ │ ├── server-simple.js │ │ ├── server.crt │ │ ├── server.js │ │ └── server.key ├── ch14 │ ├── async-await │ │ ├── app-koa.js │ │ ├── async-await-error.js │ │ ├── async-await.js │ │ └── package.json │ ├── async-example │ │ ├── customer_data.json │ │ ├── package.json │ │ ├── parallel-async.js │ │ └── parallel.js │ ├── generators │ │ ├── package.json │ │ ├── readJSON.js │ │ └── server.js │ └── promise │ │ ├── axios.js │ │ ├── basic-promise-2.js │ │ ├── basic-promise-3.js │ │ ├── basic-promise.js │ │ ├── package.json │ │ ├── readJSON.js │ │ └── try-catch.js ├── ch15 │ ├── banking-api │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── api │ │ │ ├── package.json │ │ │ └── server.js │ │ └── test.sh │ └── docker-node-hello │ │ ├── Dockerfile │ │ └── server.js ├── ch16 │ └── serverless │ │ ├── create-api.sh │ │ ├── db-api-test-post.json │ │ ├── db-api-test.json │ │ ├── index.js │ │ ├── lambda-trust-policy.json │ │ ├── my-first-fn.yaml │ │ └── output.txt ├── ch2 │ ├── express-styl │ │ ├── app.js │ │ ├── bin │ │ │ └── www │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ └── stylesheets │ │ │ │ ├── style.css │ │ │ │ └── style.styl │ │ ├── routes │ │ │ ├── index.js │ │ │ └── users.js │ │ └── views │ │ │ ├── error.jade │ │ │ ├── index.jade │ │ │ └── layout.jade │ ├── hello-advanced │ │ ├── app.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── views │ │ │ └── index.pug │ └── hello-simple │ │ ├── package-lock.json │ │ ├── package.json │ │ └── server.js ├── ch3 │ ├── blog-express │ │ ├── Makefile │ │ ├── app.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── tests │ │ │ └── index.js │ │ └── views │ │ │ └── index.pug │ └── test-example │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── test-assert-v2.js │ │ ├── test-assert.js │ │ └── test-expect.js ├── ch4 │ ├── consolidate │ │ ├── app.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── templates │ │ │ ├── index.html │ │ │ └── platforms.html │ ├── handlebars-example │ │ ├── handlebars-example.html │ │ ├── handlebars-example.js │ │ ├── package-lock.json │ │ └── package.json │ └── pug-example │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── pug-example.js │ │ ├── pug-example.pug │ │ └── pug-method-example.js ├── ch5 │ ├── blog-express │ │ ├── Makefile │ │ ├── app.js │ │ ├── db │ │ │ ├── articles.json │ │ │ └── users.json │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── css │ │ │ │ ├── bootstrap-3.0.2 │ │ │ │ │ ├── css │ │ │ │ │ │ ├── bootstrap-theme.css │ │ │ │ │ │ ├── bootstrap-theme.min.css │ │ │ │ │ │ ├── bootstrap.css │ │ │ │ │ │ └── bootstrap.min.css │ │ │ │ │ ├── fonts │ │ │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ │ │ └── glyphicons-halflings-regular.woff │ │ │ │ │ └── js │ │ │ │ │ │ ├── bootstrap.js │ │ │ │ │ │ └── bootstrap.min.js │ │ │ │ ├── style.css │ │ │ │ └── style.styl │ │ │ └── js │ │ │ │ ├── admin.js │ │ │ │ ├── blog.js │ │ │ │ ├── jquery-2.0.3.min.js │ │ │ │ └── jquery-2.0.3.min.map │ │ ├── readme.md │ │ ├── routes │ │ │ ├── article.js │ │ │ ├── index.js │ │ │ └── user.js │ │ ├── seed.sh │ │ ├── tests │ │ │ └── index.js │ │ └── views │ │ │ ├── admin.pug │ │ │ ├── article.pug │ │ │ ├── includes │ │ │ └── menu.pug │ │ │ ├── index.pug │ │ │ ├── layout.pug │ │ │ ├── login.pug │ │ │ └── post.pug │ └── mongo-examples │ │ ├── mongo-native-insert.js │ │ ├── mongo-native.js │ │ ├── mongoskin.js │ │ ├── package-lock.json │ │ └── package.json ├── ch6 │ ├── blog-everyauth │ │ ├── Makefile │ │ ├── app.js │ │ ├── db │ │ │ ├── articles.json │ │ │ └── users.json │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── css │ │ │ │ ├── bootstrap-3.0.2 │ │ │ │ │ ├── css │ │ │ │ │ │ ├── bootstrap-theme.css │ │ │ │ │ │ ├── bootstrap-theme.min.css │ │ │ │ │ │ ├── bootstrap.css │ │ │ │ │ │ └── bootstrap.min.css │ │ │ │ │ ├── fonts │ │ │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ │ │ └── glyphicons-halflings-regular.woff │ │ │ │ │ └── js │ │ │ │ │ │ ├── bootstrap.js │ │ │ │ │ │ └── bootstrap.min.js │ │ │ │ ├── style.css │ │ │ │ └── style.styl │ │ │ └── js │ │ │ │ ├── admin.js │ │ │ │ ├── blog.js │ │ │ │ └── jquery-2.0.3.min.js │ │ ├── readme.md │ │ ├── routes │ │ │ ├── article.js │ │ │ ├── index.js │ │ │ └── user.js │ │ ├── seed.sh │ │ ├── tests │ │ │ └── index.js │ │ └── views │ │ │ ├── admin.pug │ │ │ ├── article.pug │ │ │ ├── includes │ │ │ └── menu.pug │ │ │ ├── index.pug │ │ │ ├── layout.pug │ │ │ ├── login.pug │ │ │ └── post.pug │ ├── blog-password │ │ ├── Makefile │ │ ├── app.js │ │ ├── db │ │ │ ├── articles.json │ │ │ └── users.json │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── css │ │ │ │ ├── bootstrap-3.0.2 │ │ │ │ │ ├── css │ │ │ │ │ │ ├── bootstrap-theme.css │ │ │ │ │ │ ├── bootstrap-theme.min.css │ │ │ │ │ │ ├── bootstrap.css │ │ │ │ │ │ └── bootstrap.min.css │ │ │ │ │ ├── fonts │ │ │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ │ │ └── glyphicons-halflings-regular.woff │ │ │ │ │ └── js │ │ │ │ │ │ ├── bootstrap.js │ │ │ │ │ │ └── bootstrap.min.js │ │ │ │ ├── style.css │ │ │ │ └── style.styl │ │ │ └── js │ │ │ │ ├── admin.js │ │ │ │ ├── blog.js │ │ │ │ └── jquery-2.0.3.min.js │ │ ├── readme.md │ │ ├── routes │ │ │ ├── article.js │ │ │ ├── index.js │ │ │ └── user.js │ │ ├── seed.sh │ │ ├── tests │ │ │ └── index.js │ │ └── views │ │ │ ├── admin.pug │ │ │ ├── article.pug │ │ │ ├── includes │ │ │ └── menu.pug │ │ │ ├── index.pug │ │ │ ├── layout.pug │ │ │ ├── login.pug │ │ │ └── post.pug │ ├── jwt-example │ │ ├── package.json │ │ └── server.js │ └── oauth-example │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── start.sh │ │ └── twitter-oauth.js ├── ch7 │ ├── blog-mongoose │ │ ├── Makefile │ │ ├── app.js │ │ ├── db │ │ │ ├── articles.json │ │ │ └── users.json │ │ ├── models │ │ │ ├── article.js │ │ │ ├── index.js │ │ │ └── user.js │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ ├── css │ │ │ │ ├── bootstrap-3.0.2 │ │ │ │ │ ├── css │ │ │ │ │ │ ├── bootstrap-theme.css │ │ │ │ │ │ ├── bootstrap-theme.min.css │ │ │ │ │ │ ├── bootstrap.css │ │ │ │ │ │ └── bootstrap.min.css │ │ │ │ │ ├── fonts │ │ │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ │ │ └── glyphicons-halflings-regular.woff │ │ │ │ │ └── js │ │ │ │ │ │ ├── bootstrap.js │ │ │ │ │ │ └── bootstrap.min.js │ │ │ │ ├── style.css │ │ │ │ └── style.styl │ │ │ └── js │ │ │ │ ├── admin.js │ │ │ │ ├── blog.js │ │ │ │ └── jquery-2.0.3.min.js │ │ ├── readme.md │ │ ├── routes │ │ │ ├── article.js │ │ │ ├── index.js │ │ │ └── user.js │ │ ├── seed.sh │ │ ├── tests │ │ │ ├── index.js │ │ │ └── rest-api.js │ │ └── views │ │ │ ├── admin.pug │ │ │ ├── article.pug │ │ │ ├── includes │ │ │ └── menu.pug │ │ │ ├── index.pug │ │ │ ├── layout.pug │ │ │ ├── login.pug │ │ │ └── post.pug │ ├── gravatar.js │ └── mongoose-example │ │ ├── mongoose.js │ │ ├── package-lock.json │ │ └── package.json ├── ch8 │ ├── rest-express │ │ ├── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── test │ │ │ └── index.js │ └── rest-hapi │ │ ├── index.js │ │ ├── package-lock.json │ │ ├── package.json │ │ └── test │ │ └── index.js └── ch9 │ ├── socket-express │ ├── app.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── stylesheets │ │ │ └── style.css │ ├── routes │ │ └── index.js │ └── views │ │ ├── error.pug │ │ ├── index.pug │ │ └── layout.pug │ └── ws-basic │ ├── index.html │ ├── package-lock.json │ ├── package.json │ └── server.js ├── front-matter.md ├── outro └── outro.md └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.zip -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "automagically", 4 | "falsy", 5 | "mongoskin", 6 | "truthy", 7 | "truthyness" 8 | ], 9 | "cSpell.language": "en,en-US" 10 | } -------------------------------------------------------------------------------- /9781430265955.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/9781430265955.jpg -------------------------------------------------------------------------------- /9781484230381.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/9781484230381.jpg -------------------------------------------------------------------------------- /chapter1/media/Homebrew Install On Request Events #1- node with 736,243 events (4.31% of total) #2- git with 354,671 events (2.08% of total).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/Homebrew Install On Request Events #1- node with 736,243 events (4.31% of total) #2- git with 354,671 events (2.08% of total).png -------------------------------------------------------------------------------- /chapter1/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image1.png -------------------------------------------------------------------------------- /chapter1/media/image10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image10.png -------------------------------------------------------------------------------- /chapter1/media/image11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image11.png -------------------------------------------------------------------------------- /chapter1/media/image12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image12.png -------------------------------------------------------------------------------- /chapter1/media/image13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image13.png -------------------------------------------------------------------------------- /chapter1/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image2.png -------------------------------------------------------------------------------- /chapter1/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image3.png -------------------------------------------------------------------------------- /chapter1/media/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image4.png -------------------------------------------------------------------------------- /chapter1/media/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image5.png -------------------------------------------------------------------------------- /chapter1/media/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image6.png -------------------------------------------------------------------------------- /chapter1/media/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image7.png -------------------------------------------------------------------------------- /chapter1/media/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image8.png -------------------------------------------------------------------------------- /chapter1/media/image9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter1/media/image9.png -------------------------------------------------------------------------------- /chapter10/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter10/media/image1.png -------------------------------------------------------------------------------- /chapter10/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter10/media/image2.png -------------------------------------------------------------------------------- /chapter10/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter10/media/image3.png -------------------------------------------------------------------------------- /chapter10/media/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter10/media/image4.png -------------------------------------------------------------------------------- /chapter10/media/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter10/media/image5.png -------------------------------------------------------------------------------- /chapter10/media/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter10/media/image6.png -------------------------------------------------------------------------------- /chapter11/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter11/media/image1.png -------------------------------------------------------------------------------- /chapter11/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter11/media/image2.png -------------------------------------------------------------------------------- /chapter11/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter11/media/image3.png -------------------------------------------------------------------------------- /chapter11/media/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter11/media/image4.png -------------------------------------------------------------------------------- /chapter12/media/modulecounts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter12/media/modulecounts.png -------------------------------------------------------------------------------- /chapter13/media/Click-on-ADVANCED.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter13/media/Click-on-ADVANCED.png -------------------------------------------------------------------------------- /chapter13/media/Click-on-Proceed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter13/media/Click-on-Proceed.png -------------------------------------------------------------------------------- /chapter13/media/localhost-cert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter13/media/localhost-cert.png -------------------------------------------------------------------------------- /chapter13/media/localhost-request.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter13/media/localhost-request.png -------------------------------------------------------------------------------- /chapter13/media/push-alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter13/media/push-alert.png -------------------------------------------------------------------------------- /chapter13/media/push-inspect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter13/media/push-inspect.png -------------------------------------------------------------------------------- /chapter13/media/yahoo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter13/media/yahoo.png -------------------------------------------------------------------------------- /chapter15/media/WhatIsDocker_2_VMs_0-2_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/WhatIsDocker_2_VMs_0-2_2.png -------------------------------------------------------------------------------- /chapter15/media/WhatIsDocker_3_Containers_2_0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/WhatIsDocker_3_Containers_2_0.png -------------------------------------------------------------------------------- /chapter15/media/aws-ec2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ec2.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-1.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-10-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-10-2.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-10.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-11.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-12.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-13.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-14.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-15.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-16.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-2.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-3.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-4.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-5.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-6.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-7.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-8.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-9.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-console-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-console-1.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-console-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-console-2.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-console-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-console-3.png -------------------------------------------------------------------------------- /chapter15/media/aws-ecs-console-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/aws-ecs-console-4.png -------------------------------------------------------------------------------- /chapter15/media/docker-ee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/docker-ee.png -------------------------------------------------------------------------------- /chapter15/media/docker-engine-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/docker-engine-mac.png -------------------------------------------------------------------------------- /chapter15/media/docker-images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/docker-images.png -------------------------------------------------------------------------------- /chapter15/media/docker-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/docker-running.png -------------------------------------------------------------------------------- /chapter15/media/hello-world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/hello-world.png -------------------------------------------------------------------------------- /chapter15/media/node-npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter15/media/node-npm.png -------------------------------------------------------------------------------- /chapter16/media/serverless-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter16/media/serverless-1.png -------------------------------------------------------------------------------- /chapter16/media/serverless-db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter16/media/serverless-db.png -------------------------------------------------------------------------------- /chapter16/media/serverless-postman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter16/media/serverless-postman.png -------------------------------------------------------------------------------- /chapter16/media/serverless-web-console-1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter16/media/serverless-web-console-1.gif -------------------------------------------------------------------------------- /chapter16/media/serverless-web-console-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter16/media/serverless-web-console-2.gif -------------------------------------------------------------------------------- /chapter2/media/image10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image10.png -------------------------------------------------------------------------------- /chapter2/media/image11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image11.png -------------------------------------------------------------------------------- /chapter2/media/image12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image12.png -------------------------------------------------------------------------------- /chapter2/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image2.png -------------------------------------------------------------------------------- /chapter2/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image3.png -------------------------------------------------------------------------------- /chapter2/media/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image4.png -------------------------------------------------------------------------------- /chapter2/media/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image5.png -------------------------------------------------------------------------------- /chapter2/media/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image6.png -------------------------------------------------------------------------------- /chapter2/media/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image7.png -------------------------------------------------------------------------------- /chapter2/media/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image8.png -------------------------------------------------------------------------------- /chapter2/media/image9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter2/media/image9.png -------------------------------------------------------------------------------- /chapter3/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter3/media/image1.png -------------------------------------------------------------------------------- /chapter3/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter3/media/image2.png -------------------------------------------------------------------------------- /chapter3/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter3/media/image3.png -------------------------------------------------------------------------------- /chapter3/media/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter3/media/image4.png -------------------------------------------------------------------------------- /chapter4/media/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/Thumbs.db -------------------------------------------------------------------------------- /chapter4/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/image1.png -------------------------------------------------------------------------------- /chapter4/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/image2.png -------------------------------------------------------------------------------- /chapter4/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/image3.png -------------------------------------------------------------------------------- /chapter4/media/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/image4.png -------------------------------------------------------------------------------- /chapter4/media/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/image5.png -------------------------------------------------------------------------------- /chapter4/media/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/image6.png -------------------------------------------------------------------------------- /chapter4/media/image7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/image7.png -------------------------------------------------------------------------------- /chapter4/media/image8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter4/media/image8.png -------------------------------------------------------------------------------- /chapter5/media/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter5/media/Thumbs.db -------------------------------------------------------------------------------- /chapter5/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter5/media/image1.png -------------------------------------------------------------------------------- /chapter5/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter5/media/image2.png -------------------------------------------------------------------------------- /chapter5/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter5/media/image3.png -------------------------------------------------------------------------------- /chapter5/media/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter5/media/image4.png -------------------------------------------------------------------------------- /chapter5/media/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter5/media/image5.png -------------------------------------------------------------------------------- /chapter5/media/image6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter5/media/image6.png -------------------------------------------------------------------------------- /chapter6/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter6/media/image1.png -------------------------------------------------------------------------------- /chapter6/media/jwt-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter6/media/jwt-1.png -------------------------------------------------------------------------------- /chapter6/media/jwt-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter6/media/jwt-2.png -------------------------------------------------------------------------------- /chapter6/media/jwt-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter6/media/jwt-3.png -------------------------------------------------------------------------------- /chapter6/media/jwt-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter6/media/jwt-4.png -------------------------------------------------------------------------------- /chapter6/media/jwt-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter6/media/jwt-5.png -------------------------------------------------------------------------------- /chapter6/media/jwt-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter6/media/jwt-6.png -------------------------------------------------------------------------------- /chapter7/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter7/media/image1.png -------------------------------------------------------------------------------- /chapter8/media/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter8/media/Thumbs.db -------------------------------------------------------------------------------- /chapter8/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter8/media/image1.png -------------------------------------------------------------------------------- /chapter8/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter8/media/image2.png -------------------------------------------------------------------------------- /chapter8/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter8/media/image3.png -------------------------------------------------------------------------------- /chapter9/media/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter9/media/Thumbs.db -------------------------------------------------------------------------------- /chapter9/media/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter9/media/image1.png -------------------------------------------------------------------------------- /chapter9/media/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter9/media/image2.png -------------------------------------------------------------------------------- /chapter9/media/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter9/media/image3.png -------------------------------------------------------------------------------- /chapter9/media/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter9/media/image4.png -------------------------------------------------------------------------------- /chapter9/media/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/chapter9/media/image5.png -------------------------------------------------------------------------------- /code/ch1/buf.js: -------------------------------------------------------------------------------- 1 | const bufFromArray = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]) 2 | console.log(bufFromArray.toString()) // "buffer" 3 | 4 | const arrayBuffer = new Uint16Array(2) 5 | arrayBuffer[0] = 5 6 | arrayBuffer[1] = 7000 7 | 8 | // Shares memory with `arrayBuffer` 9 | const bufFromArrayBuffer = Buffer.from(arrayBuffer.buffer) 10 | 11 | // Prints: 12 | console.log(bufFromArrayBuffer) 13 | 14 | // Changing the original Uint16Array changes the Buffer also 15 | arrayBuffer[1] = 7001 16 | 17 | // Prints: 18 | console.log(bufFromArrayBuffer) 19 | 20 | const bufFromString = Buffer.from('¿Cómo está?') 21 | 22 | console.log(bufFromString.toString('utf8')) // ¿Cómo está? 23 | console.log(bufFromString.toString()) // ¿Cómo está? 24 | 25 | console.log(bufFromString.toString('ascii')) // B?CC3mo estC!? 26 | 27 | const bufFromHex = Buffer.from('c2bf43c3b36d6f20657374c3a13f', 'hex') 28 | 29 | console.log(bufFromHex.toString()) // ¿Cómo está? -------------------------------------------------------------------------------- /code/ch1/class.js: -------------------------------------------------------------------------------- 1 | class baseModel { 2 | constructor(options = {}, data = []) { // class constructor 3 | this.name = 'Base' 4 | this.url = 'http://azat.co/api' 5 | this.data = data 6 | this.options = options 7 | } 8 | getName() { // class method 9 | console.log(`Class name: ${this.name}`) 10 | } 11 | } 12 | class AccountModel extends baseModel { 13 | constructor(options, data) { 14 | super({private: true}, ['32113123123', '524214691']) //call the parent method with super 15 | this.name = 'Account Model' 16 | this.url +='/accounts/' 17 | } 18 | get accountsData() { //calculated attribute getter 19 | // ... make XHR 20 | return this.data 21 | } 22 | } 23 | 24 | let accounts = new AccountModel(5) 25 | accounts.getName() 26 | console.log('Data is %s', accounts.accountsData) -------------------------------------------------------------------------------- /code/ch1/hello-debug.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | debugger 3 | http.createServer(function (req, res) { 4 | res.writeHead(200, {'Content-Type': 'text/plain'}) 5 | debugger 6 | res.end('Hello World\n') 7 | }).listen(1337, '127.0.0.1') 8 | console.log('Server running at http://127.0.0.1:1337/') -------------------------------------------------------------------------------- /code/ch1/hello.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | http.createServer(function (req, res) { 3 | res.writeHead(200, {'Content-Type': 'text/plain'}) 4 | res.end('Hello World\n') 5 | }).listen(1337, '127.0.0.1') 6 | console.log('Server running at http://127.0.0.1:1337/') -------------------------------------------------------------------------------- /code/ch10/domains/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var routes = require('./routes'); 3 | var http = require('http'); 4 | var path = require('path'); 5 | var errorHandler = require('errorhandler'); 6 | 7 | var app = express(); 8 | 9 | app.set('port', process.env.PORT || 3000); 10 | app.set('views', __dirname + '/views'); 11 | app.set('view engine', 'jade'); 12 | app.use(express.static(path.join(__dirname, 'public'))); 13 | 14 | var domain = require('domain'); 15 | var defaultHandler = errorHandler(); 16 | app.get('/', routes.index); 17 | 18 | app.get('/e', function (req, res, next) { 19 | var d = domain.create(); 20 | d.on('error', function (error) { 21 | console.error(error.stack); 22 | res.send(500, {'error': error.message}); 23 | }); 24 | d.run(function () { 25 | //error prone code goes here 26 | throw new Error('Database is down.'); 27 | // next(new Error('Database is down.')); 28 | }); 29 | }); 30 | 31 | app.use(function (error, req, res, next) { 32 | if (domain.active) { 33 | console.info('caught with domain', domain.active); 34 | domain.active.emit('error', error); 35 | } else { 36 | console.info('no domain'); 37 | defaultHandler(error, req, res, next); 38 | } 39 | }); 40 | 41 | http.createServer(app).listen(app.get('port'), function () { 42 | console.log('Express server listening on port ' + app.get('port')); 43 | }); 44 | -------------------------------------------------------------------------------- /code/ch10/domains/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-domains", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "4.1.2", 10 | "pug": "", 11 | "errorhandler": "1.0.1" 12 | } 13 | } -------------------------------------------------------------------------------- /code/ch10/domains/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /code/ch10/domains/routes/index.js: -------------------------------------------------------------------------------- 1 | exports.index = function(req, res){ 2 | res.render('index', { title: 'Express' }); 3 | }; -------------------------------------------------------------------------------- /code/ch10/domains/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} -------------------------------------------------------------------------------- /code/ch10/domains/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /code/ch10/examples/cluster.js: -------------------------------------------------------------------------------- 1 | const cluster = require('cluster') 2 | const numCPUs = require('os').cpus().length 3 | const express = require('express') 4 | 5 | if (cluster.isMaster) { 6 | console.log(' Fork %s worker(s) from master', numCPUs) 7 | for (let i = 0; i < numCPUs; i++) { 8 | cluster.fork() 9 | } 10 | cluster.on('online', (worker) => { 11 | console.log('worker is running on %s pid', worker.process.pid) 12 | }) 13 | cluster.on('exit', (worker, code, signal) => { 14 | console.log('worker with %s is closed', worker.process.pid) 15 | }) 16 | } else if (cluster.isWorker) { 17 | const port = 3000 18 | console.log('worker (%s) is now listening to http://localhost:%s', cluster.worker.process.pid, port) 19 | const app = express() 20 | app.get('*', (req, res) => { 21 | res.status(200).send(`cluser ${cluster.worker.process.pid} responded \n`) 22 | }) 23 | app.listen(port) 24 | } 25 | -------------------------------------------------------------------------------- /code/ch10/examples/envvar.js: -------------------------------------------------------------------------------- 1 | console.log (process.env.NODE_ENV, 2 | process.env.API_KEY, 3 | process.env.DB_PASSWORD) 4 | -------------------------------------------------------------------------------- /code/ch10/examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "examples", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "cluster.js", 6 | "dependencies": { 7 | "express": "^4.1.2", 8 | "replify": "^1.2.0" 9 | }, 10 | "devDependencies": {}, 11 | "scripts": { 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "keywords": [], 15 | "author": "Azat Mardan (http://azat.co/)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /code/ch10/examples/repl.js: -------------------------------------------------------------------------------- 1 | const repl = require('repl') 2 | const net = require('net') 3 | const options = {name: 'azat'} 4 | 5 | const app = {a: 1} 6 | net.createServer((socket) => { 7 | repl.start(`${options.name}> `, socket).context.app = app 8 | }).listen(`/tmp/repl-app-${options.name}`) 9 | 10 | // var repl = require('repl'); 11 | // var net = require('net'), 12 | // options = { name: 'azat' }; 13 | 14 | // app = {a:1}; 15 | // net.createServer(function (socket) { 16 | // repl.start(options.name + "> ", socket).context.app = app 17 | // }).listen(3000); 18 | -------------------------------------------------------------------------------- /code/ch10/grunt-example/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | pkg: grunt.file.readJSON('package.json'), 5 | coffee: { 6 | compile: { 7 | files: { 8 | 'source/<%= pkg.name %>.js': ['source/**/*.coffee'] // compile and concat into single file 9 | } 10 | } 11 | }, 12 | concat: { 13 | options: { 14 | separator: ';' 15 | }, 16 | dist: { 17 | src: ['source/**/*.js'], 18 | dest: 'build/<%= pkg.name %>.js' 19 | } 20 | }, 21 | uglify: { 22 | options: { 23 | banner: '/*! <%= pkg.name %> <%= grunt.template.today("dd-mm-yyyy") %> */\n' 24 | }, 25 | dist: { 26 | files: { 27 | 'build/<%= pkg.name %>.min.js': ['<%= concat.dist.dest %>'] 28 | } 29 | } 30 | }, 31 | 32 | jshint: { 33 | files: ['Gruntfile.js', 'source/**/*.js'], 34 | options: { 35 | // options here to override JSHint defaults 36 | globals: { 37 | jQuery: true, 38 | console: true, 39 | module: true, 40 | document: true 41 | } 42 | } 43 | }, 44 | watch: { 45 | files: ['<%= jshint.files %>'], 46 | tasks: ['jshint', 'qunit'] 47 | } 48 | }) 49 | 50 | grunt.loadNpmTasks('grunt-contrib-uglify') 51 | grunt.loadNpmTasks('grunt-contrib-jshint') 52 | grunt.loadNpmTasks('grunt-contrib-concat') 53 | grunt.loadNpmTasks('grunt-contrib-coffee') 54 | 55 | 56 | grunt.registerTask('default', [ 'jshint', 'coffee','concat', 'uglify']) 57 | 58 | } -------------------------------------------------------------------------------- /code/ch10/grunt-example/build/grunt-example.js: -------------------------------------------------------------------------------- 1 | $.ajaxSetup({ 2 | xhrFields: {withCredentials: true}, 3 | error: function(xhr, status, error) { 4 | $('.alert').removeClass('hidden'); 5 | $('.alert').html("Status: " + status + ", error: " + error); 6 | } 7 | }); 8 | 9 | var findTr = function(event) { 10 | var target = event.srcElement || event.target; 11 | var $target = $(target); 12 | var $tr = $target.parents('tr'); 13 | return $tr; 14 | }; 15 | 16 | var remove = function(event) { 17 | var $tr = findTr(event); 18 | var id = $tr.data('id'); 19 | $.ajax({ 20 | url: '/api/articles/' + id, 21 | type: 'DELETE', 22 | success: function(data, status, xhr) { 23 | $('.alert').addClass('hidden'); 24 | $tr.remove(); 25 | } 26 | }); 27 | }; 28 | 29 | var update = function(event) { 30 | var $tr = findTr(event); 31 | $tr.find('button').attr('disabled', 'disabled'); 32 | var data = { 33 | published: $tr.hasClass('unpublished') 34 | }; 35 | var id = $tr.attr('data-id'); 36 | $.ajax({ 37 | url: '/api/articles/' + id, 38 | type: 'PUT', 39 | contentType: 'application/json', 40 | data: JSON.stringify({article: data}), 41 | success: function(dataResponse, status, xhr) { 42 | $tr.find('button').removeAttr('disabled'); 43 | $('.alert').addClass('hidden'); 44 | if (data.published) { 45 | $tr.removeClass('unpublished').find('.glyphicon-play').removeClass('glyphicon-play').addClass('glyphicon-pause'); 46 | } else { 47 | $tr.addClass('unpublished').find('.glyphicon-pause').removeClass('glyphicon-pause').addClass('glyphicon-play'); 48 | } 49 | } 50 | }); 51 | }; 52 | 53 | $(document).ready(function(){ 54 | var $element = $('.admin tbody'); 55 | $element.on('click', 'button.remove', remove); 56 | $element.on('click', 'button', update); 57 | });;(function() { 58 | var factorial; 59 | 60 | factorial = function(x) { 61 | if (x === 1) { 62 | return 1; 63 | } 64 | return x * factorial(x - 1); 65 | }; 66 | 67 | window.factorial = factorial; 68 | 69 | }).call(this); 70 | 71 | (function() { 72 | var Car; 73 | 74 | Car = (function() { 75 | function Car() {} 76 | 77 | Car.prototype._setType = function(type) { 78 | if (type == null) { 79 | type = 'compact'; 80 | } 81 | return this.type = type; 82 | }; 83 | 84 | Car.prototype.echoType = function() { 85 | return console.log(toyota.type); 86 | }; 87 | 88 | return Car; 89 | 90 | })(); 91 | 92 | window.Car = Car; 93 | 94 | }).call(this); 95 | -------------------------------------------------------------------------------- /code/ch10/grunt-example/build/grunt-example.min.js: -------------------------------------------------------------------------------- 1 | /*! grunt-example 22-02-2014 */ 2 | $.ajaxSetup({xhrFields:{withCredentials:!0},error:function(a,b,c){$(".alert").removeClass("hidden"),$(".alert").html("Status: "+b+", error: "+c)}});var findTr=function(a){var b=a.srcElement||a.target,c=$(b),d=c.parents("tr");return d},remove=function(a){var b=findTr(a),c=b.data("id");$.ajax({url:"/api/articles/"+c,type:"DELETE",success:function(){$(".alert").addClass("hidden"),b.remove()}})},update=function(a){var b=findTr(a);b.find("button").attr("disabled","disabled");var c={published:b.hasClass("unpublished")},d=b.attr("data-id");$.ajax({url:"/api/articles/"+d,type:"PUT",contentType:"application/json",data:JSON.stringify({article:c}),success:function(){b.find("button").removeAttr("disabled"),$(".alert").addClass("hidden"),c.published?b.removeClass("unpublished").find(".glyphicon-play").removeClass("glyphicon-play").addClass("glyphicon-pause"):b.addClass("unpublished").find(".glyphicon-pause").removeClass("glyphicon-pause").addClass("glyphicon-play")}})};$(document).ready(function(){var a=$(".admin tbody");a.on("click","button.remove",remove),a.on("click","button",update)}),function(){var a;a=function(b){return 1===b?1:b*a(b-1)},window.factorial=a}.call(this),function(){var a;a=function(){function a(){}return a.prototype._setType=function(a){return null==a&&(a="compact"),this.type=a},a.prototype.echoType=function(){return console.log(toyota.type)},a}(),window.Car=a}.call(this); -------------------------------------------------------------------------------- /code/ch10/grunt-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-example", 3 | "version": "0.0.1", 4 | "devDependencies": { 5 | "grunt": "~0.4.2", 6 | "grunt-contrib-jshint": "~0.6.3", 7 | "grunt-contrib-uglify": "~0.2.2", 8 | "grunt-contrib-coffee": "~0.10.1", 9 | "grunt-contrib-concat": "~0.3.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /code/ch10/grunt-example/source/admin.js: -------------------------------------------------------------------------------- 1 | $.ajaxSetup({ 2 | xhrFields: {withCredentials: true}, 3 | error: function(xhr, status, error) { 4 | $('.alert').removeClass('hidden'); 5 | $('.alert').html("Status: " + status + ", error: " + error); 6 | } 7 | }); 8 | 9 | var findTr = function(event) { 10 | var target = event.srcElement || event.target; 11 | var $target = $(target); 12 | var $tr = $target.parents('tr'); 13 | return $tr; 14 | }; 15 | 16 | var remove = function(event) { 17 | var $tr = findTr(event); 18 | var id = $tr.data('id'); 19 | $.ajax({ 20 | url: '/api/articles/' + id, 21 | type: 'DELETE', 22 | success: function(data, status, xhr) { 23 | $('.alert').addClass('hidden'); 24 | $tr.remove(); 25 | } 26 | }); 27 | }; 28 | 29 | var update = function(event) { 30 | var $tr = findTr(event); 31 | $tr.find('button').attr('disabled', 'disabled'); 32 | var data = { 33 | published: $tr.hasClass('unpublished') 34 | }; 35 | var id = $tr.attr('data-id'); 36 | $.ajax({ 37 | url: '/api/articles/' + id, 38 | type: 'PUT', 39 | contentType: 'application/json', 40 | data: JSON.stringify({article: data}), 41 | success: function(dataResponse, status, xhr) { 42 | $tr.find('button').removeAttr('disabled'); 43 | $('.alert').addClass('hidden'); 44 | if (data.published) { 45 | $tr.removeClass('unpublished').find('.glyphicon-play').removeClass('glyphicon-play').addClass('glyphicon-pause'); 46 | } else { 47 | $tr.addClass('unpublished').find('.glyphicon-pause').removeClass('glyphicon-pause').addClass('glyphicon-play'); 48 | } 49 | } 50 | }); 51 | }; 52 | 53 | $(document).ready(function(){ 54 | var $element = $('.admin tbody'); 55 | $element.on('click', 'button.remove', remove); 56 | $element.on('click', 'button', update); 57 | }); -------------------------------------------------------------------------------- /code/ch10/grunt-example/source/factorial.coffee: -------------------------------------------------------------------------------- 1 | factorial = (x) -> 2 | if x is 1 then return 1 3 | x * factorial(x - 1) 4 | 5 | window.factorial = factorial -------------------------------------------------------------------------------- /code/ch10/grunt-example/source/grunt-example.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var factorial; 3 | 4 | factorial = function(x) { 5 | if (x === 1) { 6 | return 1; 7 | } 8 | return x * factorial(x - 1); 9 | }; 10 | 11 | window.factorial = factorial; 12 | 13 | }).call(this); 14 | 15 | (function() { 16 | var Car; 17 | 18 | Car = (function() { 19 | function Car() {} 20 | 21 | Car.prototype._setType = function(type) { 22 | if (type == null) { 23 | type = 'compact'; 24 | } 25 | return this.type = type; 26 | }; 27 | 28 | Car.prototype.echoType = function() { 29 | return console.log(toyota.type); 30 | }; 31 | 32 | return Car; 33 | 34 | })(); 35 | 36 | window.Car = Car; 37 | 38 | }).call(this); 39 | -------------------------------------------------------------------------------- /code/ch10/grunt-example/source/index.coffee: -------------------------------------------------------------------------------- 1 | class Car 2 | _setType: (type='compact')-> 3 | @type = type 4 | echoType: ()-> 5 | console.log toyota.type 6 | 7 | 8 | 9 | window.Car = Car 10 | -------------------------------------------------------------------------------- /code/ch13/http2-express/app.js: -------------------------------------------------------------------------------- 1 | const http2 = require('http2') 2 | const fs = require('fs') 3 | const express = require('express') 4 | 5 | const app = express() 6 | app.get('/', (req, res)=>{ 7 | console.log('here') 8 | res.set('content-type', 'text/html') 9 | res.status(200).send('

Hello World

') 10 | }) 11 | const server = http2.createSecureServer({ 12 | key: fs.readFileSync('server.key'), 13 | cert: fs.readFileSync('server.crt') 14 | }, app) 15 | 16 | // server.on('error', (err) => console.error(err)) 17 | // server.on('socketError', (err) => console.error(err)) 18 | 19 | server.listen(3000) -------------------------------------------------------------------------------- /code/ch13/http2-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http2-express", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Azat Mardan (http://azat.co/)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "express": "4.16.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /code/ch13/http2-express/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDtTCCAp2gAwIBAgIJAJDAAp5TN7sbMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzANBgNVBAoTBk5PeDA4 4 | QTELMAkGA1UECxMCTkEwHhcNMTcxMjIwMDYwODE2WhcNMTgwMTE5MDYwODE2WjBF 5 | MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK 6 | EwZOT3gwOEExCzAJBgNVBAsTAk5BMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 7 | CgKCAQEArXLn7FOe2nmwcFnYaGhpptaQAMdyNOCuybTXND5n47GSfbPNx5F6O8pJ 8 | l70zUtbhiW4F1reqw4cgxIuJFAVGy0+DF0BOwaTqqejy5XTRD2zGbT1cVkAtbUJg 9 | QJrjTR1L70rEzPiK+QCeFyR/M+AJiDmXnk1l2S+pkcxkbSCCydTWnIBmb2ferAsJ 10 | 004OxqAv3v4wRA10BiLpuLt4pS7iO9YztgJi+L+Pnk9hViSW8AAqfzB7iQC0vDZG 11 | 0WwGQNzPa5yEw4qnE9KuegU4p1DJVoFoZb3TQ7Ng0FLVAPig56yp6yhiYtYDu6Ai 12 | c1So7xSFd0bQgYkrvArJYr2faGwtCQIDAQABo4GnMIGkMB0GA1UdDgQWBBRyi/7x 13 | LAvv7svdeITM5vcHG/nYWjB1BgNVHSMEbjBsgBRyi/7xLAvv7svdeITM5vcHG/nY 14 | WqFJpEcwRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQHEwJTRjEP 15 | MA0GA1UEChMGTk94MDhBMQswCQYDVQQLEwJOQYIJAJDAAp5TN7sbMAwGA1UdEwQF 16 | MAMBAf8wDQYJKoZIhvcNAQELBQADggEBABF3beKxuwk6C9K8J0FYaKdh3DhRT3WF 17 | BaHa489y9ZydlbMD1ITLgehQZC7EJnm2vNWSF+FY0Yptu5QjAD8qqWTD/hv/wguO 18 | VjHaidGmxZIAL7I/pMh/vKlYdP6wwSrUvR+My2Dyq/LwdOjG4GzqbPdrs91PdOLs 19 | LRvzNuDtyz+mZO22Nb4M8bdrNI/4MKSy512bPUHsKpTQsPmci14c+Gt1Z8t2hqYz 20 | F6uRUuaQI0Lznd7+TZEGsghF65cu6bDwkqlP9XZkPFXy9fVnKRhkFdnjZXIq3zZp 21 | Qg3Hk9hPy9t12Cs7Mb8Zaf3obsDEDEPT94dSfrte9A42HYtwsOv500U= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /code/ch13/http2-express/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEArXLn7FOe2nmwcFnYaGhpptaQAMdyNOCuybTXND5n47GSfbPN 3 | x5F6O8pJl70zUtbhiW4F1reqw4cgxIuJFAVGy0+DF0BOwaTqqejy5XTRD2zGbT1c 4 | VkAtbUJgQJrjTR1L70rEzPiK+QCeFyR/M+AJiDmXnk1l2S+pkcxkbSCCydTWnIBm 5 | b2ferAsJ004OxqAv3v4wRA10BiLpuLt4pS7iO9YztgJi+L+Pnk9hViSW8AAqfzB7 6 | iQC0vDZG0WwGQNzPa5yEw4qnE9KuegU4p1DJVoFoZb3TQ7Ng0FLVAPig56yp6yhi 7 | YtYDu6Aic1So7xSFd0bQgYkrvArJYr2faGwtCQIDAQABAoIBAQCL7WJLFp7V9Ewy 8 | J1ldybZKQVjFOCHbpNN9qGXhIONhHpVm0ZKWiLeK2sQKJTntf6tN2rPdKCkcOrxl 9 | s4gv3Z3iUVAZhujCchBaaLLkCBzMi918rX77miTLuzu5gvsS8Cqdbr1b+rrq7OOd 10 | GfWA3SHzgzrdG+WplDkBhVoTa6H3vmduz++8cJRodiPJRo0MleFg70vu/dCt1CI2 11 | bDk2H0hVZOd3uEVPkD5S2WIydIh3jartT2reRpfQAFC2bXiyWvfj7J4aoysWR/vR 12 | 5cyx1VBEqjqKZN/resEBdSeXblG9H3ucXu1VvsrPuJJqrZU35llyklX6YdRw+41n 13 | EyhmKkUNAoGBAOFOWKblBk2LgofyiPg4P4C1aGAyt6itCAkvD1ECjSEpUgkA6yhv 14 | WThjkJFxBV+/vV6ba4EoJNnCKZKbTVhUgeJhgiiBDxhLH1zKyWq3Nl48/M/p3svH 15 | 8J0BfZKnjx+O/7TIovBr5VNoBf8/nV7d432SiTcXi6L4muNyGJcv8+vPAoGBAMUU 16 | BBHkGfMzeO30JVSYgKkqBIjp3BfJAj2uHav9LKOgAKAmunEXj/GK8IY/1m5wmvJ0 17 | xnQ36u2SaBQGj6MfJO6QsV8p5M8fOKqjEUx5mIlpGSV6qKDNhiGewtrGz+cKTCm2 18 | 7NeUcbNSPpbyvJd/Ztt89+7GSL7W6Vt03xSKtlenAoGAZcG7ukzPxvAOWRYKFIla 19 | 7OeBp4jgd/cGHRe7BCxxzxtZaKWYj5+SnSwim/1+ntr7TSJGsIOP+SV/Hy6zBQTN 20 | 0I+XeHGpfqBXHtKbm235ySAkUkbfEAxSOKb2ruisdGViSaUgKhOg9EWUB88UZUcz 21 | uoAXJuGkeEttsPHT91x5/vMCgYB/fLpBRJiQ3dw9ujEn71tTky3Jz9ajwFFlyyvA 22 | pY9p7chZ0ZPMMtzX+PQtNECnwspJS8dRSRb9RjdztObYM/51IT7Me9JbNiw3HjPD 23 | j+lgAo/U0esh0rIRL52HXxMAt4WbeoztzEZdJTf999qN+kl0dPwCB1zprybYFdt1 24 | c2IPgwKBgGu1QuxiUY5CQpv3gA6wDUkob/RFJtE3eqALa0FrWthBs05kEQ2Ah0nn 25 | xOwuuFNFojCg6lBpshb6bipe5hmYhHpDQ4M4WBt1CnAr6BVtCzFzN7zkA+1BSJHP 26 | zGjNRfO3lg3uxNvtoew4kyDIgsajq6AMv9fZs3vvXcw7dnhgPKqn 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /code/ch13/http2-push/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDtTCCAp2gAwIBAgIJAPtympgvwgZnMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzANBgNVBAoTBk5PeDA4 4 | QTELMAkGA1UECxMCTkEwHhcNMTcxMjIxMDUxNDQ5WhcNMTgwMTIwMDUxNDQ5WjBF 5 | MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK 6 | EwZOT3gwOEExCzAJBgNVBAsTAk5BMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 7 | CgKCAQEApqu3a8MWX8E9st8Vs773Rq/vdctGqa4fvubYX8+gh6uDz9sd9lyRHQ1+ 8 | 4GmfgYpI0k/UkEDJl2mBwD3MaK7cdfbvA/Lsv6cTciR1zmivjyLg91EDRr5rvh2b 9 | Jwpe4Uo1pFQH3ThrnFe7jlNbvaUfA2Al+ypNmyBey4xLNaJbbQJwN9DKS5lmCvvN 10 | 9Rg46LI1yIySlFpD5RPZpew4CW4oCFNjvIw1ONMR/bSnMtWLpN1Nl7FAqERbC1WL 11 | SCF/awkysPp+XLyPLgWVaAHES1ZtxaAGynpcJLAxACW+b6GTr7/gX5OP+e8IP7R4 12 | i1wzL/COMHsCgO81bZRxssGS/1kM8QIDAQABo4GnMIGkMB0GA1UdDgQWBBSvbU49 13 | fhwZuOXBxwe6rR7OPSZxlDB1BgNVHSMEbjBsgBSvbU49fhwZuOXBxwe6rR7OPSZx 14 | lKFJpEcwRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQHEwJTRjEP 15 | MA0GA1UEChMGTk94MDhBMQswCQYDVQQLEwJOQYIJAPtympgvwgZnMAwGA1UdEwQF 16 | MAMBAf8wDQYJKoZIhvcNAQELBQADggEBADKjz8zUg6swLKSxHRQZWmSnRv8JD2lR 17 | jvsb/Qw2A07DkuW3rIjDYhaZtU4RyKadzFw00uxia+CZyfVJ73DinFjt1NHeeE7p 18 | nHeMPQ3TLUgzmkYs2uW087ujGKDxOiuJbSVsNg6cc8GD9m7CQX4i5WF/nORFyBLA 19 | tfKRAmSTBEb5HTNYT95m835PMwAxJ/aVmeVRuJOuS+Pn/B6UMBiXRMFmp1OWae7G 20 | iE7jRw3KZ7G5ypJEN6xDl7vnSZjxSqBkn15W0Bw3gWhjxnjb0GOGOeS5BgXzOO8F 21 | Ao832oEdSrmaEI5dRVfl1f13L02lWjH514gdpkvTngl7m6jYXwcA14Q= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /code/ch13/http2-push/server.js: -------------------------------------------------------------------------------- 1 | const http2 = require('http2') 2 | const fs = require('fs') 3 | 4 | const server = http2.createSecureServer({ 5 | key: fs.readFileSync('server.key'), 6 | cert: fs.readFileSync('server.crt') 7 | }) 8 | 9 | server.on('error', (err) => console.error(err)) 10 | server.on('socketError', (err) => console.error(err)) 11 | 12 | server.on('stream', (stream, headers) => { 13 | stream.respond({ 14 | 'content-type': 'text/html', 15 | ':status': 200 16 | }) 17 | stream.pushStream({ ':path': '/myfakefile.js' }, (pushStream) => { 18 | pushStream.respond({ 19 | 'content-type': 'text/javascript', 20 | ':status': 200 21 | }) 22 | pushStream.end(`alert('you win')`) 23 | }) 24 | stream.end('

Hello World

') 25 | }) 26 | 27 | server.listen(3000) 28 | -------------------------------------------------------------------------------- /code/ch13/http2-push/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEApqu3a8MWX8E9st8Vs773Rq/vdctGqa4fvubYX8+gh6uDz9sd 3 | 9lyRHQ1+4GmfgYpI0k/UkEDJl2mBwD3MaK7cdfbvA/Lsv6cTciR1zmivjyLg91ED 4 | Rr5rvh2bJwpe4Uo1pFQH3ThrnFe7jlNbvaUfA2Al+ypNmyBey4xLNaJbbQJwN9DK 5 | S5lmCvvN9Rg46LI1yIySlFpD5RPZpew4CW4oCFNjvIw1ONMR/bSnMtWLpN1Nl7FA 6 | qERbC1WLSCF/awkysPp+XLyPLgWVaAHES1ZtxaAGynpcJLAxACW+b6GTr7/gX5OP 7 | +e8IP7R4i1wzL/COMHsCgO81bZRxssGS/1kM8QIDAQABAoIBAEvQYm/XXu8RpLIf 8 | vex0WmDCGMfZPnxiozggI555gIkWILBE+L0e4Dd58xO7l/3B3FwmSgeQIOMJ8CZ1 9 | Ne0MVWYnfBOsxbpsEnFiWmAYyHrwn6qAJGBir/nb176Lfhs2uW3Vd3pOdsy4L62P 10 | B3Udjhzn+H7mgESnFde7Kl7sSnAr5qO3EIQeif85ZDMEn1IBhTfNXxQD6L3ByL7M 11 | k9oJslLW60NcrDLsq8cIvdBY9C9/etDDIDCIhhFCXV13Sh5POmpapJNrVCnePRB8 12 | Fh31M1YpU2++GC0Yq2hoU5aypUkPAJiV0f7OBdggY9k1phM4dvdWvrC2xrzMjVgC 13 | tEX6Fa0CgYEA2JovzupQEKhmxar5SDj2FFlwJfW+ccyzVOTisgS3iwE/VB3tulWr 14 | cW8O/S3HvtB4tJBraYN+XA+bmRTU839UTnl4JcvqgrEBJIYJfDtU9kz6xeOYd+s3 15 | tGaLfBXdHWZp06ZousGu363h97GCoLZbmdYROhd5UFoFbyCo0lxf4hcCgYEAxPyH 16 | 9vtPaZWzJ0liRXb9kJ8RGTcPvprIApBEMiPAkCi4yvpTJ6K3PvjehdlEwU9eLiWq 17 | DC68wDPTCIDfTQEI6c1gZoLbr/3MtYZBN85ehrPID9UrqIOxc5C76dKU/9wVljhi 18 | 9sYoZmo/1rhc+7TbNOpKIRuNIxtp5NhBb/OqljcCgYAA9jZOIeY3HLhQGmE++9nD 19 | 313LHVs6ICGPt/B1v+WcvWWtlvBuSmEXgNm9MA3aAAz5KEcB0e6ms/8J3/jOF/uk 20 | 2Y86A7zH1cdehSXXol/PNCpM5KTRq36001u9Mk+oCMNhiDSg16lSHvCI7AXbdtPT 21 | TAsJif8k5zFs++UkAfr/AwKBgQC8ji1nkK83FgBi/q9+ZqPYpyoqxLXhcS86pPVi 22 | OS69zvzz7iFiszJMqNP3sz1I/Lvd2FepSaOLJxA9933Hq9HoWpqIBYJcN8iXiWcs 23 | D/ITZkt+PEKKD2Twhf4Zl2TgdL6FCHxSUuOhBNsOoXL/zOJ3CDzk7AZxMHBwiUA5 24 | 9Te4iQKBgG4d8HF7ckF4J6gGJHX368qVcYOTQS4ZAgGPCE0JPFyXBTSZune73dI5 25 | 4L9XoDfnIKgj2xSdG6I6gB0whRYhcLJ2cWzYhotucvyZcZDx1uZv9MThzZ8kLPdt 26 | u3WHwpzNZbmV+2E89LyI8E+dh2AGuxuHu2T1f66FXYuQna8RCsh0 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /code/ch13/http2/server-simple.js: -------------------------------------------------------------------------------- 1 | const http2 = require('http2') 2 | const fs = require('fs') 3 | 4 | const server = http2.createSecureServer({ 5 | key: fs.readFileSync('server.key'), 6 | cert: fs.readFileSync('server.crt') 7 | }, (req, res) => { 8 | res.end('hello') 9 | }) 10 | server.on('error', (err) => console.error(err)) 11 | 12 | server.listen(3000) 13 | -------------------------------------------------------------------------------- /code/ch13/http2/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDtTCCAp2gAwIBAgIJAMP27nwpVJ2YMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV 3 | BAYTAlVTMQswCQYDVQQIEwJDQTELMAkGA1UEBxMCU0YxDzANBgNVBAoTBk5PeDA4 4 | QTELMAkGA1UECxMCTkEwHhcNMTcxMjIwMDU0ODM0WhcNMTgwMTE5MDU0ODM0WjBF 5 | MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExCzAJBgNVBAcTAlNGMQ8wDQYDVQQK 6 | EwZOT3gwOEExCzAJBgNVBAsTAk5BMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 7 | CgKCAQEAx/MkBcSG61pMaJ7Pi0HA0FKEhOfz1DLcsEGeOi+zc0LmhbxMHWF1fUa3 8 | RrI+KO+jwRWxYD+5RPkw1+lXeOC4wgUGiFd+3DI0P8g2h3cnVtW/HXQ7Sla8pP9I 9 | rA+d963n5AHin0DphcMyHB3t37kMaZR+YBstC58hVSTTDMYQA6E/Q3wm/p7Ueq0w 10 | uxtyzWuJ6ODfwy+ZsL570AiPsvyOqnwzlrfLseb+4SrB8zncI4lvMcRjZjdM56g0 11 | NED7Ft9AQ8wbEYEHBqk7LsdEUz4bEauOAD7ho0W46nxIQU5J/3dqxeO5c8rzIjad 12 | FeUcU3CnZuXZKhlyH+IVf/yL+x5jdwIDAQABo4GnMIGkMB0GA1UdDgQWBBTT1FfC 13 | y1WFPKRWzyCXofYY8kwGpjB1BgNVHSMEbjBsgBTT1FfCy1WFPKRWzyCXofYY8kwG 14 | pqFJpEcwRTELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQHEwJTRjEP 15 | MA0GA1UEChMGTk94MDhBMQswCQYDVQQLEwJOQYIJAMP27nwpVJ2YMAwGA1UdEwQF 16 | MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAAXYTioUSrJmoBad2rJIwxy4GAl9QHbK 17 | Rop6ENSuid4VfaG+0037fubR0ecan4vvIB5zS93RX2EgnW554mNS7ACSb7JBwJjX 18 | aAZZyVfhzyfyYun52GKBmSORvoqsgj9WKkDC7wn5BCYbm40e2hF4ilogPC8SzPJH 19 | lnHp6Z8NIenVmRAkny7PIdSxnchiR+ai7fZFmHivCpqXgnhL2QelmsJK5fOn1oMA 20 | DPP9oE0QsK1px1WhdmI5wJAkTLSNY7cc943TqRC7ABoe6mkk5pqoIXoewrBg9S4a 21 | udwl5wQ5V3axRfMxqqqlcOZqR2g1/8tQPav1khBFuJypk1GFEq4BWg0= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /code/ch13/http2/server.js: -------------------------------------------------------------------------------- 1 | const http2 = require('http2') 2 | const fs = require('fs') 3 | 4 | const server = http2.createSecureServer({ 5 | key: fs.readFileSync('server.key'), 6 | cert: fs.readFileSync('server.crt') 7 | }) 8 | server.on('error', (err) => console.error(err)) 9 | server.on('socketError', (err) => console.error(err)) 10 | 11 | server.on('stream', (stream, headers) => { 12 | // stream is a Duplex 13 | stream.respond({ 14 | 'content-type': 'text/html', 15 | ':status': 200 16 | }) 17 | stream.end('

Hello World

') 18 | }) 19 | 20 | server.listen(3000) 21 | -------------------------------------------------------------------------------- /code/ch13/http2/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEogIBAAKCAQEAx/MkBcSG61pMaJ7Pi0HA0FKEhOfz1DLcsEGeOi+zc0LmhbxM 3 | HWF1fUa3RrI+KO+jwRWxYD+5RPkw1+lXeOC4wgUGiFd+3DI0P8g2h3cnVtW/HXQ7 4 | Sla8pP9IrA+d963n5AHin0DphcMyHB3t37kMaZR+YBstC58hVSTTDMYQA6E/Q3wm 5 | /p7Ueq0wuxtyzWuJ6ODfwy+ZsL570AiPsvyOqnwzlrfLseb+4SrB8zncI4lvMcRj 6 | ZjdM56g0NED7Ft9AQ8wbEYEHBqk7LsdEUz4bEauOAD7ho0W46nxIQU5J/3dqxeO5 7 | c8rzIjadFeUcU3CnZuXZKhlyH+IVf/yL+x5jdwIDAQABAoIBABFHCkLRU5M8r0OP 8 | DYIMAXw+1SKi5GfeqUE1ulVuz8PS8RZ3SeB0DnHvzWB3gHWLF4xUsooNl5XFLppJ 9 | mHxidrSVOJZJcTXXxLoKM+QKwMR5HdWOuJzY194uDPqJMu43AcWrAAkg08zyK/9P 10 | +sENPWxWH4Fwd7j8XCX+U5pT9whJHHbDDaHRK8imK4HBfbXy+Lh8wCwY2OhO+Cc7 11 | Fm7xkYwk49KxBmbQbXbMxakELbZ453uFF6UsQhQSikM0mzDUJTMAdAgqxsKspDSL 12 | hnHccg9ewvX3/UuGfircuEJYYj2i+myD0XpVOi8BH3dr0Z7oJjIsyTIT7VDyz7hr 13 | ENd6uLkCgYEA9CEjvoo8fFl4ZAIYozU6xcCfMlUdFLVmGJVAbMlSEUEtPC0duy/Y 14 | 8KrayRIXmLL0iMdLQutBwCDLpcPcnyes9tYndVu9FF3RqRsrN1qOoSNvijVCdPjE 15 | 2m6lDDcUN8twLsWubZg9ih6ttG3jPuOym9iST5MgQf0FHr1m38MaLLsCgYEA0awQ 16 | VRE2MkFlEoYQcSPM1NFCaohKcFDVuOKqObHtX0WqpSgyLXJr49gXzk4h0tkdVT29 17 | G9t6Tsjpvcfqjpce2NwBiyVupeE9mvlETowmxxrn3zvSXwkse9/AIQEFYURiATYJ 18 | iZoQCAl89AYFtKDNDH2Z3F4ZW1Ay3qeyaWzhtnUCgYA/IO6wT+jSMwSpv80FLzeS 19 | eXvxNQc/GqVbCAVP3ENniNYcsIH2rKnJmjTXN+F4MBaB5B83H8Sx3QksRVigo07b 20 | yYcMlvtu78/OjeL//l/RlWs7bwhPXSDyZxgw73E/+FB7F81CM9L+C27ZsGzKD3FZ 21 | UYiMITx/M+GGEG4wwK/dDwKBgAGk+dPQ+L3YKXTFvGdGwpqAsaaoPv02hRaYIIGn 22 | VZdDspzs/cfBd/snfLhPgJBox4QdodgQFpt7k+m7IanQoZtNzm1K8901MQiAH/sF 23 | umAhY+QqUfIQ3lpyXM1ldqdWo/YieFyt6qZFfMdlLLJH+JO6gHRsAILo1B1O72VI 24 | R1TpAoGAT/gdgv4g0/nOb5c8FhxLtKpY5cnsjgnj8fgVxjI75JwbgM8tXqanjuCw 25 | e0ytl7mG8E/x1yok+a7fSaGTwYUg+snAq7kFAq5NCOJEfZpuTv0fQMY/5iIB1RnC 26 | Cm3xMVIfWqFUe1q0WMUxPSVP6Jo3okpKl2Hta6lUo8DZoBLP3S0= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /code/ch14/async-await/app-koa.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const Koa = require('koa') 3 | const app = new Koa() 4 | 5 | app.use(async ctx => { 6 | const response = await axios.get('http://azat.co') 7 | ctx.body = response.data 8 | }) 9 | 10 | app.listen(3000) -------------------------------------------------------------------------------- /code/ch14/async-await/async-await-error.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const getAzatsWebsite = async () => { 3 | try { 4 | const response = await axios.get('https://azat.co') 5 | return response.data 6 | } catch(e) { 7 | console.log('oooops') 8 | } 9 | } 10 | getAzatsWebsite().then(console.log) -------------------------------------------------------------------------------- /code/ch14/async-await/async-await.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | const getAzatsWebsite = async () => { 3 | const response = await axios.get('http://azat.co') 4 | return response.data 5 | } 6 | getAzatsWebsite().then(console.log) -------------------------------------------------------------------------------- /code/ch14/async-await/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-await", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "async-await.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Azat Mardan (http://azat.co/)", 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /code/ch14/async-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "async-example", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "parallel.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Azat Mardan (http://azat.co/)", 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /code/ch14/async-example/parallel-async.js: -------------------------------------------------------------------------------- 1 | const mongodb= require('mongodb') 2 | const url = 'mongodb://localhost:27017' 3 | const customers = require('./customer_data.json') 4 | const async = require('async') 5 | 6 | const finalCallback = (results)=>{ 7 | console.log(results) 8 | process.exit(0) 9 | } 10 | 11 | let tasks = [] 12 | const limit = customers.length 13 | 14 | mongodb.MongoClient.connect(url, (error, dbServer) => { 15 | if (error) return console.log(error) 16 | const db = dbServer.db('cryptoexchange') 17 | for (let i=0; i { 19 | db.collection('customers').insert(customers[i], (error, results) => { 20 | done(error, results) 21 | }) 22 | }) 23 | } 24 | async.parallel(tasks, (errors, results) => { 25 | if (errors) console.error(errors) 26 | finalCallback(results) 27 | }) 28 | }) 29 | 30 | -------------------------------------------------------------------------------- /code/ch14/async-example/parallel.js: -------------------------------------------------------------------------------- 1 | const mongodb= require('mongodb') 2 | const url = 'mongodb://localhost:27017' 3 | const customers = require('./customer_data.json') 4 | 5 | const finalCallback = (results)=>{ 6 | console.log(results) 7 | process.exit(0) 8 | } 9 | 10 | let tasksCompleted = 0 11 | const limit = customers.length 12 | 13 | mongodb.MongoClient.connect(url, (error, dbServer) => { 14 | if (error) return console.log(error) 15 | const db = dbServer.db('cryptoexchange') 16 | for (let i=0; i { 19 | tasksCompleted++ 20 | if (tasksCompleted === limit) return finalCallback(`Finished ${tasksCompleted} insertions for DB migration`) 21 | }) 22 | } 23 | }) -------------------------------------------------------------------------------- /code/ch14/generators/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generators", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js" 9 | }, 10 | "keywords": [], 11 | "author": "Azat Mardan (http://azat.co/)", 12 | "license": "MIT" 13 | } 14 | -------------------------------------------------------------------------------- /code/ch14/generators/readJSON.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | 4 | function* readJSON(filename, enc='utf8'){ 5 | const caller = yield 6 | fs.readFile(filename, enc, function (err, res){ 7 | if (err) caller.failure(err) 8 | else { 9 | try { 10 | caller.success(JSON.parse(res)) 11 | } catch (ex) { 12 | caller.failure(ex) 13 | } 14 | } 15 | }) 16 | } 17 | 18 | const gen = readJSON('./package.json') 19 | 20 | console.log(gen.next()) 21 | console.log(gen.next()) -------------------------------------------------------------------------------- /code/ch14/generators/server.js: -------------------------------------------------------------------------------- 1 | const koa = require('koa') 2 | const app = new koa() 3 | app.use(function *(next){ 4 | var start = new Date 5 | yield next 6 | var ms = new Date - start 7 | console.log('%s %s - %s', this.method, this.url, ms) 8 | }) 9 | app.use(function *(){ 10 | this.body = 'Hello World' 11 | }) 12 | 13 | app.listen(3000) -------------------------------------------------------------------------------- /code/ch14/promise/axios.js: -------------------------------------------------------------------------------- 1 | const axios = require('axios') 2 | // axios.get('http://azat.co') 3 | // .then((response)=>response.data) 4 | // .then(html => console.log(html)) 5 | 6 | 7 | axios.get('https://azat.co') 8 | .then((response)=>response.data) 9 | .then(html => console.log(html)) 10 | .catch(e=>console.error(e)) -------------------------------------------------------------------------------- /code/ch14/promise/basic-promise-2.js: -------------------------------------------------------------------------------- 1 | function myAsyncTimeoutFn(data) { 2 | 3 | let _callback = null 4 | 5 | setTimeout( () => { 6 | 7 | if ( _callback ) _callback() 8 | }, 1000) 9 | 10 | return { 11 | then(cb){ 12 | _callback = cb 13 | } 14 | } 15 | 16 | } 17 | 18 | myAsyncTimeoutFn('just a silly string argument').then(() => { 19 | console.log('Final callback is here') 20 | }) -------------------------------------------------------------------------------- /code/ch14/promise/basic-promise-3.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | function readFilePromise( filename ) { 3 | let _callback = () => {} 4 | let _errorCallback = () => {} 5 | 6 | fs.readFile(filename, (error, buffer) => { 7 | if (error) _errorCallback(error) 8 | else _callback(buffer) 9 | }) 10 | 11 | return { 12 | then( cb, errCb ){ 13 | _callback = cb 14 | _errorCallback = errCb 15 | } 16 | } 17 | 18 | } 19 | 20 | // readFilePromise('package.json').then( buffer => { 21 | // console.log( buffer.toString() ) 22 | // process.exit(0) 23 | // }, err => { 24 | // console.error( err ) 25 | // process.exit(1) 26 | // }) 27 | 28 | readFilePromise('package.jsan').then( buffer => { 29 | console.log( buffer.toString() ) 30 | process.exit(0) 31 | }, err => { 32 | console.error( err ) 33 | process.exit(1) 34 | }) -------------------------------------------------------------------------------- /code/ch14/promise/basic-promise.js: -------------------------------------------------------------------------------- 1 | function myAsyncTimeoutFn(data, callback) { 2 | setTimeout(() => { 3 | callback() 4 | }, 1000) 5 | } 6 | 7 | myAsyncTimeoutFn('just a silly string argument', () => { 8 | console.log('Final callback is here') 9 | }) -------------------------------------------------------------------------------- /code/ch14/promise/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "promise", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Azat Mardan (http://azat.co/)", 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /code/ch14/promise/readJSON.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | function readJSON(filename, enc='utf8'){ 3 | return new Promise(function (resolve, reject){ 4 | fs.readFile(filename, enc, function (err, res){ 5 | if (err) reject(err); 6 | else { 7 | try { 8 | resolve(JSON.parse(res)) 9 | } catch (ex) { 10 | reject(ex) 11 | } 12 | } 13 | }) 14 | }) 15 | } 16 | 17 | readJSON('./package.json').then(console.log) -------------------------------------------------------------------------------- /code/ch14/promise/try-catch.js: -------------------------------------------------------------------------------- 1 | try { 2 | JSON.parse('not valid json for sure') 3 | } catch (e) { 4 | console.error('nice message you will see') 5 | } 6 | console.log('still working') 7 | 8 | try { 9 | setTimeout(()=>JSON.parse('not valid json for sure'), 0) 10 | } catch (e) { 11 | console.error('nice message you will never see') 12 | } 13 | console.log('still working') -------------------------------------------------------------------------------- /code/ch15/banking-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6-alpine 2 | 3 | 4 | # Some image metadata 5 | LABEL version="1.0" 6 | LABEL description="This is an example of a Node API server with connection to MongoDB. \ 7 | More details at https://github.com/azat-co/node-in-production and https://node.university" 8 | #ARG mongodb_container_name 9 | #ARG app_env 10 | 11 | # Environment variables 12 | # Add/change/overwrite with docker run --env key=value 13 | # ENV NODE_ENV=$app_env 14 | ENV PORT=3000 15 | # ENV DB_URI="mongodb://${mongodb_container_name}:27017/db-${app_env}" 16 | # agr->env->npm start->pm2-dev or pm2-docker 17 | # User 18 | #USER app 19 | # Mount Volume in docker run command 20 | 21 | RUN npm i -g pm2@2.4.6 22 | 23 | # Create api directory 24 | RUN mkdir -p /usr/src/api 25 | # From now one we are working in /usr/src/api 26 | WORKDIR /usr/src/api 27 | 28 | # Install api dependencies 29 | COPY ./api/package.json . 30 | # Run build if necessary with devDependencies then clean them up 31 | RUN npm i --production 32 | 33 | # Copy keys from a secret URL, e.g., S3 bucket or GitHub Gist 34 | # Example adds an image from a remote URL 35 | ADD "https://process.filestackapi.com/ADNupMnWyR7kCWRvm76Laz/resize=height:60/https://www.filepicker.io/api/file/WYqKiG0xQQ65DBnss8nD" ./public/node-university-logo.png 36 | 37 | # Copy API source code 38 | COPY ./api/ . 39 | 40 | EXPOSE 3000 41 | 42 | # The following command will use NODE_ENV to run pm2-docker or pm2-dev 43 | CMD ["npm", "start"] 44 | -------------------------------------------------------------------------------- /code/ch15/banking-api/README.md: -------------------------------------------------------------------------------- 1 | see labs/1-dockerized-node.md 2 | -------------------------------------------------------------------------------- /code/ch15/banking-api/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "banking-api", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "server.js", 6 | "scripts": { 7 | "test": "sh ./test.sh", 8 | "start": "if [[ ${NODE_ENV} = production ]]; then ./node_modules/.bin/pm2-docker start -i 0 server.js; else ./node_modules/.bin/pm2-dev server.js; fi" 9 | }, 10 | "keywords": [], 11 | "author": "Azat Mardan", 12 | "license": "MIT", 13 | "dependencies": { 14 | "errorhandler": "1.5.0", 15 | "express": "4.15.2", 16 | "globalog": "1.0.0", 17 | "monk": "4.0.0", 18 | "pm2": "2.4.6" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /code/ch15/banking-api/api/server.js: -------------------------------------------------------------------------------- 1 | require('globalog') 2 | const http = require('http') 3 | const express = require('express') 4 | const errorhandler = require('errorhandler') 5 | const app = express() 6 | const monk = require('monk') 7 | 8 | const db = monk(process.env.DB_URI, (err)=>{ 9 | if (err) { 10 | error(err) 11 | process.exit(1) 12 | } 13 | }) 14 | 15 | const accounts = db.get('accounts') 16 | 17 | app.use(express.static('public')) 18 | app.use(errorhandler()) 19 | 20 | app.get('/accounts', (req, res, next)=>{ 21 | accounts.find({ }, (err, docs) =>{ 22 | if (err) return next(err) 23 | return res.send(docs) 24 | }) 25 | }) 26 | 27 | app.get('/accounts/:accountId/transactions', (req, res)=>{ 28 | accounts.findOne({_id: req.params.accountId}, (err, doc) =>{ 29 | if (err) return next(err) 30 | return res.send(doc.transactions) 31 | }) 32 | }) 33 | 34 | http.createServer(app).listen(process.env.PORT, ()=>{ 35 | log(`Listening on port ${process.env.PORT}`) 36 | }) 37 | -------------------------------------------------------------------------------- /code/ch15/banking-api/test.sh: -------------------------------------------------------------------------------- 1 | curl localhost:3000 2 | curl localhost:3000/accounts 3 | curl localhost:3000/accounts/10/transactions 4 | -------------------------------------------------------------------------------- /code/ch15/docker-node-hello/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6-alpine 2 | RUN mkdir -p /usr/src/app 3 | WORKDIR /usr/src/app 4 | COPY server.js /usr/src/app 5 | EXPOSE 3000 6 | CMD [ "node", "server.js" ] 7 | -------------------------------------------------------------------------------- /code/ch15/docker-node-hello/server.js: -------------------------------------------------------------------------------- 1 | const port = 3000 2 | require('http') 3 | .createServer((req, res) => { 4 | console.log('url:', req.url) 5 | res.end('hello world') 6 | }) 7 | .listen(port, (error)=>{ 8 | console.log(`server is running on ${port}`) 9 | }) 10 | -------------------------------------------------------------------------------- /code/ch16/serverless/db-api-test-post.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpMethod": "POST", 3 | "queryStringParameters": { 4 | "TableName": "messages" 5 | }, 6 | "body": { 7 | "TableName": "messages", 8 | "Item":{ 9 | "id":"1", 10 | "author": "Neil Armstrong", 11 | "text": "That is one small step for (a) man, one giant leap for mankind" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /code/ch16/serverless/db-api-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpMethod": "GET", 3 | "queryStringParameters": { 4 | "TableName": "messages" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /code/ch16/serverless/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | console.log('Loading function') 4 | const doc = require('dynamodb-doc') 5 | const dynamo = new doc.DynamoDB() 6 | 7 | // All the request info in event 8 | // "handler" is defined on the function creation 9 | exports.handler = (event, context, callback) => { 10 | // Callback to finish response 11 | const done = (err, res) => callback(null, { 12 | statusCode: err ? '400' : '200', 13 | body: err ? err.message : JSON.stringify(res), 14 | headers: { 15 | 'Content-Type': 'application/json' 16 | } 17 | }) 18 | // To support mock testing, accept object not just strings 19 | if (typeof event.body === 'string') { event.body = JSON.parse(event.body) } 20 | switch (event.httpMethod) { 21 | // Table name and key are in payload 22 | case 'DELETE': 23 | dynamo.deleteItem(event.body, done) 24 | break 25 | // No payload, just a query string param 26 | case 'GET': 27 | dynamo.scan({ TableName: event.queryStringParameters.TableName }, done) 28 | break 29 | // Table name and key are in payload 30 | case 'POST': 31 | dynamo.putItem(event.body, done) 32 | break 33 | // Table name and key are in payload 34 | case 'PUT': 35 | dynamo.updateItem(event.body, done) 36 | break 37 | default: 38 | done(new Error(`Unsupported method "${event.httpMethod}"`)) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /code/ch16/serverless/lambda-trust-policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "", 6 | "Effect": "Allow", 7 | "Principal": { 8 | "Service": [ 9 | "lambda.amazonaws.com" 10 | ] 11 | }, 12 | "Action": "sts:AssumeRole" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /code/ch16/serverless/my-first-fn.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: 'AWS::Serverless-2016-10-31' 3 | Description: >- 4 | A simple backend (read/write to DynamoDB) with a RESTful API endpoint using 5 | Amazon API Gateway. 6 | Resources: 7 | myfirstfn: 8 | Type: 'AWS::Serverless::Function' 9 | Properties: 10 | Handler: index.handler 11 | Runtime: nodejs6.10 12 | CodeUri: . 13 | Description: >- 14 | A simple backend (read/write to DynamoDB) with a RESTful API endpoint 15 | using Amazon API Gateway. 16 | MemorySize: 512 17 | Timeout: 10 18 | Role: 'arn:aws:iam::161599702702:role/service-role/my-role-for-fn' 19 | Events: 20 | Api1: 21 | Type: Api 22 | Properties: 23 | Path: /my-first-fn 24 | Method: ANY 25 | -------------------------------------------------------------------------------- /code/ch16/serverless/output.txt: -------------------------------------------------------------------------------- 1 | {"statusCode":"200","body":"{\"Items\":[{\"id\":\"1\",\"text\":\"That is one small step for (a) man, one giant leap for mankind\",\"author\":\"Neil Armstrong\"}],\"Count\":1,\"ScannedCount\":1}","headers":{"Content-Type":"application/json"}} -------------------------------------------------------------------------------- /code/ch2/express-styl/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var stylus = require('stylus'); 8 | 9 | var index = require('./routes/index'); 10 | var users = require('./routes/users'); 11 | 12 | var app = express(); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'jade'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(stylus.middleware(path.join(__dirname, 'public'))); 25 | app.use(express.static(path.join(__dirname, 'public'))); 26 | 27 | app.use('/', index); 28 | app.use('/users', users); 29 | 30 | // catch 404 and forward to error handler 31 | app.use(function(req, res, next) { 32 | var err = new Error('Not Found'); 33 | err.status = 404; 34 | next(err); 35 | }); 36 | 37 | // error handler 38 | app.use(function(err, req, res, next) { 39 | // set locals, only providing error in development 40 | res.locals.message = err.message; 41 | res.locals.error = req.app.get('env') === 'development' ? err : {}; 42 | 43 | // render the error page 44 | res.status(err.status || 500); 45 | res.render('error'); 46 | }); 47 | 48 | module.exports = app; 49 | -------------------------------------------------------------------------------- /code/ch2/express-styl/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('express-styl:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /code/ch2/express-styl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-styl", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.17.1", 10 | "cookie-parser": "~1.4.3", 11 | "debug": "~2.6.3", 12 | "express": "~4.15.2", 13 | "jade": "~1.11.0", 14 | "morgan": "~1.8.1", 15 | "serve-favicon": "~2.4.2", 16 | "stylus": "0.54.5" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /code/ch2/express-styl/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | a { 6 | color: #00b7ff; 7 | } 8 | -------------------------------------------------------------------------------- /code/ch2/express-styl/public/stylesheets/style.styl: -------------------------------------------------------------------------------- 1 | body 2 | padding: 50px 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif 4 | a 5 | color: #00B7FF 6 | -------------------------------------------------------------------------------- /code/ch2/express-styl/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var router = express.Router() 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }) 7 | }) 8 | 9 | module.exports = router 10 | -------------------------------------------------------------------------------- /code/ch2/express-styl/routes/users.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var router = express.Router() 3 | 4 | /* GET users listing. */ 5 | router.get('/', function(req, res, next) { 6 | res.send('respond with a resource') 7 | }) 8 | 9 | module.exports = router 10 | -------------------------------------------------------------------------------- /code/ch2/express-styl/views/error.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /code/ch2/express-styl/views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to #{title} 6 | -------------------------------------------------------------------------------- /code/ch2/express-styl/views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content 8 | -------------------------------------------------------------------------------- /code/ch2/hello-advanced/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | 3 | const http = require('http') 4 | const path = require('path') 5 | 6 | let app = express() 7 | 8 | app.set('port', process.env.PORT || 3000) 9 | app.set('views', path.join(__dirname, 'views')) 10 | app.set('view engine', 'pug') 11 | 12 | app.all('*', (req, res) => { 13 | res.render('index', {msg: 'Welcome to Practical Node.js!'}) 14 | }) 15 | 16 | http.createServer(app).listen(app.get('port'), () => { 17 | console.log(`Express server listening on port ${app.get('port')}`) 18 | }) -------------------------------------------------------------------------------- /code/ch2/hello-advanced/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-advanced", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "4.15.4", 10 | "pug": "2.0.0-rc.4" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /code/ch2/hello-advanced/views/index.pug: -------------------------------------------------------------------------------- 1 | h1 hello 2 | p= msg -------------------------------------------------------------------------------- /code/ch2/hello-simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-simple", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Azat Mardan (http://azat.co/)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "express": "4.15.4" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /code/ch2/hello-simple/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | let app = express() 3 | 4 | app.all('*', (req, res) => { 5 | res.send('Welcome to Practical Node.js!') 6 | }) 7 | 8 | app.listen(3000, () => {console.log('Open at localhost:3000')}) -------------------------------------------------------------------------------- /code/ch3/blog-express/Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = list 2 | MOCHA_OPTS = --ui bdd -c 3 | 4 | test: 5 | clear 6 | echo Starting test ********************************************************* 7 | ./node_modules/mocha/bin/mocha \ 8 | --reporter $(REPORTER) \ 9 | $(MOCHA_OPTS) \ 10 | tests/*.js 11 | echo Ending test 12 | 13 | .PHONY: test -------------------------------------------------------------------------------- /code/ch3/blog-express/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | 3 | const http = require('http') 4 | const path = require('path') 5 | 6 | const app = express() 7 | 8 | app.set('port', process.env.PORT || 3000) 9 | app.set('views', path.join(__dirname, 'views')) 10 | app.set('view engine', 'pug') 11 | 12 | app.all('*', (req, res) => { 13 | res.render('index', {msg: 'Welcome to the Practical Node.js!'}) 14 | }) 15 | 16 | // http.createServer(app).listen(app.get('port'), () => { 17 | // console.log('Express server listening on port ' + app.get('port')) 18 | // }) 19 | 20 | const server = http.createServer(app) 21 | const boot = () => { 22 | server.listen(app.get('port'), () => { 23 | console.info(`Express server listening on port ${app.get('port')}`) 24 | }) 25 | } 26 | const shutdown = () => { 27 | server.close() 28 | } 29 | if (require.main === module) { 30 | boot() 31 | } else { 32 | console.info('Running app as a module') 33 | exports.boot = boot 34 | exports.shutdown = shutdown 35 | exports.port = app.get('port') 36 | } -------------------------------------------------------------------------------- /code/ch3/blog-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-express", 3 | "version": "0.0.2", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js", 7 | "test": "mocha tests" 8 | }, 9 | "dependencies": { 10 | "express": "4.16.2", 11 | "pug": "2.0.0-rc.4", 12 | "stylus": "0.54.5" 13 | }, 14 | "devDependencies": { 15 | "expect.js": "0.3.1", 16 | "mocha": "4.0.1", 17 | "superagent": "3.8.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/ch3/blog-express/tests/index.js: -------------------------------------------------------------------------------- 1 | const boot = require('../app').boot 2 | const shutdown = require('../app').shutdown 3 | const port = require('../app').port 4 | const superagent = require('superagent') 5 | const expect = require('expect.js') 6 | 7 | describe('server', () => { 8 | before(() => { 9 | boot() 10 | }) 11 | 12 | describe('homepage', () => { 13 | it('should respond to GET', (done) => { 14 | superagent 15 | .get(`http://localhost:${port}`) 16 | .end((error, response) => { 17 | expect(response.status).to.equal(200) 18 | done() 19 | }) 20 | }) 21 | }) 22 | 23 | after(() => { 24 | shutdown() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /code/ch3/blog-express/views/index.pug: -------------------------------------------------------------------------------- 1 | h1 hello 2 | p= msg -------------------------------------------------------------------------------- /code/ch3/test-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "mocha test-expect.js" 4 | }, 5 | "devDependencies": { 6 | "chai": "4.1.2", 7 | "mocha": "4.0.1" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /code/ch3/test-example/test-assert-v2.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var expected, current 3 | 4 | before(() => { 5 | expected = ['a', 'b', 'c'] 6 | }) 7 | 8 | describe('String#split', () => { 9 | 10 | beforeEach(() => { 11 | current = 'a,b,c'.split(',') 12 | }) 13 | 14 | it('should return an array', () => { 15 | assert(Array.isArray(current)) 16 | }) 17 | 18 | it('should return the same array', () => { 19 | assert.equal(expected.length, 20 | current.length, 21 | 'arrays have equal length') 22 | for (let i = 0; i < expected.length; i++) { 23 | assert.equal(expected[i], 24 | current[i], 25 | `i element is equal`) 26 | } 27 | }) 28 | 29 | }) -------------------------------------------------------------------------------- /code/ch3/test-example/test-assert.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | // const assert = require('chai').assert 3 | const testArray = ['a','b','c'] 4 | const testString = 'a,b,c' 5 | 6 | describe('String#split', () => { 7 | 8 | it('should return an array', () => { 9 | assert(Array.isArray('a,b,c'.split(','))) 10 | }) 11 | 12 | it('should return the same array', () => { 13 | assert.equal(testArray.length, 14 | testString.split(',').length, 15 | `arrays have equal length`) 16 | for (let i = 0; i < testArray.length; i++) { 17 | assert.equal(testArray[i], 18 | testString.split(',')[i], 19 | `i element is equal`) 20 | } 21 | }) 22 | 23 | }) -------------------------------------------------------------------------------- /code/ch3/test-example/test-expect.js: -------------------------------------------------------------------------------- 1 | const {expect} = require('chai') 2 | let expected 3 | let current 4 | 5 | before(() => { 6 | expected = ['a', 'b', 'c'] 7 | }) 8 | 9 | describe('String#split', () => { 10 | 11 | beforeEach(() => { 12 | current = 'a,b,c'.split(',') 13 | }) 14 | 15 | it('should return an array', () => { 16 | expect(Array.isArray(current)).to.be.true 17 | }) 18 | 19 | it('should return the same array', () => { 20 | expect(expected.length).to.equal(current.length) 21 | for (let i = 0; i < expected.length; i++) { 22 | expect(expected[i]).equal(current[i]) 23 | } 24 | }) 25 | }) -------------------------------------------------------------------------------- /code/ch4/consolidate/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const cons = require('consolidate') 3 | const path = require('path') 4 | 5 | let app = express() 6 | 7 | app.engine('html', cons.swig) 8 | 9 | app.set('view engine', 'html') 10 | app.set('views', path.join(__dirname, 'templates')) 11 | 12 | var platforms = [ 13 | { name: 'node' }, 14 | { name: 'ruby' }, 15 | { name: 'python' } 16 | ] 17 | 18 | app.get('/', (req, res) => { 19 | res.render('index', { 20 | title: 'Consolidate This' 21 | }) 22 | }) 23 | 24 | app.get('/platforms', (req, res) => { 25 | res.render('platforms', { 26 | title: 'Platforms', 27 | platforms: platforms 28 | }) 29 | }) 30 | 31 | app.listen(3000, () => { 32 | console.log('Express server listening on port 3000') 33 | }) 34 | -------------------------------------------------------------------------------- /code/ch4/consolidate/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consolidate-express", 3 | "version": "0.0.2", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "consolidate": "0.15.0", 10 | "express": "4.16.2", 11 | "swig": "1.4.2" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /code/ch4/consolidate/templates/index.html: -------------------------------------------------------------------------------- 1 |

{{ title }}

2 | platforms -------------------------------------------------------------------------------- /code/ch4/consolidate/templates/platforms.html: -------------------------------------------------------------------------------- 1 |

{{ title }}

2 |
    3 | {% for platform in platforms %} 4 | 5 | {{ platform.name }} 6 | 7 | {% endfor %} 8 |
-------------------------------------------------------------------------------- /code/ch4/handlebars-example/handlebars-example.html: -------------------------------------------------------------------------------- 1 |
2 |

{{custom_title title}}

3 |
4 |
5 |

{{body}}

6 |
7 | -------------------------------------------------------------------------------- /code/ch4/handlebars-example/handlebars-example.js: -------------------------------------------------------------------------------- 1 | const handlebars = require('handlebars') 2 | const fs = require('fs') 3 | const path = require('path') 4 | 5 | const data = { 6 | title: 'practical node.js', 7 | author: '@azatmardan', 8 | tags: ['express', 'node', 'javascript'] 9 | } 10 | data.body = process.argv[2] 11 | const filePath = path.join(__dirname, 12 | 'handlebars-example.html') 13 | 14 | data.tableData = [ 15 | {name: 'express', url: 'http://expressjs.com/'}, 16 | {name: 'hapi', url: 'http://spumko.github.io/'}, 17 | {name: 'compound', url: 'http://compoundjs.com/'}, 18 | {name: 'derby', url: 'http://derbyjs.com/'} 19 | ] 20 | 21 | fs.readFile(filePath, 'utf-8', (error, source) => { 22 | if (error) return console.error(error) 23 | handlebars.registerHelper('table', (data) => { 24 | let str = '' 25 | for (let i = 0; i < data.length; i++) { 26 | str += '' 27 | for (var key in data[i]) { 28 | str += '' 29 | } 30 | str += '' 31 | } 32 | str += '
' + data[i][key] + '
' 33 | return new handlebars.SafeString(str) 34 | }) 35 | handlebars.registerHelper('custom_title', (title) => { 36 | let words = title.split(' ') 37 | for (let i = 0; i < words.length; i++) { 38 | if (words[i].length > 4) { 39 | words[i] = words[i][0].toUpperCase() + words[i].substr(1) 40 | } 41 | } 42 | title = words.join(' ') 43 | return title 44 | }) 45 | const template = handlebars.compile(source) 46 | const html = template(data) 47 | console.log(html) 48 | }) 49 | -------------------------------------------------------------------------------- /code/ch4/handlebars-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "handlebars-example", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "handlebars-example.js", 6 | "dependencies": { 7 | "handlebars": "4.0.11" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "start": "node handlebars-example.js" 13 | }, 14 | "keywords": [], 15 | "author": "Azat Mardan (http://azat.co/)", 16 | "license": "MIT" 17 | } 18 | -------------------------------------------------------------------------------- /code/ch4/pug-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pug-example", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "pug-example.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node pug-example.js" 9 | }, 10 | "keywords": [], 11 | "author": "Azat Mardan (http://azat.co/)", 12 | "license": "MIT", 13 | "dependencies": { 14 | "pug": "2.0.0-rc.4" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /code/ch4/pug-example/pug-example.js: -------------------------------------------------------------------------------- 1 | const pug = require('pug'), 2 | fs = require('fs') 3 | 4 | let data = { 5 | title: 'Practical Node.js', 6 | author: { 7 | twitter: '@azatmardan', 8 | name: 'Azat' 9 | }, 10 | tags: ['express', 'node', 'javascript'] 11 | } 12 | data.body = process.argv[2] 13 | 14 | fs.readFile('pug-example.pug', 'utf-8', (error, source) => { 15 | let template = pug.compile(source) 16 | let html = template(data) 17 | console.log(html) 18 | }) 19 | 20 | //pug.render 21 | 22 | // fs.readFile('pug-example.pug', 'utf-8', (error, source) => { 23 | // const html = pug.render(source, data) 24 | // console.log(html) 25 | // }) 26 | 27 | //pug.renderFile 28 | 29 | // pug.renderFile('pug-example.pug', data, (error, html) => { 30 | // console.log(html) 31 | // }) 32 | -------------------------------------------------------------------------------- /code/ch4/pug-example/pug-example.pug: -------------------------------------------------------------------------------- 1 | .header 2 | h1= title 3 | p 4 | .body 5 | p= body 6 | .footer 7 | div= By 8 | a(href="http://twitter.com/#{author.twitter}")= author.name 9 | ul 10 | each tag, index in tags 11 | li= tag -------------------------------------------------------------------------------- /code/ch4/pug-example/pug-method-example.js: -------------------------------------------------------------------------------- 1 | const pug = require('pug') 2 | 3 | const pugTemplate = `// content goes here 4 | p Node.js is a non-blocking I/O for scalable apps. 5 | //- @todo change this to a class 6 | p(id="footer") Copyright 2014 Azat` 7 | 8 | 9 | const htmlString = pug.render(pugTemplate, {pretty: true}) 10 | console.log(htmlString) 11 | 12 | const pugTemplate2 = `doctype html 13 | html(lang="en") 14 | head 15 | title Why JavaScript is Awesome | CodingFear: programming and human circumstances 16 | script(type='text/javascript'). 17 | const a = 1 18 | console.log(\`Some JavaScript code here and the value of a is \${a}\`) 19 | body 20 | h1 Why JavaScript is Awesome 21 | #container.col 22 | p You are amazing 23 | p Get on it! 24 | p. 25 | JavaScript is fun. Almost everything 26 | can be written in JavaScript. It is huge.` 27 | 28 | 29 | const htmlString2 = pug.render(pugTemplate2, {pretty: true}) 30 | console.log(htmlString2) 31 | 32 | -------------------------------------------------------------------------------- /code/ch5/blog-express/Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = list 2 | MOCHA_OPTS = --ui bdd -c 3 | 4 | db: 5 | echo Seeding blog-test ***************************************************** 6 | ./seed.sh 7 | test: 8 | clear 9 | 10 | echo Starting test ********************************************************* 11 | ./node_modules/mocha/bin/mocha \ 12 | --reporter $(REPORTER) \ 13 | $(MOCHA_OPTS) \ 14 | tests/*.js 15 | echo Ending test 16 | 17 | .PHONY: test db -------------------------------------------------------------------------------- /code/ch5/blog-express/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const routes = require('./routes') 3 | const http = require('http') 4 | const path = require('path') 5 | const mongoskin = require('mongoskin') 6 | const dbUrl = process.env.MONGOHQ_URL || 'mongodb://@localhost:27017/blog' 7 | 8 | const db = mongoskin.db(dbUrl) 9 | const collections = { 10 | articles: db.collection('articles'), 11 | users: db.collection('users') 12 | } 13 | 14 | // const cookieParser = require('cookie-parser') 15 | // const session = require('express-session') 16 | const logger = require('morgan') 17 | const errorHandler = require('errorhandler') 18 | const bodyParser = require('body-parser') 19 | const methodOverride = require('method-override') 20 | 21 | const app = express() 22 | app.locals.appTitle = 'blog-express' 23 | 24 | // Expose collections to request handlers 25 | app.use((req, res, next) => { 26 | if (!collections.articles || !collections.users) return next(new Error('No collections.')) 27 | req.collections = collections 28 | return next() 29 | }) 30 | 31 | // Express.js configurations 32 | app.set('port', process.env.PORT || 3000) 33 | app.set('views', path.join(__dirname, 'views')) 34 | app.set('view engine', 'pug') 35 | 36 | // Express.js middleware configuration 37 | app.use(logger('dev')) 38 | app.use(bodyParser.json()) 39 | app.use(bodyParser.urlencoded({extended: true})) 40 | app.use(methodOverride()) 41 | app.use(require('stylus').middleware(path.join(__dirname, 'public'))) 42 | app.use(express.static(path.join(__dirname, 'public'))) 43 | 44 | // development only 45 | if (app.get('env') === 'development') { 46 | app.use(errorHandler('dev')) 47 | } 48 | 49 | // Pages and routes 50 | app.get('/', routes.index) 51 | app.get('/login', routes.user.login) 52 | app.post('/login', routes.user.authenticate) 53 | app.get('/logout', routes.user.logout) 54 | app.get('/admin', routes.article.admin) 55 | app.get('/post', routes.article.post) 56 | app.post('/post', routes.article.postArticle) 57 | app.get('/articles/:slug', routes.article.show) 58 | 59 | // REST API routes 60 | app.get('/api/articles', routes.article.list) 61 | app.post('/api/articles', routes.article.add) 62 | app.put('/api/articles/:id', routes.article.edit) 63 | app.delete('/api/articles/:id', routes.article.del) 64 | 65 | app.all('*', (req, res) => { 66 | res.status(404).send() 67 | }) 68 | 69 | // http.createServer(app).listen(app.get('port'), function(){ 70 | // console.log('Express server listening on port ' + app.get('port')); 71 | // }); 72 | 73 | const server = http.createServer(app) 74 | const boot = function () { 75 | server.listen(app.get('port'), function () { 76 | console.info(`Express server listening on port ${app.get('port')}`) 77 | }) 78 | } 79 | const shutdown = function () { 80 | server.close(process.exit) 81 | } 82 | if (require.main === module) { 83 | boot() 84 | } else { 85 | console.info('Running app as a module') 86 | exports.boot = boot 87 | exports.shutdown = shutdown 88 | exports.port = app.get('port') 89 | } 90 | -------------------------------------------------------------------------------- /code/ch5/blog-express/db/articles.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "title": "Node is a movement", 3 | "slug": "node-movement", 4 | "published": true, 5 | "text": "In one random deployment, it is often assumed that the number of scattered sensors are more than that required by the critical sensor density. Otherwise, complete area coverage may not be guaranteed in this deployment, and some coverage holes may exist. Besides using more sensors to improve coverage, mobile sensor nodes can be used to improve network coverage. Mobile sensor nodes are equipped with mobile platforms and can move around after initial deployment. Although a mobile sensor node normally is more expensive than its stationary compeer, it can serve many functionalities and improve network coverage. The design of a node movement strategy usually needs to address the following question:Where to move and how to efficiently move mobile modes so that area coverage can be optimized? \nIn different network scenarios, the objectives of nodes’ movement are different. In a hybrid network consisting of both stationary and mobile sensor nodes, the objective is mainly to relocate mobile nodes to heal the coverage holes caused by the stationary nodes. In a mobile network consisting of only mobile nodes, the primary objective is to maximize the coverage of these mobile nodes, and in event monitoring scenario, an important objective is to dispatch mobile nodes to the sources of events for better event coverage." 6 | }, { 7 | "title": "Express.js Experience", 8 | "slug": "express-experience", 9 | "text": "Work in progress", 10 | "published": false 11 | },{ 12 | "title": "Node.js FUNdamentals: A Concise Overview of The Main Concepts", 13 | "slug": "node-fundamentals", 14 | "published": true, 15 | "text": "Node.js is a highly efficient and scalable non-blocking I/O platform that was build on top of Google Chrome V8 engine and its ECMAScript. This means that most front-end JavaScript (another implementation of ECMAScript) objects, functions and methods are available in Node.js. Please refer to JavaScript FUNdamentals if you need a refresher on JS-specific basics." 16 | }] -------------------------------------------------------------------------------- /code/ch5/blog-express/db/users.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "email": "hi@azat.co", 3 | "admin": true, 4 | "password": "1" 5 | }] -------------------------------------------------------------------------------- /code/ch5/blog-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-express", 3 | "version": "0.0.5", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js", 7 | "seed": "make db", 8 | "test": "make test", 9 | "st": "standard app.js && standard tests/index.js && standard routes/*" 10 | }, 11 | "dependencies": { 12 | "body-parser": "1.18.2", 13 | "cookie-parser": "1.4.3", 14 | "errorhandler": "1.5.0", 15 | "express": "4.16.2", 16 | "express-session": "1.15.6", 17 | "method-override": "2.3.10", 18 | "mongodb": "2.2.33", 19 | "mongoskin": "2.1.0", 20 | "morgan": "1.9.1", 21 | "pug": "2.0.0-rc.4", 22 | "serve-favicon": "2.4.5", 23 | "stylus": "0.54.5" 24 | }, 25 | "devDependencies": { 26 | "standard": "10.0.3", 27 | "mocha": "4.0.1", 28 | "superagent": "3.8.0", 29 | "expect.js": "0.3.1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /code/ch5/blog-express/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch5/blog-express/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /code/ch5/blog-express/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch5/blog-express/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /code/ch5/blog-express/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch5/blog-express/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /code/ch5/blog-express/public/css/style.css: -------------------------------------------------------------------------------- 1 | #wrap { 2 | min-height: 100%; 3 | height: auto; 4 | margin: 0 auto -60px; 5 | padding: 0 0 60px; 6 | } 7 | footer { 8 | height: 60px; 9 | } 10 | html, 11 | body { 12 | height: 100%; 13 | } 14 | .content { 15 | margin-top: 40px; 16 | margin-bottom: 40px; 17 | } 18 | .glyphicon-remove, 19 | .glyphicon-pause, 20 | .glyphicon-play { 21 | cursor: pointer; 22 | } 23 | tr.unpublished { 24 | color: #cec1c2; 25 | } 26 | td.action { 27 | width: 20px; 28 | } 29 | .alert { 30 | margin-top: 20px; 31 | } 32 | -------------------------------------------------------------------------------- /code/ch5/blog-express/public/css/style.styl: -------------------------------------------------------------------------------- 1 | #wrap 2 | min-height: 100%; 3 | height: auto; 4 | margin: 0 auto -60px; 5 | padding: 0 0 60px; 6 | 7 | footer 8 | height: 60px; 9 | 10 | html, body 11 | height: 100%; 12 | .content 13 | margin-top 40px 14 | margin-bottom 40px 15 | .glyphicon-remove, .glyphicon-pause, .glyphicon-play 16 | cursor pointer 17 | tr.unpublished 18 | color #cec1c2 19 | td.action 20 | width 20px 21 | .alert 22 | margin-top 20px 23 | -------------------------------------------------------------------------------- /code/ch5/blog-express/public/js/admin.js: -------------------------------------------------------------------------------- 1 | $.ajaxSetup({ 2 | xhrFields: {withCredentials: true}, 3 | error: function (xhr, status, error) { 4 | $('.alert').removeClass('hidden') 5 | $('.alert').html('Status: ' + status + ', error: ' + error) 6 | } 7 | }) 8 | 9 | var findTr = function (event) { 10 | var target = event.srcElement || event.target 11 | var $target = $(target) 12 | var $tr = $target.parents('tr') 13 | return $tr 14 | } 15 | 16 | var remove = function (event) { 17 | var $tr = findTr(event) 18 | var id = $tr.data('id') 19 | $.ajax({ 20 | url: '/api/articles/' + id, 21 | type: 'DELETE', 22 | success: function (data, status, xhr) { 23 | $('.alert').addClass('hidden') 24 | $tr.remove() 25 | } 26 | }) 27 | } 28 | 29 | var update = function (event) { 30 | var $tr = findTr(event) 31 | $tr.find('button').attr('disabled', 'disabled') 32 | var data = { 33 | published: $tr.hasClass('unpublished') 34 | } 35 | var id = $tr.attr('data-id') 36 | $.ajax({ 37 | url: '/api/articles/' + id, 38 | type: 'PUT', 39 | contentType: 'application/json', 40 | data: JSON.stringify({article: data}), 41 | success: function (dataResponse, status, xhr) { 42 | $tr.find('button').removeAttr('disabled') 43 | $('.alert').addClass('hidden') 44 | if (data.published) { 45 | $tr.removeClass('unpublished').find('.glyphicon-play').removeClass('glyphicon-play').addClass('glyphicon-pause') 46 | } else { 47 | $tr.addClass('unpublished').find('.glyphicon-pause').removeClass('glyphicon-pause').addClass('glyphicon-play') 48 | } 49 | } 50 | }) 51 | } 52 | 53 | $(document).ready(function () { 54 | var $element = $('.admin tbody') 55 | $element.on('click', 'button.remove', remove) 56 | $element.on('click', 'button', update) 57 | }) 58 | -------------------------------------------------------------------------------- /code/ch5/blog-express/public/js/blog.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch5/blog-express/public/js/blog.js -------------------------------------------------------------------------------- /code/ch5/blog-express/readme.md: -------------------------------------------------------------------------------- 1 | # Blog with articles/posts 2 | 3 | ## Usage 4 | 5 | ``` 6 | npm i 7 | npm run seed 8 | make test 9 | npm start 10 | ``` 11 | 12 | # Todo 13 | 14 | * Markdown 15 | * Edit post (modal, fetch data via AJAX) 16 | * Photo upload 17 | * salt password -------------------------------------------------------------------------------- /code/ch5/blog-express/routes/article.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET article page. 4 | */ 5 | 6 | exports.show = (req, res, next) => { 7 | if (!req.params.slug) return next(new Error('No article slug.')) 8 | req.collections.articles.findOne({slug: req.params.slug}, (error, article) => { 9 | if (error) return next(error) 10 | if (!article.published) return res.status(401).send() 11 | res.render('article', article) 12 | }) 13 | } 14 | 15 | /* 16 | * GET articles API. 17 | */ 18 | 19 | exports.list = (req, res, next) => { 20 | req.collections.articles.find({}).toArray((error, articles) => { 21 | if (error) return next(error) 22 | res.send({articles: articles}) 23 | }) 24 | } 25 | 26 | /* 27 | * POST article API. 28 | */ 29 | 30 | exports.add = (req, res, next) => { 31 | if (!req.body.article) return next(new Error('No article payload.')) 32 | let article = req.body.article 33 | article.published = false 34 | req.collections.articles.insert(article, (error, articleResponse) => { 35 | if (error) return next(error) 36 | res.send(articleResponse) 37 | }) 38 | } 39 | 40 | /* 41 | * PUT article API. 42 | */ 43 | 44 | exports.edit = (req, res, next) => { 45 | if (!req.params.id) return next(new Error('No article ID.')) 46 | req.collections.articles.updateById(req.params.id, {$set: req.body.article}, (error, count) => { 47 | if (error) return next(error) 48 | res.send({affectedCount: count}) 49 | }) 50 | } 51 | 52 | /* 53 | * DELETE article API. 54 | */ 55 | 56 | exports.del = (req, res, next) => { 57 | if (!req.params.id) return next(new Error('No article ID.')) 58 | req.collections.articles.removeById(req.params.id, (error, count) => { 59 | if (error) return next(error) 60 | res.send({affectedCount: count}) 61 | }) 62 | } 63 | 64 | /* 65 | * GET article POST page. 66 | */ 67 | 68 | exports.post = (req, res, next) => { 69 | if (!req.body.title) { res.render('post') } 70 | } 71 | 72 | /* 73 | * POST article POST page. 74 | */ 75 | 76 | exports.postArticle = (req, res, next) => { 77 | if (!req.body.title || !req.body.slug || !req.body.text) { 78 | return res.render('post', {error: 'Fill title, slug and text.'}) 79 | } 80 | const article = { 81 | title: req.body.title, 82 | slug: req.body.slug, 83 | text: req.body.text, 84 | published: false 85 | } 86 | req.collections.articles.insert(article, (error, articleResponse) => { 87 | if (error) return next(error) 88 | res.render('post', {error: 'Article was added. Publish it on Admin page.'}) 89 | }) 90 | } 91 | 92 | /* 93 | * GET admin page. 94 | */ 95 | 96 | exports.admin = (req, res, next) => { 97 | req.collections.articles.find({}, {sort: {_id: -1}}).toArray((error, articles) => { 98 | if (error) return next(error) 99 | res.render('admin', {articles: articles}) 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /code/ch5/blog-express/routes/index.js: -------------------------------------------------------------------------------- 1 | exports.article = require('./article') 2 | exports.user = require('./user') 3 | 4 | /* 5 | * GET home page. 6 | */ 7 | 8 | exports.index = (req, res, next) => { 9 | req.collections.articles 10 | .find({published: true}, {sort: {_id: -1}}) 11 | .toArray((error, articles) => { 12 | if (error) return next(error) 13 | res.render('index', {articles: articles}) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /code/ch5/blog-express/routes/user.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET users listing. 4 | */ 5 | 6 | exports.list = (req, res, next) => { 7 | res.send('respond with a resource') 8 | } 9 | 10 | /* 11 | * GET login page. 12 | */ 13 | 14 | exports.login = (req, res, next) => { 15 | res.render('login') 16 | } 17 | 18 | /* 19 | * GET logout route. 20 | */ 21 | 22 | exports.logout = (req, res, next) => { 23 | res.redirect('/') 24 | } 25 | 26 | /* 27 | * POST authenticate route. 28 | */ 29 | 30 | exports.authenticate = (req, res, next) => { 31 | res.redirect('/admin') 32 | } 33 | -------------------------------------------------------------------------------- /code/ch5/blog-express/seed.sh: -------------------------------------------------------------------------------- 1 | mongoimport --host=127.0.0.1 --db blog --collection users --file ./db//users.json --jsonArray 2 | mongoimport --host=127.0.0.1 --db blog --collection articles --file ./db/articles.json --jsonArray 3 | -------------------------------------------------------------------------------- /code/ch5/blog-express/tests/index.js: -------------------------------------------------------------------------------- 1 | const boot = require('../app').boot 2 | const shutdown = require('../app').shutdown 3 | const port = require('../app').port 4 | const superagent = require('superagent') 5 | const expect = require('expect.js') 6 | 7 | // TODO: seed from the test and then clean up 8 | const seedArticles = require('../db/articles.json') 9 | // const seedUsers = require('../db/users.json') 10 | 11 | describe('server', () => { 12 | before(() => { 13 | boot() 14 | }) 15 | 16 | describe('homepage', () => { 17 | it('should respond to GET', (done) => { 18 | superagent 19 | .get(`http://localhost:${port}`) 20 | .end((error, res) => { 21 | expect(error).to.be(null) 22 | expect(res.status).to.equal(200) 23 | done() 24 | }) 25 | }) 26 | it('should contain posts', (done) => { 27 | superagent 28 | .get(`http://localhost:${port}`) 29 | .end((error, res) => { 30 | expect(error).to.be(null) 31 | expect(res.text).to.be.ok 32 | seedArticles.forEach((item, index, list) => { 33 | if (item.published) { 34 | expect(res.text).to.contain(`

${item.title}`) 35 | } else { 36 | expect(res.text).not.to.contain(`

${item.title}`) 37 | } 38 | // console.log(item.title, res.text) 39 | }) 40 | done() 41 | }) 42 | }) 43 | }) 44 | 45 | describe('article page', () => { 46 | it('should display text or 401', (done) => { 47 | let n = seedArticles.length 48 | seedArticles.forEach((item, index, list) => { 49 | superagent 50 | .get(`http://localhost:${port}/articles/${seedArticles[index].slug}`) 51 | .end((error, res) => { 52 | if (item.published) { 53 | expect(error).to.be(null) 54 | expect(res.text).to.contain(seedArticles[index].text) 55 | } else { 56 | expect(error).to.be.ok 57 | expect(res.status).to.be(401) 58 | } 59 | // console.log(item.title) 60 | if (index + 1 === n) { 61 | done() 62 | } 63 | }) 64 | }) 65 | }) 66 | }) 67 | 68 | after(() => { 69 | shutdown() 70 | }) 71 | }) 72 | -------------------------------------------------------------------------------- /code/ch5/blog-express/views/admin.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'admin' 4 | block content 5 | div.admin 6 | if (articles.length === 0 ) 7 | p 8 | | Nothing to display. Add a new 9 | a(href="/post") article 10 | |. 11 | else 12 | 13 | table.table.table-stripped 14 | thead 15 | tr 16 | th(colspan="2") Actions 17 | th Post Title 18 | tbody 19 | each article, index in articles 20 | tr(data-id=`${article._id}`, class=(!article.published)?'unpublished':'') 21 | td.action 22 | button.btn.btn-danger.btn-sm.remove(type="button") 23 | span.glyphicon.glyphicon-remove(title="Remove") 24 | td.action 25 | button.btn.btn-default.btn-sm.publish(type="button") 26 | span.glyphicon(class=(article.published)?"glyphicon-pause":"glyphicon-play", title=(article.published)?"Unpublish":"Publish") 27 | td= article.title 28 | script(type="text/javascript", src="js/admin.js") 29 | -------------------------------------------------------------------------------- /code/ch5/blog-express/views/article.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | p 5 | h1= title 6 | p= text -------------------------------------------------------------------------------- /code/ch5/blog-express/views/includes/menu.pug: -------------------------------------------------------------------------------- 1 | .menu 2 | ul.nav.nav-pills 3 | li(class=(menu === 'index')?'active':'') 4 | a(href='/') Home 5 | if (admin || menu ==='admin' || menu==='post') 6 | li(class=(menu === 'post')?'active':'') 7 | a(href="/post") Post 8 | li(class=(menu === 'admin')?'active':'') 9 | a(href="/admin") Admin 10 | li 11 | a(href="/logout") Log out 12 | else 13 | li(class=(menu === 'login')?'active':'') 14 | a(href='/login') Log in -------------------------------------------------------------------------------- /code/ch5/blog-express/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block page 4 | - var menu = 'index' 5 | block content 6 | if (articles.length === 0) 7 | | There's no published content yet. 8 | a(href="/login") Log in 9 | | to post and publish. 10 | else 11 | each article, index in articles 12 | div 13 | h2 14 | a(href=`/articles/${article.slug}`)= article.title -------------------------------------------------------------------------------- /code/ch5/blog-express/views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= appTitle 5 | script(type="text/javascript", src="/js/jquery-2.0.3.min.js") 6 | link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap.min.css") 7 | link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap-theme.min.css") 8 | link(rel="stylesheet", href="/css/style.css") 9 | script(type="text/javascript", src="/css/bootstrap-3.0.2/js/bootstrap.min.js") 10 | script(type="text/javascript", src="/js/blog.js") 11 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 12 | body 13 | #wrap 14 | .container 15 | h1.page-header= appTitle 16 | p.lead Welcome to example from Express.js Experience by  17 | a(href="http://twitter.com/azat_co") @azatmardan 18 | |. Please enjoy. 19 | block page 20 | block header 21 | div 22 | include includes/menu 23 | block alert 24 | div.alert.alert-warning.hidden 25 | .content 26 | block content 27 | block footer 28 | footer 29 | .container 30 | p 31 | | Copyright © 2018 | Issues? Submit to 32 | a(href="https://github.com/azat-co/blog-express/issues") GitHub 33 | | . -------------------------------------------------------------------------------- /code/ch5/blog-express/views/login.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'login' 4 | block content 5 | .col-md-4.col-md-offset-4 6 | h2 Log in 7 | div= error 8 | div 9 | form(action="/login", method="POST") 10 | p 11 | input.form-control(name="email", type="text", placeholder="hi@node.university") 12 | p 13 | input.form-control(name="password", type="password", placeholder="***") 14 | p 15 | button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in 16 | -------------------------------------------------------------------------------- /code/ch5/blog-express/views/post.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'post' 4 | block content 5 | 6 | h2 Post an Article 7 | div= error 8 | div.col-md-8 9 | form(action="/post", method="POST", role="form") 10 | div.form-group 11 | label(for="title") Title 12 | input#title.form-control(name="title", type="text", placeholder="JavaScript is good") 13 | div.form-group 14 | label(for="slug") Slug 15 | input#slug.form-control(name="slug", type="text", placeholder="js-good") 16 | span.help-block This string will be used in the URL. 17 | div.form-group 18 | label(for="text") Text 19 | textarea#text.form-control(rows="5", name="text", placeholder="Text") 20 | p 21 | button.btn.btn-primary(type="submit") Save 22 | -------------------------------------------------------------------------------- /code/ch5/mongo-examples/mongo-native-insert.js: -------------------------------------------------------------------------------- 1 | const mongo = require('mongodb') 2 | const dbHost = '127.0.0.1' 3 | const dbPort = 27017 4 | 5 | const {Db, Server} = mongo 6 | const db = new Db('local', 7 | new Server(dbHost, dbPort), 8 | {safe: true} 9 | ) 10 | 11 | db.open((error, dbConnection) => { 12 | if (error) { 13 | console.error(error) 14 | return process.exit(1) 15 | } 16 | console.log('db state: ', db._state) 17 | const item = { 18 | name: 'Azat' 19 | } 20 | dbConnection.collection('messages').insert(item, (error, document) => { 21 | if (error) { 22 | console.error(error) 23 | return process.exit(1) 24 | } 25 | console.info('created/inserted: ', document) 26 | db.close() 27 | process.exit(0) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /code/ch5/mongo-examples/mongo-native.js: -------------------------------------------------------------------------------- 1 | const mongo = require('mongodb') 2 | const dbHost = '127.0.0.1' 3 | const dbPort = 27017 4 | 5 | const {Db, Server} = mongo 6 | const db = new Db('local', new Server(dbHost, dbPort), {safe: true}) 7 | 8 | db.open((error, dbConnection) => { 9 | if (error) { 10 | console.error(error) 11 | process.exit(1) 12 | } 13 | console.log('db state: ', db._state) 14 | dbConnection.collection('messages').findOne({}, (error, item) => { 15 | if (error) { 16 | console.error(error) 17 | process.exit(1) 18 | } 19 | console.info('findOne: ', item) 20 | item.text = 'hi' 21 | var id = item._id.toString() // we can store ID in a string 22 | console.info('before saving: ', item) 23 | dbConnection 24 | .collection('messages') 25 | .save(item, (error, document) => { 26 | if (error) { 27 | console.error(error) 28 | return process.exit(1) 29 | } 30 | console.info('save: ', document) 31 | dbConnection.collection('messages') 32 | .find({_id: new mongo.ObjectID(id)}) 33 | .toArray((error, documents) => { 34 | if (error) { 35 | console.error(error) 36 | return process.exit(1) 37 | } 38 | console.info('find: ', documents) 39 | db.close() 40 | process.exit(0) 41 | } 42 | ) 43 | }) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /code/ch5/mongo-examples/mongoskin.js: -------------------------------------------------------------------------------- 1 | const mongoskin = require('mongoskin') 2 | const { toObjectID } = mongoskin.helper 3 | const dbHost = '127.0.0.1' 4 | const dbPort = 27017 5 | 6 | const db = mongoskin.db(`mongodb://${dbHost}:${dbPort}/local`) 7 | 8 | db.bind('messages').bind({ 9 | findOneAndAddText: function (text, fn) { // No fat arrow fn because we need to let bind pass the collection to use this on the next line... this can be replaced with db.messages too 10 | this.findOne({}, (error, document) => { 11 | if (error) { 12 | console.error(error) 13 | return process.exit(1) 14 | } 15 | console.info('findOne: ', document) 16 | document.text = text 17 | var id = document._id.toString() // We can store ID in a string 18 | console.info('before saving: ', document) 19 | this.save(document, (error, count) => { 20 | if (error) { 21 | console.error(error) 22 | return process.exit(1) 23 | } 24 | console.info('save: ', count) 25 | return fn(count, id) 26 | }) 27 | }) 28 | } 29 | }) 30 | 31 | db.messages.findOneAndAddText('hi', (count, id) => { 32 | db.messages.find({ 33 | _id: toObjectID(id) 34 | }).toArray((error, documents) => { 35 | if (error) { 36 | console.error(error) 37 | return process.exit(1) 38 | } 39 | console.info('find: ', documents) 40 | db.close() 41 | process.exit(0) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /code/ch5/mongo-examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongodb-examples", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "mongo-native-insert.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Azat Mardan (http://azat.co/)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "mongodb": "2.2.33", 14 | "mongoskin": "2.1.0" 15 | }, 16 | "devDependencies": { 17 | "standard": "10.0.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = list 2 | MOCHA_OPTS = --ui bdd -c 3 | 4 | db: 5 | echo Seeding blog-test ***************************************************** 6 | ./seed.sh 7 | test: 8 | clear 9 | 10 | echo Starting test ********************************************************* 11 | ./node_modules/mocha/bin/mocha \ 12 | --reporter $(REPORTER) \ 13 | $(MOCHA_OPTS) \ 14 | tests/*.js 15 | echo Ending test 16 | start: 17 | TWITTER_CONSUMER_KEY=AAAAAAAAAAAAAAAAAAAAA \ 18 | TWITTER_CONSUMER_SECRET=BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB \ 19 | NODE_ENV=development \ 20 | node app.js 21 | 22 | .PHONY: test db start -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/db/articles.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "title": "Node is a movement", 3 | "slug": "node-movement", 4 | "published": true, 5 | "text": "In one random deployment, it is often assumed that the number of scattered sensors are more than that required by the critical sensor density. Otherwise, complete area coverage may not be guaranteed in this deployment, and some coverage holes may exist. Besides using more sensors to improve coverage, mobile sensor nodes can be used to improve network coverage. Mobile sensor nodes are equipped with mobile platforms and can move around after initial deployment. Although a mobile sensor node normally is more expensive than its stationary compeer, it can serve many functionalities and improve network coverage. The design of a node movement strategy usually needs to address the following question:Where to move and how to efficiently move mobile modes so that area coverage can be optimized? \nIn different network scenarios, the objectives of nodes’ movement are different. In a hybrid network consisting of both stationary and mobile sensor nodes, the objective is mainly to relocate mobile nodes to heal the coverage holes caused by the stationary nodes. In a mobile network consisting of only mobile nodes, the primary objective is to maximize the coverage of these mobile nodes, and in event monitoring scenario, an important objective is to dispatch mobile nodes to the sources of events for better event coverage." 6 | }, { 7 | "title": "Express.js Experience", 8 | "slug": "express-experience", 9 | "text": "Work in progress", 10 | "published": false 11 | },{ 12 | "title": "Node.js FUNdamentals: A Concise Overview of The Main Concepts", 13 | "slug": "node-fundamentals", 14 | "published": true, 15 | "text": "Node.js is a highly efficient and scalable non-blocking I/O platform that was build on top of Google Chrome V8 engine and its ECMAScript. This means that most front-end JavaScript (another implementation of ECMAScript) objects, functions and methods are available in Node.js. Please refer to JavaScript FUNdamentals if you need a refresher on JS-specific basics." 16 | }] -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/db/users.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "email": "hi@azat.co", 3 | "admin": true, 4 | "password": "1" 5 | }] -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-everyauth", 3 | "version": "0.0.7", 4 | "private": true, 5 | "scripts": { 6 | "start": "make start", 7 | "seed": "sh ./seed.sh", 8 | "test": "make test", 9 | "st": "standard app.js && standard tests/index.js && standard routes/*" 10 | }, 11 | "author": "Azat Mardan (http://azat.co/)", 12 | "license": "MIT", 13 | "dependencies": { 14 | "body-parser": "1.18.2", 15 | "cookie-parser": "1.4.3", 16 | "errorhandler": "1.5.0", 17 | "everyauth": "0.4.9", 18 | "express": "4.16.2", 19 | "express-session": "1.15.6", 20 | "method-override": "2.3.10", 21 | "mongodb": "2.2.33", 22 | "mongoskin": "2.1.0", 23 | "morgan": "1.9.1", 24 | "pug": "2.0.0-rc.4", 25 | "serve-favicon": "2.4.5", 26 | "stylus": "0.54.5" 27 | }, 28 | "devDependencies": { 29 | "expect.js": "0.3.1", 30 | "mocha": "4.0.1", 31 | "standard": "10.0.3", 32 | "superagent": "3.8.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch6/blog-everyauth/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch6/blog-everyauth/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch6/blog-everyauth/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/public/css/style.css: -------------------------------------------------------------------------------- 1 | #wrap { 2 | min-height: 100%; 3 | height: auto; 4 | margin: 0 auto -60px; 5 | padding: 0 0 60px; 6 | } 7 | footer { 8 | height: 60px; 9 | } 10 | html, 11 | body { 12 | height: 100%; 13 | } 14 | .content { 15 | margin-top: 40px; 16 | margin-bottom: 40px; 17 | } 18 | .glyphicon-remove, 19 | .glyphicon-pause, 20 | .glyphicon-play { 21 | cursor: pointer; 22 | } 23 | tr.unpublished { 24 | color: #cec1c2; 25 | } 26 | td.action { 27 | width: 20px; 28 | } 29 | .alert { 30 | margin-top: 20px; 31 | } 32 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/public/css/style.styl: -------------------------------------------------------------------------------- 1 | #wrap 2 | min-height: 100%; 3 | height: auto; 4 | margin: 0 auto -60px; 5 | padding: 0 0 60px; 6 | 7 | footer 8 | height: 60px; 9 | 10 | html, body 11 | height: 100%; 12 | .content 13 | margin-top 40px 14 | margin-bottom 40px 15 | .glyphicon-remove, .glyphicon-pause, .glyphicon-play 16 | cursor pointer 17 | tr.unpublished 18 | color #cec1c2 19 | td.action 20 | width 20px 21 | .alert 22 | margin-top 20px 23 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/public/js/admin.js: -------------------------------------------------------------------------------- 1 | $.ajaxSetup({ 2 | xhrFields: {withCredentials: true}, 3 | error: function(xhr, status, error) { 4 | $('.alert').removeClass('hidden'); 5 | $('.alert').html("Status: " + status + ", error: " + error); 6 | } 7 | }); 8 | 9 | var findTr = function(event) { 10 | var target = event.srcElement || event.target; 11 | var $target = $(target); 12 | var $tr = $target.parents('tr'); 13 | return $tr; 14 | }; 15 | 16 | var remove = function(event) { 17 | var $tr = findTr(event); 18 | var id = $tr.data('id'); 19 | $.ajax({ 20 | url: '/api/articles/' + id, 21 | type: 'DELETE', 22 | success: function(data, status, xhr) { 23 | $('.alert').addClass('hidden'); 24 | $tr.remove(); 25 | } 26 | }) 27 | }; 28 | 29 | var update = function(event) { 30 | var $tr = findTr(event); 31 | $tr.find('button').attr('disabled', 'disabled'); 32 | var data = { 33 | published: $tr.hasClass('unpublished') 34 | }; 35 | var id = $tr.attr('data-id'); 36 | $.ajax({ 37 | url: '/api/articles/' + id, 38 | type: 'PUT', 39 | contentType: 'application/json', 40 | data: JSON.stringify({article: data}), 41 | success: function(dataResponse, status, xhr) { 42 | $tr.find('button').removeAttr('disabled'); 43 | $('.alert').addClass('hidden'); 44 | if (data.published) { 45 | $tr.removeClass('unpublished').find('.glyphicon-play').removeClass('glyphicon-play').addClass('glyphicon-pause'); 46 | } else { 47 | $tr.addClass('unpublished').find('.glyphicon-pause').removeClass('glyphicon-pause').addClass('glyphicon-play'); 48 | } 49 | } 50 | }) 51 | }; 52 | 53 | $(document).ready(function(){ 54 | var $element = $('.admin tbody'); 55 | $element.on('click', 'button.remove', remove); 56 | $element.on('click', 'button', update); 57 | }) -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/public/js/blog.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch6/blog-everyauth/public/js/blog.js -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/readme.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | 3 | * Markdown 4 | * Edit post (modal, fetch data via ajax) 5 | * Photo upload 6 | * salt password -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/routes/article.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET article page. 4 | */ 5 | 6 | exports.show = (req, res, next) => { 7 | if (!req.params.slug) return next(new Error('No article slug.')) 8 | req.collections.articles.findOne({slug: req.params.slug}, (error, article) => { 9 | if (error) return next(error) 10 | if (!article.published) return res.status(401).send() 11 | res.render('article', article) 12 | }) 13 | } 14 | 15 | /* 16 | * GET articles API. 17 | */ 18 | 19 | exports.list = (req, res, next) => { 20 | req.collections.articles.find({}).toArray((error, articles) => { 21 | if (error) return next(error) 22 | res.send({articles: articles}) 23 | }) 24 | } 25 | 26 | /* 27 | * POST article API. 28 | */ 29 | 30 | exports.add = (req, res, next) => { 31 | if (!req.body.article) return next(new Error('No article payload.')) 32 | let article = req.body.article 33 | article.published = false 34 | req.collections.articles.insert(article, (error, articleResponse) => { 35 | if (error) return next(error) 36 | res.send(articleResponse) 37 | }) 38 | } 39 | 40 | /* 41 | * PUT article API. 42 | */ 43 | 44 | exports.edit = (req, res, next) => { 45 | if (!req.params.id) return next(new Error('No article ID.')) 46 | req.collections.articles.updateById(req.params.id, {$set: req.body.article}, (error, count) => { 47 | if (error) return next(error) 48 | res.send({affectedCount: count}) 49 | }) 50 | } 51 | 52 | /* 53 | * DELETE article API. 54 | */ 55 | 56 | exports.del = (req, res, next) => { 57 | if (!req.params.id) return next(new Error('No article ID.')) 58 | req.collections.articles.removeById(req.params.id, (error, count) => { 59 | if (error) return next(error) 60 | res.send({affectedCount: count}) 61 | }) 62 | } 63 | 64 | /* 65 | * GET article POST page. 66 | */ 67 | 68 | exports.post = (req, res, next) => { 69 | if (!req.body.title) { res.render('post') } 70 | } 71 | 72 | /* 73 | * POST article POST page. 74 | */ 75 | 76 | exports.postArticle = (req, res, next) => { 77 | if (!req.body.title || !req.body.slug || !req.body.text) { 78 | return res.render('post', {error: 'Fill title, slug and text.'}) 79 | } 80 | const article = { 81 | title: req.body.title, 82 | slug: req.body.slug, 83 | text: req.body.text, 84 | published: false 85 | } 86 | req.collections.articles.insert(article, (error, articleResponse) => { 87 | if (error) return next(error) 88 | res.render('post', {error: 'Article was added. Publish it on Admin page.'}) 89 | }) 90 | } 91 | 92 | /* 93 | * GET admin page. 94 | */ 95 | 96 | exports.admin = (req, res, next) => { 97 | req.collections.articles.find({}, {sort: {_id: -1}}).toArray((error, articles) => { 98 | if (error) return next(error) 99 | res.render('admin', {articles: articles}) 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/routes/index.js: -------------------------------------------------------------------------------- 1 | exports.article = require('./article') 2 | exports.user = require('./user') 3 | 4 | /* 5 | * GET home page. 6 | */ 7 | 8 | exports.index = (req, res, next) => { 9 | req.collections.articles 10 | .find({published: true}, {sort: {_id: -1}}) 11 | .toArray((error, articles) => { 12 | if (error) return next(error) 13 | res.render('index', {articles: articles}) 14 | }) 15 | } -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/routes/user.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET users listing. 4 | */ 5 | 6 | exports.list = function (req, res) { 7 | res.send('respond with a resource') 8 | } 9 | 10 | /* 11 | * GET login page. 12 | */ 13 | 14 | exports.login = function (req, res, next) { 15 | res.render('login') 16 | } 17 | 18 | /* 19 | * GET logout route. 20 | */ 21 | 22 | exports.logout = function (req, res, next) { 23 | req.session.destroy() 24 | res.redirect('/') 25 | } 26 | 27 | /* 28 | * POST authenticate route. 29 | */ 30 | 31 | exports.authenticate = function (req, res, next) { 32 | if (!req.body.email || !req.body.password) { return res.render('login', {error: 'Please enter your email and password.'}) } 33 | req.collections.users.findOne({ 34 | email: req.body.email, 35 | password: req.body.password 36 | }, function (error, user) { 37 | if (error) return next(error) 38 | if (!user) return res.render('login', {error: 'Incorrect email&password combination.'}) 39 | req.session.user = user 40 | req.session.admin = user.admin 41 | res.redirect('/admin') 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/seed.sh: -------------------------------------------------------------------------------- 1 | mongoimport --db blog --collection users --file ./db/users.json --jsonArray 2 | mongoimport --db blog --collection articles --file ./db/articles.json --jsonArray 3 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/tests/index.js: -------------------------------------------------------------------------------- 1 | const boot = require('../app').boot 2 | const shutdown = require('../app').shutdown 3 | const port = require('../app').port 4 | const superagent = require('superagent') 5 | const expect = require('expect.js') 6 | 7 | // TODO: seed from the test and then clean up 8 | const seedArticles = require('../db/articles.json') 9 | // const seedUsers = require('../db/users.json') 10 | 11 | describe('server', function () { 12 | before(function () { 13 | boot() 14 | }) 15 | 16 | describe('homepage', function () { 17 | it('should respond to GET', function (done) { 18 | superagent 19 | .get(`http://localhost:${port}`) 20 | .end((error, res) => { 21 | expect(error).to.be(null) 22 | expect(res.status).to.equal(200) 23 | done() 24 | }) 25 | }) 26 | it('should contain posts', function (done) { 27 | superagent 28 | .get(`http://localhost:${port}`) 29 | .end((error, res) => { 30 | expect(error).to.be(null) 31 | expect(res.text).to.be.ok 32 | seedArticles.forEach(function (item, index, list) { 33 | if (item.published) { 34 | expect(res.text).to.contain(`

${item.title}`) 35 | } else { 36 | expect(res.text).not.to.contain(`

${item.title}`) 37 | } 38 | // console.log(item.title, res.text) 39 | }) 40 | done() 41 | }) 42 | }) 43 | }) 44 | 45 | describe('article page', function () { 46 | it('should display text or 401', function (done) { 47 | let n = seedArticles.length 48 | seedArticles.forEach(function (item, index, list) { 49 | superagent 50 | .get(`http://localhost:${port}/articles/${seedArticles[index].slug}`) 51 | .end((error, res) => { 52 | if (item.published) { 53 | expect(error).to.be(null) 54 | expect(res.text).to.contain(seedArticles[index].text) 55 | } else { 56 | expect(error).to.be.ok 57 | expect(res.status).to.be(401) 58 | } 59 | // console.log(item.title) 60 | if (index + 1 === n) { 61 | done() 62 | } 63 | }) 64 | }) 65 | }) 66 | }) 67 | after(function () { 68 | shutdown() 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/views/admin.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'admin' 4 | block content 5 | div.admin 6 | if (articles.length === 0 ) 7 | p 8 | | Nothing to display. Add a new 9 | a(href="/post") article 10 | |. 11 | else 12 | 13 | table.table.table-stripped 14 | thead 15 | tr 16 | th(colspan="2") Actions 17 | th Post Title 18 | tbody 19 | each article, index in articles 20 | tr(data-id=`${article._id}`, class=(!article.published)?'unpublished':'') 21 | td.action 22 | button.btn.btn-danger.btn-sm.remove(type="button") 23 | span.glyphicon.glyphicon-remove(title="Remove") 24 | td.action 25 | button.btn.btn-default.btn-sm.publish(type="button") 26 | span.glyphicon(class=(article.published)?"glyphicon-pause":"glyphicon-play", title=(article.published)?"Unpublish":"Publish") 27 | td= article.title 28 | script(type="text/javascript", src="js/admin.js") 29 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/views/article.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | p 5 | h1= title 6 | p= text -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/views/includes/menu.pug: -------------------------------------------------------------------------------- 1 | .menu 2 | ul.nav.nav-pills 3 | li(class=(menu === "index") ? "active" : "") 4 | a(href="/") Home 5 | if (admin) 6 | li(class=(menu === "post") ? "active" : "") 7 | a(href="/post") Post 8 | li(class=(menu === "admin") ? "active" : "") 9 | a(href="/admin") Admin 10 | li 11 | a(href="/logout") Log out 12 | else 13 | li(class=(menu === "login") ? "active" : "") 14 | a(href="/login") Log in 15 | li 16 | a(href='/auth/twitter') Sign in with Twitter -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block page 4 | - var menu = 'index' 5 | block content 6 | if (articles.length === 0) 7 | | There's no published content yet. 8 | a(href="/login") Log in 9 | | to post and publish. 10 | else 11 | each article, index in articles 12 | div 13 | h2 14 | a(href=`/articles/${article.slug}`)= article.title -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= appTitle 5 | script(type="text/javascript", src="/js/jquery-2.0.3.min.js") 6 | link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap.min.css") 7 | link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap-theme.min.css") 8 | link(rel="stylesheet", href="/css/style.css") 9 | script(type="text/javascript", src="/css/bootstrap-3.0.2/js/bootstrap.min.js") 10 | script(type="text/javascript", src="/js/blog.js") 11 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 12 | body 13 | #wrap 14 | .container 15 | h1.page-header= appTitle 16 | p.lead Welcome to example from Express.js Experience by  17 | a(href="http://twitter.com/azat_co") @azatmardan 18 | |. Please enjoy. 19 | block page 20 | block header 21 | div 22 | include includes/menu 23 | block alert 24 | div.alert.alert-warning.hidden 25 | .content 26 | block content 27 | block footer 28 | footer 29 | .container 30 | p 31 | | Copyright © 2018 | Issues? Submit to 32 | a(href="https://github.com/azat-co/blog-express/issues") GitHub 33 | | . -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/views/login.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'login' 4 | block content 5 | .col-md-4.col-md-offset-4 6 | h2 Log in 7 | div= error 8 | div 9 | a(href='/auth/twitter') Sign in with Twitter 10 | div 11 | form(action="/login", method="POST") 12 | p 13 | input.form-control(name="email", type="text", placeholder="hi@azat.co") 14 | p 15 | input.form-control(name="password", type="password", placeholder="***") 16 | p 17 | button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in 18 | -------------------------------------------------------------------------------- /code/ch6/blog-everyauth/views/post.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'post' 4 | block content 5 | 6 | h2 Post an Article 7 | div= error 8 | div.col-md-8 9 | form(action="/post", method="POST", role="form") 10 | div.form-group 11 | label(for="title") Title 12 | input#title.form-control(name="title", type="text", placeholder="JavaScript is good") 13 | div.form-group 14 | label(for="slug") Slug 15 | input#slug.form-control(name="slug", type="text", placeholder="js-good") 16 | span.help-block This string will be used in the URL. 17 | div.form-group 18 | label(for="text") Text 19 | textarea#text.form-control(rows="5", name="text", placeholder="Text") 20 | p 21 | button.btn.btn-primary(type="submit") Save 22 | -------------------------------------------------------------------------------- /code/ch6/blog-password/Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = list 2 | MOCHA_OPTS = --ui bdd -c 3 | 4 | db: 5 | echo Seeding blog-test ***************************************************** 6 | ./seed.sh 7 | test: 8 | clear 9 | 10 | echo Starting test ********************************************************* 11 | ./node_modules/mocha/bin/mocha \ 12 | --reporter $(REPORTER) \ 13 | $(MOCHA_OPTS) \ 14 | tests/*.js 15 | echo Ending test 16 | 17 | .PHONY: test db -------------------------------------------------------------------------------- /code/ch6/blog-password/db/articles.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "title": "Node is a movement", 3 | "slug": "node-movement", 4 | "published": true, 5 | "text": "In one random deployment, it is often assumed that the number of scattered sensors are more than that required by the critical sensor density. Otherwise, complete area coverage may not be guaranteed in this deployment, and some coverage holes may exist. Besides using more sensors to improve coverage, mobile sensor nodes can be used to improve network coverage. Mobile sensor nodes are equipped with mobile platforms and can move around after initial deployment. Although a mobile sensor node normally is more expensive than its stationary compeer, it can serve many functionalities and improve network coverage. The design of a node movement strategy usually needs to address the following question:Where to move and how to efficiently move mobile modes so that area coverage can be optimized? \nIn different network scenarios, the objectives of nodes’ movement are different. In a hybrid network consisting of both stationary and mobile sensor nodes, the objective is mainly to relocate mobile nodes to heal the coverage holes caused by the stationary nodes. In a mobile network consisting of only mobile nodes, the primary objective is to maximize the coverage of these mobile nodes, and in event monitoring scenario, an important objective is to dispatch mobile nodes to the sources of events for better event coverage." 6 | }, { 7 | "title": "Express.js Experience", 8 | "slug": "express-experience", 9 | "text": "Work in progress", 10 | "published": false 11 | },{ 12 | "title": "Node.js FUNdamentals: A Concise Overview of The Main Concepts", 13 | "slug": "node-fundamentals", 14 | "published": true, 15 | "text": "Node.js is a highly efficient and scalable non-blocking I/O platform that was build on top of Google Chrome V8 engine and its ECMAScript. This means that most front-end JavaScript (another implementation of ECMAScript) objects, functions and methods are available in Node.js. Please refer to JavaScript FUNdamentals if you need a refresher on JS-specific basics." 16 | }] -------------------------------------------------------------------------------- /code/ch6/blog-password/db/users.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "email": "hi@azat.co", 3 | "admin": true, 4 | "password": "1" 5 | }] -------------------------------------------------------------------------------- /code/ch6/blog-password/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-password", 3 | "version": "0.0.7", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js", 7 | "seed": "sh ./seed.sh", 8 | "test": "make test", 9 | "st": "standard app.js && standard tests/index.js && standard routes/*" 10 | }, 11 | "dependencies": { 12 | "body-parser": "1.18.2", 13 | "cookie-parser": "1.4.3", 14 | "errorhandler": "1.5.0", 15 | "express": "4.16.2", 16 | "express-session": "1.15.6", 17 | "method-override": "2.3.10", 18 | "mongodb": "2.2.33", 19 | "mongoskin": "2.1.0", 20 | "morgan": "1.9.1", 21 | "pug": "2.0.0-rc.4", 22 | "serve-favicon": "2.4.5", 23 | "stylus": "0.54.5" 24 | }, 25 | "devDependencies": { 26 | "expect.js": "0.3.1", 27 | "mocha": "4.0.1", 28 | "standard": "10.0.3", 29 | "superagent": "3.8.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /code/ch6/blog-password/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch6/blog-password/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /code/ch6/blog-password/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch6/blog-password/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /code/ch6/blog-password/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch6/blog-password/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /code/ch6/blog-password/public/css/style.css: -------------------------------------------------------------------------------- 1 | #wrap { 2 | min-height: 100%; 3 | height: auto; 4 | margin: 0 auto -60px; 5 | padding: 0 0 60px; 6 | } 7 | footer { 8 | height: 60px; 9 | } 10 | html, 11 | body { 12 | height: 100%; 13 | } 14 | .content { 15 | margin-top: 40px; 16 | margin-bottom: 40px; 17 | } 18 | .glyphicon-remove, 19 | .glyphicon-pause, 20 | .glyphicon-play { 21 | cursor: pointer; 22 | } 23 | tr.unpublished { 24 | color: #cec1c2; 25 | } 26 | td.action { 27 | width: 20px; 28 | } 29 | .alert { 30 | margin-top: 20px; 31 | } 32 | -------------------------------------------------------------------------------- /code/ch6/blog-password/public/css/style.styl: -------------------------------------------------------------------------------- 1 | #wrap 2 | min-height: 100%; 3 | height: auto; 4 | margin: 0 auto -60px; 5 | padding: 0 0 60px; 6 | 7 | footer 8 | height: 60px; 9 | 10 | html, body 11 | height: 100%; 12 | .content 13 | margin-top 40px 14 | margin-bottom 40px 15 | .glyphicon-remove, .glyphicon-pause, .glyphicon-play 16 | cursor pointer 17 | tr.unpublished 18 | color #cec1c2 19 | td.action 20 | width 20px 21 | .alert 22 | margin-top 20px 23 | -------------------------------------------------------------------------------- /code/ch6/blog-password/public/js/admin.js: -------------------------------------------------------------------------------- 1 | $.ajaxSetup({ 2 | xhrFields: {withCredentials: true}, 3 | error: function(xhr, status, error) { 4 | $('.alert').removeClass('hidden'); 5 | $('.alert').html("Status: " + status + ", error: " + error); 6 | } 7 | }); 8 | 9 | var findTr = function(event) { 10 | var target = event.srcElement || event.target; 11 | var $target = $(target); 12 | var $tr = $target.parents('tr'); 13 | return $tr; 14 | }; 15 | 16 | var remove = function(event) { 17 | var $tr = findTr(event); 18 | var id = $tr.data('id'); 19 | $.ajax({ 20 | url: '/api/articles/' + id, 21 | type: 'DELETE', 22 | success: function(data, status, xhr) { 23 | $('.alert').addClass('hidden'); 24 | $tr.remove(); 25 | } 26 | }) 27 | }; 28 | 29 | var update = function(event) { 30 | var $tr = findTr(event); 31 | $tr.find('button').attr('disabled', 'disabled'); 32 | var data = { 33 | published: $tr.hasClass('unpublished') 34 | }; 35 | var id = $tr.attr('data-id'); 36 | $.ajax({ 37 | url: '/api/articles/' + id, 38 | type: 'PUT', 39 | contentType: 'application/json', 40 | data: JSON.stringify({article: data}), 41 | success: function(dataResponse, status, xhr) { 42 | $tr.find('button').removeAttr('disabled'); 43 | $('.alert').addClass('hidden'); 44 | if (data.published) { 45 | $tr.removeClass('unpublished').find('.glyphicon-play').removeClass('glyphicon-play').addClass('glyphicon-pause'); 46 | } else { 47 | $tr.addClass('unpublished').find('.glyphicon-pause').removeClass('glyphicon-pause').addClass('glyphicon-play'); 48 | } 49 | } 50 | }) 51 | }; 52 | 53 | $(document).ready(function(){ 54 | var $element = $('.admin tbody'); 55 | $element.on('click', 'button.remove', remove); 56 | $element.on('click', 'button', update); 57 | }) -------------------------------------------------------------------------------- /code/ch6/blog-password/public/js/blog.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch6/blog-password/public/js/blog.js -------------------------------------------------------------------------------- /code/ch6/blog-password/readme.md: -------------------------------------------------------------------------------- 1 | # Blog with admin password page 2 | 3 | ``` 4 | npm i 5 | npm run seed 6 | make test 7 | npm start 8 | ``` -------------------------------------------------------------------------------- /code/ch6/blog-password/routes/article.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET article page. 4 | */ 5 | 6 | exports.show = (req, res, next) => { 7 | if (!req.params.slug) return next(new Error('No article slug.')) 8 | req.collections.articles.findOne({slug: req.params.slug}, (error, article) => { 9 | if (error) return next(error) 10 | if (!article.published) return res.status(401).send() 11 | res.render('article', article) 12 | }) 13 | } 14 | 15 | /* 16 | * GET articles API. 17 | */ 18 | 19 | exports.list = (req, res, next) => { 20 | req.collections.articles.find({}).toArray((error, articles) => { 21 | if (error) return next(error) 22 | res.send({articles: articles}) 23 | }) 24 | } 25 | 26 | /* 27 | * POST article API. 28 | */ 29 | 30 | exports.add = (req, res, next) => { 31 | if (!req.body.article) return next(new Error('No article payload.')) 32 | let article = req.body.article 33 | article.published = false 34 | req.collections.articles.insert(article, (error, articleResponse) => { 35 | if (error) return next(error) 36 | res.send(articleResponse) 37 | }) 38 | } 39 | 40 | /* 41 | * PUT article API. 42 | */ 43 | 44 | exports.edit = (req, res, next) => { 45 | if (!req.params.id) return next(new Error('No article ID.')) 46 | req.collections.articles.updateById(req.params.id, {$set: req.body.article}, (error, count) => { 47 | if (error) return next(error) 48 | res.send({affectedCount: count}) 49 | }) 50 | } 51 | 52 | /* 53 | * DELETE article API. 54 | */ 55 | 56 | exports.del = (req, res, next) => { 57 | if (!req.params.id) return next(new Error('No article ID.')) 58 | req.collections.articles.removeById(req.params.id, (error, count) => { 59 | if (error) return next(error) 60 | res.send({affectedCount: count}) 61 | }) 62 | } 63 | 64 | /* 65 | * GET article POST page. 66 | */ 67 | 68 | exports.post = (req, res, next) => { 69 | if (!req.body.title) { res.render('post') } 70 | } 71 | 72 | /* 73 | * POST article POST page. 74 | */ 75 | 76 | exports.postArticle = (req, res, next) => { 77 | if (!req.body.title || !req.body.slug || !req.body.text) { 78 | return res.render('post', {error: 'Fill title, slug and text.'}) 79 | } 80 | const article = { 81 | title: req.body.title, 82 | slug: req.body.slug, 83 | text: req.body.text, 84 | published: false 85 | } 86 | req.collections.articles.insert(article, (error, articleResponse) => { 87 | if (error) return next(error) 88 | res.render('post', {error: 'Article was added. Publish it on Admin page.'}) 89 | }) 90 | } 91 | 92 | /* 93 | * GET admin page. 94 | */ 95 | 96 | exports.admin = (req, res, next) => { 97 | req.collections.articles.find({}, {sort: {_id: -1}}).toArray((error, articles) => { 98 | if (error) return next(error) 99 | res.render('admin', {articles: articles}) 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /code/ch6/blog-password/routes/index.js: -------------------------------------------------------------------------------- 1 | exports.article = require('./article'); 2 | exports.user = require('./user'); 3 | 4 | /* 5 | * GET home page. 6 | */ 7 | 8 | exports.index = function(req, res, next){ 9 | req.collections.articles.find({published: true}, {sort: {_id:-1}}).toArray(function(error, articles){ 10 | if (error) return next(error); 11 | res.render('index', { articles: articles}); 12 | }) 13 | }; 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /code/ch6/blog-password/routes/user.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET users listing. 4 | */ 5 | 6 | exports.list = function (req, res) { 7 | res.send('respond with a resource') 8 | } 9 | 10 | /* 11 | * GET login page. 12 | */ 13 | 14 | exports.login = function (req, res, next) { 15 | res.render('login') 16 | } 17 | 18 | /* 19 | * GET logout route. 20 | */ 21 | 22 | exports.logout = function (req, res, next) { 23 | req.session.destroy() 24 | res.redirect('/') 25 | } 26 | 27 | /* 28 | * POST authenticate route. 29 | */ 30 | 31 | exports.authenticate = function (req, res, next) { 32 | if (!req.body.email || !req.body.password) { return res.render('login', {error: 'Please enter your email and password.'}) } 33 | req.collections.users.findOne({ 34 | email: req.body.email, 35 | password: req.body.password 36 | }, function (error, user) { 37 | if (error) return next(error) 38 | if (!user) return res.render('login', {error: 'Incorrect email&password combination.'}) 39 | req.session.user = user 40 | req.session.admin = user.admin 41 | res.redirect('/admin') 42 | }) 43 | } 44 | -------------------------------------------------------------------------------- /code/ch6/blog-password/seed.sh: -------------------------------------------------------------------------------- 1 | mongoimport --db blog --collection users --file ./db/users.json --jsonArray 2 | mongoimport --db blog --collection articles --file ./db/articles.json --jsonArray 3 | -------------------------------------------------------------------------------- /code/ch6/blog-password/tests/index.js: -------------------------------------------------------------------------------- 1 | const boot = require('../app').boot 2 | const shutdown = require('../app').shutdown 3 | const port = require('../app').port 4 | const superagent = require('superagent') 5 | const expect = require('expect.js') 6 | 7 | // TODO: seed from the test and then clean up 8 | const seedArticles = require('../db/articles.json') 9 | // const seedUsers = require('../db/users.json') 10 | 11 | describe('server', function () { 12 | before(function () { 13 | boot() 14 | }) 15 | 16 | describe('homepage', function () { 17 | it('should respond to GET', function (done) { 18 | superagent 19 | .get(`http://localhost:${port}`) 20 | .end((error, res) => { 21 | expect(error).to.be(null) 22 | expect(res.status).to.equal(200) 23 | done() 24 | }) 25 | }) 26 | it('should contain posts', function (done) { 27 | superagent 28 | .get(`http://localhost:${port}`) 29 | .end((error, res) => { 30 | expect(error).to.be(null) 31 | expect(res.text).to.be.ok 32 | seedArticles.forEach(function (item, index, list) { 33 | if (item.published) { 34 | expect(res.text).to.contain(`

${item.title}`) 35 | } else { 36 | expect(res.text).not.to.contain(`

${item.title}`) 37 | } 38 | // console.log(item.title, res.text) 39 | }) 40 | done() 41 | }) 42 | }) 43 | }) 44 | 45 | describe('article page', function () { 46 | it('should display text or 401', function (done) { 47 | let n = seedArticles.length 48 | seedArticles.forEach(function (item, index, list) { 49 | superagent 50 | .get(`http://localhost:${port}/articles/${seedArticles[index].slug}`) 51 | .end((error, res) => { 52 | if (item.published) { 53 | expect(error).to.be(null) 54 | expect(res.text).to.contain(seedArticles[index].text) 55 | } else { 56 | expect(error).to.be.ok 57 | expect(res.status).to.be(401) 58 | } 59 | // console.log(item.title) 60 | if (index + 1 === n) { 61 | done() 62 | } 63 | }) 64 | }) 65 | }) 66 | }) 67 | after(function () { 68 | shutdown() 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /code/ch6/blog-password/views/admin.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = "admin" 4 | block content 5 | div.admin 6 | if (articles.length === 0 ) 7 | p 8 | | Nothing to display. Add a new 9 | a(href="/post") article 10 | |. 11 | else 12 | 13 | table.table.table-stripped 14 | thead 15 | tr 16 | th(colspan="2") Actions 17 | th Post Title 18 | tbody 19 | each article, index in articles 20 | tr(data-id=`${article._id}`, class=(!article.published) ? "unpublished" : "") 21 | td.action 22 | button.btn.btn-danger.btn-sm.remove(type="button") 23 | span.glyphicon.glyphicon-remove(title="Remove") 24 | td.action 25 | button.btn.btn-default.btn-sm.publish(type="button") 26 | span.glyphicon(class=(article.published) ? "glyphicon-pause":"glyphicon-play", title=(article.published) ? "Unpublish" : "Publish") 27 | td= article.title 28 | script(type="text/javascript", src="js/admin.js") 29 | -------------------------------------------------------------------------------- /code/ch6/blog-password/views/article.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | p 5 | h1= title 6 | p= text -------------------------------------------------------------------------------- /code/ch6/blog-password/views/includes/menu.pug: -------------------------------------------------------------------------------- 1 | .menu 2 | ul.nav.nav-pills 3 | li(class=(menu === "index") ? "active" : "") 4 | a(href="/") Home 5 | if (admin) 6 | li(class=(menu === "post") ? "active" : "") 7 | a(href="/post") Post 8 | li(class=(menu === "admin") ? "active" : "") 9 | a(href="/admin") Admin 10 | li 11 | a(href="/logout") Log out 12 | else 13 | li(class=(menu === "login") ? "active" : "") 14 | a(href="/login") Log in -------------------------------------------------------------------------------- /code/ch6/blog-password/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block page 4 | - var menu = 'index' 5 | block content 6 | if (articles.length === 0) 7 | | There's no published content yet. 8 | a(href="/login") Log in 9 | | to post and publish. 10 | else 11 | each article, index in articles 12 | div 13 | h2 14 | a(href=`/articles/${article.slug}`)= article.title -------------------------------------------------------------------------------- /code/ch6/blog-password/views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= appTitle 5 | script(type="text/javascript", src="/js/jquery-2.0.3.min.js") 6 | link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap.min.css") 7 | link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap-theme.min.css") 8 | link(rel="stylesheet", href="/css/style.css") 9 | script(type="text/javascript", src="/css/bootstrap-3.0.2/js/bootstrap.min.js") 10 | script(type="text/javascript", src="/js/blog.js") 11 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 12 | body 13 | #wrap 14 | .container 15 | h1.page-header= appTitle 16 | p.lead Welcome to example from Express.js Experience by  17 | a(href="http://twitter.com/azat_co") @azatmardan 18 | |. Please enjoy. 19 | block page 20 | block header 21 | div 22 | include includes/menu 23 | block alert 24 | div.alert.alert-warning.hidden 25 | .content 26 | block content 27 | block footer 28 | footer 29 | .container 30 | p 31 | | Copyright © 2018 | Issues? Submit to 32 | a(href="https://github.com/azat-co/blog-express/issues") GitHub 33 | | . -------------------------------------------------------------------------------- /code/ch6/blog-password/views/login.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'login' 4 | block content 5 | .col-md-4.col-md-offset-4 6 | h2 Log in 7 | div= error 8 | div 9 | form(action="/login", method="POST") 10 | p 11 | input.form-control(name="email", type="text", placeholder="hi@azat.co") 12 | p 13 | input.form-control(name="password", type="password", placeholder="***") 14 | p 15 | button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in 16 | -------------------------------------------------------------------------------- /code/ch6/blog-password/views/post.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'post' 4 | block content 5 | 6 | h2 Post an Article 7 | div= error 8 | div.col-md-8 9 | form(action="/post", method="POST", role="form") 10 | div.form-group 11 | label(for="title") Title 12 | input#title.form-control(name="title", type="text", placeholder="JavaScript is good") 13 | div.form-group 14 | label(for="slug") Slug 15 | input#slug.form-control(name="slug", type="text", placeholder="js-good") 16 | span.help-block This string will be used in the URL. 17 | div.form-group 18 | label(for="text") Text 19 | textarea#text.form-control(rows="5", name="text", placeholder="Text") 20 | p 21 | button.btn.btn-primary(type="submit") Save 22 | -------------------------------------------------------------------------------- /code/ch6/jwt-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-example", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Azat Mardan (http://azat.co/)", 11 | "license": "MIT" 12 | } 13 | -------------------------------------------------------------------------------- /code/ch6/jwt-example/server.js: -------------------------------------------------------------------------------- 1 | const SECRET = 'Practical Node, 2nd Edition' 2 | const express = require('express') 3 | const bodyParser = require('body-parser') 4 | const jwt = require('jsonwebtoken') 5 | const bcrypt = require('bcrypt') 6 | const app = express() 7 | app.use(bodyParser.json()) 8 | const courses = [ 9 | {title: "You Don't Know Node"}, 10 | {title: 'AWS Intro'} 11 | ] 12 | const users = [] 13 | const auth = (req, res, next) => { 14 | if (req.headers && req.headers.auth && req.headers.auth.split(' ')[0] === 'JWT') { 15 | jwt.verify(req.headers.auth.split(' ')[1], SECRET, (error, decoded) => { 16 | if (error) return res.status(401).send() 17 | req.user = decoded 18 | console.log('authenticated as ', decoded.username) 19 | next() 20 | }) 21 | } else return res.status(401).send() 22 | } 23 | 24 | app.get('/courses', (req, res) => { 25 | res.send(courses) 26 | }) 27 | app.post('/courses', auth, (req, res) => { 28 | courses.push({title: req.body.title}) 29 | res.send(courses) 30 | }) 31 | 32 | app.post('/auth/register', (req, res) => { 33 | bcrypt.hash(req.body.password, 10, (error, hash) => { 34 | if (error) return res.status(500).send() 35 | users.push({ 36 | username: req.body.username, 37 | passwordHash: hash 38 | }) 39 | res.status(201).send('registered') 40 | }) 41 | }) 42 | 43 | app.post('/auth/login', (req, res) => { 44 | const foundUser = users.find((value, index, list) => { 45 | if (value.username === req.body.username) return true 46 | else return false 47 | }) 48 | if (foundUser) { 49 | bcrypt.compare(req.body.password, foundUser.passwordHash, (error, matched) => { 50 | if (!error && matched) { 51 | res.status(201).json({token: jwt.sign({ username: foundUser.username}, SECRET)}) 52 | } else res.status(401).send() 53 | }) 54 | } else res.status(401).send() 55 | }) 56 | 57 | app.listen(3000) 58 | -------------------------------------------------------------------------------- /code/ch6/oauth-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "oauth-example", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "ch6-oauth.js", 6 | "scripts": { 7 | "start": "sh ./start.sh", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "Azat Mardan (http://azat.co/)", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "standard": "10.0.3" 15 | }, 16 | "dependencies": { 17 | "oauth": "0.9.15" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /code/ch6/oauth-example/start.sh: -------------------------------------------------------------------------------- 1 | TWITTER_CONSUMER_KEY=ABCABC \ 2 | TWITTER_CONSUMER_SECRET=XYZXYZXYZ \ 3 | node -e "console.log(process.env.TWITTER_CONSUMER_KEY)" 4 | node "twitter-oauth.js" -------------------------------------------------------------------------------- /code/ch6/oauth-example/twitter-oauth.js: -------------------------------------------------------------------------------- 1 | const OAuth = require('oauth') 2 | const OAuth2 = OAuth.OAuth2 3 | const twitterConsumerKey = 'your key' 4 | const twitterConsumerSecret = 'your secret' 5 | const oauth2 = new OAuth2(twitterConsumerKey, 6 | twitterConsumerSecret, 7 | 'https://api.twitter.com/', 8 | null, 9 | 'oauth2/token', 10 | null 11 | ) 12 | 13 | oauth2.getOAuthAccessToken( 14 | '', 15 | {'grant_type': 'client_credentials'}, 16 | function (e, access_token, refresh_token, results) { 17 | console.log('bearer: ', access_token) 18 | } 19 | ) 20 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/Makefile: -------------------------------------------------------------------------------- 1 | REPORTER = list 2 | MOCHA_OPTS = --ui bdd -c 3 | 4 | db: 5 | echo Seeding blog-test ***************************************************** 6 | ./seed.sh 7 | test: 8 | clear 9 | 10 | echo Starting test ********************************************************* 11 | ./node_modules/mocha/bin/mocha \ 12 | --reporter $(REPORTER) \ 13 | $(MOCHA_OPTS) \ 14 | --recursive \ 15 | tests/*.js 16 | echo Ending test 17 | start: 18 | TWITTER_CONSUMER_KEY=ABC \ 19 | TWITTER_CONSUMER_SECRET=XYZXYZ \ 20 | NODE_ENV=development \ 21 | node app 22 | 23 | .PHONY: test db start -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/db/articles.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "title": "Node is a movement", 3 | "slug": "node-movement", 4 | "published": true, 5 | "text": "In one random deployment, it is often assumed that the number of scattered sensors are more than that required by the critical sensor density. Otherwise, complete area coverage may not be guaranteed in this deployment, and some coverage holes may exist. Besides using more sensors to improve coverage, mobile sensor nodes can be used to improve network coverage. Mobile sensor nodes are equipped with mobile platforms and can move around after initial deployment. Although a mobile sensor node normally is more expensive than its stationary compeer, it can serve many functionalities and improve network coverage. The design of a node movement strategy usually needs to address the following question:Where to move and how to efficiently move mobile modes so that area coverage can be optimized? \nIn different network scenarios, the objectives of nodes’ movement are different. In a hybrid network consisting of both stationary and mobile sensor nodes, the objective is mainly to relocate mobile nodes to heal the coverage holes caused by the stationary nodes. In a mobile network consisting of only mobile nodes, the primary objective is to maximize the coverage of these mobile nodes, and in event monitoring scenario, an important objective is to dispatch mobile nodes to the sources of events for better event coverage." 6 | }, { 7 | "title": "Express.js Experience", 8 | "slug": "express-experience", 9 | "text": "Work in progress", 10 | "published": false 11 | },{ 12 | "title": "Node.js FUNdamentals: A Concise Overview of The Main Concepts", 13 | "slug": "node-fundamentals", 14 | "published": true, 15 | "text": "Node.js is a highly efficient and scalable non-blocking I/O platform that was build on top of Google Chrome V8 engine and its ECMAScript. This means that most front-end JavaScript (another implementation of ECMAScript) objects, functions and methods are available in Node.js. Please refer to JavaScript FUNdamentals if you need a refresher on JS-specific basics." 16 | }] -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/db/users.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "email": "hi@azat.co", 3 | "admin": true, 4 | "password": "1" 5 | }] -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/models/article.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const articleSchema = new mongoose.Schema({ 4 | title: { 5 | type: String, 6 | required: true, 7 | validate: [function (value) { 8 | return value.length <= 120 9 | }, 'Title is too long (120 max)'], 10 | default: 'New Post' 11 | }, 12 | text: String, 13 | published: { 14 | type: Boolean, 15 | default: false 16 | }, 17 | slug: { 18 | type: String, 19 | set: function (value) { 20 | return value.toLowerCase().replace(' ', '-') 21 | } 22 | } 23 | }) 24 | 25 | articleSchema.static({ 26 | list: function (callback) { 27 | this.find({}, null, {sort: {_id: -1}}, callback) 28 | } 29 | }) 30 | 31 | module.exports = mongoose.model('Article', articleSchema) 32 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/models/index.js: -------------------------------------------------------------------------------- 1 | exports.Article = require('./article') 2 | exports.User = require('./user') -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const userSchema = new mongoose.Schema({ 4 | email: { 5 | type: String, 6 | required: true, 7 | set: function (value) { return value.trim().toLowerCase() }, 8 | validate: [ 9 | function (email) { 10 | return (email.match(/[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i) != null) 11 | }, 12 | 'Invalid email' 13 | ] 14 | }, 15 | password: String, 16 | admin: { 17 | type: Boolean, 18 | default: false 19 | } 20 | }) 21 | 22 | module.exports = mongoose.model('User', userSchema) 23 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blog-mongoose", 3 | "version": "1.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "make start", 7 | "seed": "sh ./seed.sh", 8 | "test": "make test", 9 | "st": "standard app.js && standard tests/index.js && standard routes/*" 10 | }, 11 | "author": "Azat Mardan (http://azat.co/)", 12 | "license": "MIT", 13 | "dependencies": { 14 | "body-parser": "1.18.2", 15 | "cookie-parser": "1.4.3", 16 | "errorhandler": "1.5.0", 17 | "everyauth": "0.4.9", 18 | "express": "4.16.2", 19 | "express-session": "1.15.6", 20 | "method-override": "2.3.10", 21 | "mongoose": "4.13.0", 22 | "morgan": "1.9.1", 23 | "pug": "2.0.0-rc.4", 24 | "serve-favicon": "2.4.5", 25 | "stylus": "0.54.5" 26 | }, 27 | "devDependencies": { 28 | "expect.js": "0.3.1", 29 | "mocha": "4.0.1", 30 | "standard": "10.0.3", 31 | "superagent": "3.8.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch7/blog-mongoose/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch7/blog-mongoose/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch7/blog-mongoose/public/css/bootstrap-3.0.2/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/public/css/style.css: -------------------------------------------------------------------------------- 1 | #wrap { 2 | min-height: 100%; 3 | height: auto; 4 | margin: 0 auto -60px; 5 | padding: 0 0 60px; 6 | } 7 | footer { 8 | height: 60px; 9 | } 10 | html, 11 | body { 12 | height: 100%; 13 | } 14 | .content { 15 | margin-top: 40px; 16 | margin-bottom: 40px; 17 | } 18 | .glyphicon-remove, 19 | .glyphicon-pause, 20 | .glyphicon-play { 21 | cursor: pointer; 22 | } 23 | tr.unpublished { 24 | color: #cec1c2; 25 | } 26 | td.action { 27 | width: 20px; 28 | } 29 | .alert { 30 | margin-top: 20px; 31 | } 32 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/public/css/style.styl: -------------------------------------------------------------------------------- 1 | #wrap 2 | min-height: 100%; 3 | height: auto; 4 | margin: 0 auto -60px; 5 | padding: 0 0 60px; 6 | 7 | footer 8 | height: 60px; 9 | 10 | html, body 11 | height: 100%; 12 | .content 13 | margin-top 40px 14 | margin-bottom 40px 15 | .glyphicon-remove, .glyphicon-pause, .glyphicon-play 16 | cursor pointer 17 | tr.unpublished 18 | color #cec1c2 19 | td.action 20 | width 20px 21 | .alert 22 | margin-top 20px 23 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/public/js/admin.js: -------------------------------------------------------------------------------- 1 | $.ajaxSetup({ 2 | xhrFields: {withCredentials: true}, 3 | error: function(xhr, status, error) { 4 | $('.alert').removeClass('hidden'); 5 | $('.alert').html("Status: " + status + ", error: " + error); 6 | } 7 | }); 8 | 9 | var findTr = function(event) { 10 | var target = event.srcElement || event.target; 11 | var $target = $(target); 12 | var $tr = $target.parents('tr'); 13 | return $tr; 14 | }; 15 | 16 | var remove = function(event) { 17 | var $tr = findTr(event); 18 | var id = $tr.data('id'); 19 | $.ajax({ 20 | url: '/api/articles/' + id, 21 | type: 'DELETE', 22 | success: function(data, status, xhr) { 23 | $('.alert').addClass('hidden'); 24 | $tr.remove(); 25 | } 26 | }) 27 | }; 28 | 29 | var update = function(event) { 30 | var $tr = findTr(event); 31 | $tr.find('button').attr('disabled', 'disabled'); 32 | var data = { 33 | published: $tr.hasClass('unpublished') 34 | }; 35 | var id = $tr.attr('data-id'); 36 | $.ajax({ 37 | url: '/api/articles/' + id, 38 | type: 'PUT', 39 | contentType: 'application/json', 40 | data: JSON.stringify({article: data}), 41 | success: function(dataResponse, status, xhr) { 42 | $tr.find('button').removeAttr('disabled'); 43 | $('.alert').addClass('hidden'); 44 | if (data.published) { 45 | $tr.removeClass('unpublished').find('.glyphicon-play').removeClass('glyphicon-play').addClass('glyphicon-pause'); 46 | } else { 47 | $tr.addClass('unpublished').find('.glyphicon-pause').removeClass('glyphicon-pause').addClass('glyphicon-play'); 48 | } 49 | } 50 | }) 51 | }; 52 | 53 | $(document).ready(function(){ 54 | var $element = $('.admin tbody'); 55 | $element.on('click', 'button.remove', remove); 56 | $element.on('click', 'button', update); 57 | }) -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/public/js/blog.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azat-co/practicalnode/be292d1a31bfb71c355fded6ba15b0333dc5c7be/code/ch7/blog-mongoose/public/js/blog.js -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/readme.md: -------------------------------------------------------------------------------- 1 | # Todo 2 | 3 | * Markdown 4 | * Edit post (modal, fetch data via ajax) 5 | * Photo upload 6 | * salt password -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/routes/index.js: -------------------------------------------------------------------------------- 1 | exports.article = require('./article') 2 | exports.user = require('./user') 3 | 4 | /* 5 | * GET home page. 6 | */ 7 | 8 | exports.index = (req, res, next) => { 9 | req.models.Article.find( 10 | {published: true}, 11 | null, 12 | {sort: {_id: -1}}, 13 | (error, articles) => { 14 | if (error) return next(error) 15 | res.render('index', {articles: articles}) 16 | } 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/routes/user.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * GET users listing. 4 | */ 5 | 6 | exports.list = (req, res) => { 7 | res.send('respond with a resource') 8 | } 9 | 10 | /* 11 | * GET login page. 12 | */ 13 | 14 | exports.login = (req, res, next) => { 15 | res.render('login') 16 | } 17 | 18 | /* 19 | * GET logout route. 20 | */ 21 | 22 | exports.logout = (req, res, next) => { 23 | req.session.destroy() 24 | res.redirect('/') 25 | } 26 | 27 | /* 28 | * POST authenticate route. 29 | */ 30 | 31 | exports.authenticate = (req, res, next) => { 32 | if (!req.body.email || !req.body.password) { 33 | return res.render('login', {error: 'Please enter your email and password.'}) 34 | } 35 | req.models.User.findOne({ 36 | email: req.body.email, 37 | password: req.body.password 38 | }, function (error, user) { 39 | if (error) return next(error) 40 | if (!user) return res.render('login', {error: 'Incorrect email&password combination.'}) 41 | req.session.user = user 42 | req.session.admin = user.admin 43 | res.redirect('/admin') 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/seed.sh: -------------------------------------------------------------------------------- 1 | mongoimport --db blog --collection users --file ./db/users.json --jsonArray 2 | mongoimport --db blog --collection articles --file ./db/articles.json --jsonArray 3 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/tests/index.js: -------------------------------------------------------------------------------- 1 | const boot = require('../app').boot 2 | const shutdown = require('../app').shutdown 3 | const port = require('../app').port 4 | const superagent = require('superagent') 5 | const expect = require('expect.js') 6 | 7 | // TODO: seed from the test and then clean up 8 | const seedArticles = require('../db/articles.json') 9 | // const seedUsers = require('../db/users.json') 10 | 11 | describe('server', function () { 12 | before(function () { 13 | boot() 14 | }) 15 | 16 | describe('homepage', function () { 17 | it('should respond to GET', function (done) { 18 | superagent 19 | .get(`http://localhost:${port}`) 20 | .end((error, res) => { 21 | expect(error).to.be(null) 22 | expect(res.status).to.equal(200) 23 | done() 24 | }) 25 | }) 26 | it('should contain posts', function (done) { 27 | superagent 28 | .get(`http://localhost:${port}`) 29 | .end((error, res) => { 30 | expect(error).to.be(null) 31 | expect(res.text).to.be.ok 32 | seedArticles.forEach(function (item, index, list) { 33 | if (item.published) { 34 | expect(res.text).to.contain(`

${item.title}`) 35 | } else { 36 | expect(res.text).not.to.contain(`

${item.title}`) 37 | } 38 | // console.log(item.title, res.text) 39 | }) 40 | done() 41 | }) 42 | }) 43 | }) 44 | 45 | describe('article page', function () { 46 | it('should display text or 401', function (done) { 47 | let n = seedArticles.length 48 | seedArticles.forEach(function (item, index, list) { 49 | superagent 50 | .get(`http://localhost:${port}/articles/${seedArticles[index].slug}`) 51 | .end((error, res) => { 52 | if (item.published) { 53 | expect(error).to.be(null) 54 | expect(res.text).to.contain(seedArticles[index].text) 55 | } else { 56 | expect(error).to.be.ok 57 | expect(res.status).to.be(401) 58 | } 59 | // console.log(item.title) 60 | if (index + 1 === n) { 61 | done() 62 | } 63 | }) 64 | }) 65 | }) 66 | }) 67 | after(function () { 68 | // shutdown() 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/views/admin.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'admin' 4 | block content 5 | div.admin 6 | if (articles.length === 0 ) 7 | p 8 | | Nothing to display. Add a new 9 | a(href="/post") article 10 | |. 11 | else 12 | 13 | table.table.table-stripped 14 | thead 15 | tr 16 | th(colspan="2") Actions 17 | th Post Title 18 | tbody 19 | each article, index in articles 20 | tr(data-id=`${article._id}`, class=(!article.published) ? 'unpublished':'') 21 | td.action 22 | button.btn.btn-danger.btn-sm.remove(type="button") 23 | span.glyphicon.glyphicon-remove(title="Remove") 24 | td.action 25 | button.btn.btn-default.btn-sm.publish(type="button") 26 | span.glyphicon(class=(article.published) ? "glyphicon-pause" : "glyphicon-play", title=(article.published) ? "Unpublish" : "Publish") 27 | td= article.title 28 | script(type="text/javascript", src="js/admin.js") 29 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/views/article.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | p 5 | h1= title 6 | p= text -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/views/includes/menu.pug: -------------------------------------------------------------------------------- 1 | .menu 2 | ul.nav.nav-pills 3 | li(class=(menu === "index") ? "active" : "") 4 | a(href="/") Home 5 | if (admin) 6 | li(class=(menu === "post") ? "active" : "") 7 | a(href="/post") Post 8 | li(class=(menu === "admin") ? "active" : "") 9 | a(href="/admin") Admin 10 | li 11 | a(href="/logout") Log out 12 | else 13 | li(class=(menu === "login") ? "active" : "") 14 | a(href="/login") Log in 15 | li 16 | a(href="/auth/twitter") Sign in with Twitter -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block page 4 | - var menu = 'index' 5 | block content 6 | if (articles.length === 0) 7 | | There's no published content yet. 8 | a(href="/login") Log in 9 | | to post and publish. 10 | else 11 | each article, index in articles 12 | div 13 | h2 14 | a(href=`/articles/${article.slug}`)= article.title -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= appTitle 5 | script(type="text/javascript", src="/js/jquery-2.0.3.min.js") 6 | link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap.min.css") 7 | link(rel="stylesheet", href="/css/bootstrap-3.0.2/css/bootstrap-theme.min.css") 8 | link(rel="stylesheet", href="/css/style.css") 9 | script(type="text/javascript", src="/css/bootstrap-3.0.2/js/bootstrap.min.js") 10 | script(type="text/javascript", src="/js/blog.js") 11 | meta(name="viewport", content="width=device-width, initial-scale=1.0") 12 | body 13 | #wrap 14 | .container 15 | h1.page-header= appTitle 16 | p.lead Welcome to example from Express.js Experience by  17 | a(href="http://twitter.com/azat_co") @azatmardan 18 | |. Please enjoy. 19 | block page 20 | block header 21 | div 22 | include includes/menu 23 | block alert 24 | div.alert.alert-warning.hidden 25 | .content 26 | block content 27 | block footer 28 | footer 29 | .container 30 | p 31 | | Copyright © 2018 | Issues? Submit to 32 | a(href="https://github.com/azat-co/blog-express/issues") GitHub 33 | | . -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/views/login.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'login' 4 | block content 5 | .col-md-4.col-md-offset-4 6 | h2 Log in 7 | div= error 8 | div 9 | a(href='/auth/twitter') Sign in with Twitter 10 | div 11 | form(action="/login", method="POST") 12 | p 13 | input.form-control(name="email", type="text", placeholder="hi@azat.co") 14 | p 15 | input.form-control(name="password", type="password", placeholder="***") 16 | p 17 | button.btn.btn-lg.btn-primary.btn-block(type="submit") Log in 18 | -------------------------------------------------------------------------------- /code/ch7/blog-mongoose/views/post.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | block page 3 | - var menu = 'post' 4 | block content 5 | 6 | h2 Post an Article 7 | div= error 8 | div.col-md-8 9 | form(action="/post", method="POST", role="form") 10 | div.form-group 11 | label(for="title") Title 12 | input#title.form-control(name="title", type="text", placeholder="JavaScript is good") 13 | div.form-group 14 | label(for="slug") Slug 15 | input#slug.form-control(name="slug", type="text", placeholder="js-good") 16 | span.help-block This string will be used in the URL. 17 | div.form-group 18 | label(for="text") Text 19 | textarea#text.form-control(rows="5", name="text", placeholder="Text") 20 | p 21 | button.btn.btn-primary(type="submit") Save 22 | -------------------------------------------------------------------------------- /code/ch7/gravatar.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto') 2 | let email = 'Hi@azat.co ' 3 | 4 | email = email.trim() 5 | email = email.toLowerCase() 6 | 7 | var url = crypto.createHash('md5').update(email).digest('hex') 8 | console.log(url) 9 | -------------------------------------------------------------------------------- /code/ch7/mongoose-example/mongoose.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | mongoose.connect('mongodb://localhost:27017/test', {useMongoClient: true}) 3 | mongoose.Promise = global.Promise 4 | const Book = mongoose.model('Book', { name: String }) 5 | 6 | const practicalNodeBook = new Book({ name: 'Practical Node.js' }) 7 | practicalNodeBook.save((err, results) => { 8 | if (err) { 9 | console.error(err) 10 | process.exit(1) 11 | } else { 12 | console.log('Saved: ', results) 13 | process.exit(0) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /code/ch7/mongoose-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mongoose-example", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "mongoose.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "Azat Mardan (http://azat.co/)", 11 | "license": "MIT", 12 | "dependencies": { 13 | "mongoose": "4.13.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /code/ch8/rest-express/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const mongoskin = require('mongoskin') 3 | const bodyParser = require('body-parser') 4 | const logger = require('morgan') 5 | const http = require('http') 6 | 7 | const app = express() 8 | 9 | app.set('port', process.env.PORT || 3000) 10 | 11 | app.use(bodyParser.json()) 12 | app.use(logger()) 13 | 14 | const db = mongoskin.db('mongodb://@localhost:27017/test') 15 | const id = mongoskin.helper.toObjectID 16 | 17 | app.param('collectionName', (req, res, next, collectionName) => { 18 | req.collection = db.collection(collectionName) 19 | return next() 20 | }) 21 | 22 | app.get('/', (req, res, next) => { 23 | res.send('Select a collection, e.g., /collections/messages') 24 | }) 25 | 26 | app.get('/collections/:collectionName', (req, res, next) => { 27 | req.collection.find({}, {limit: 10, sort: [['_id', -1]]}) 28 | .toArray((e, results) => { 29 | if (e) return next(e) 30 | res.send(results) 31 | } 32 | ) 33 | }) 34 | 35 | app.post('/collections/:collectionName', (req, res, next) => { 36 | // TODO: Validate req.body 37 | req.collection.insert(req.body, {}, (e, results) => { 38 | if (e) return next(e) 39 | res.send(results.ops) 40 | }) 41 | }) 42 | 43 | app.get('/collections/:collectionName/:id', (req, res, next) => { 44 | req.collection.findOne({_id: id(req.params.id)}, (e, result) => { 45 | if (e) return next(e) 46 | res.send(result) 47 | }) 48 | }) 49 | 50 | app.put('/collections/:collectionName/:id', (req, res, next) => { 51 | req.collection.update({_id: id(req.params.id)}, 52 | {$set: req.body}, 53 | {safe: true, multi: false}, (e, result) => { 54 | if (e) return next(e) 55 | res.send((result.result.n === 1) ? {msg: 'success'} : {msg: 'error'}) 56 | }) 57 | }) 58 | 59 | app.delete('/collections/:collectionName/:id', (req, res, next) => { 60 | req.collection.remove({_id: id(req.params.id)}, (e, result) => { 61 | if (e) return next(e) 62 | // console.log(result) 63 | res.send((result.result.n === 1) ? {msg: 'success'} : {msg: 'error'}) 64 | }) 65 | }) 66 | 67 | const server = http.createServer(app) 68 | const boot = () => { 69 | server.listen(app.get('port'), () => { 70 | console.info(`Express server listening on port ${app.get('port')}`) 71 | }) 72 | } 73 | 74 | const shutdown = () => { 75 | server.close(process.exit) 76 | } 77 | 78 | if (require.main === module) { 79 | boot() 80 | } else { 81 | console.info('Running app as a module') 82 | exports.boot = boot 83 | exports.shutdown = shutdown 84 | exports.port = app.get('port') 85 | } 86 | -------------------------------------------------------------------------------- /code/ch8/rest-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest-express", 3 | "version": "0.2.1", 4 | "description": "REST API application with Express, Mongoskin, MongoDB, Mocha and Superagent", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "start": "node index.js", 11 | "test": "PORT=3007 ./node_modules/.bin/mocha test -R spec" 12 | }, 13 | "author": "Azat Mardan (http://azat.co/)", 14 | "license": "MIT", 15 | "dependencies": { 16 | "body-parser": "1.18.2", 17 | "express": "4.16.2", 18 | "mongodb": "2.2.33", 19 | "mongoskin": "2.1.0", 20 | "morgan": "1.9.1" 21 | }, 22 | "devDependencies": { 23 | "expect.js": "0.3.1", 24 | "mocha": "4.0.1", 25 | "standard": "10.0.3", 26 | "superagent": "3.8.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /code/ch8/rest-express/test/index.js: -------------------------------------------------------------------------------- 1 | const boot = require('../index.js').boot 2 | const shutdown = require('../index.js').shutdown 3 | const port = require('../index.js').port 4 | 5 | const superagent = require('superagent') 6 | const expect = require('expect.js') 7 | 8 | // const port = process.env.PORT || 3000 9 | 10 | before(() => { 11 | boot() 12 | }) 13 | 14 | describe('express rest api server', () => { 15 | let id 16 | 17 | it('post object', (done) => { 18 | superagent.post(`http://localhost:${port}/collections/test`) 19 | .send({ name: 'John', 20 | email: 'john@rpjs.co' 21 | }) 22 | .end((e, res) => { 23 | // console.log(res.body) 24 | expect(e).to.eql(null) 25 | expect(res.body.length).to.eql(1) 26 | expect(res.body[0]._id.length).to.eql(24) 27 | id = res.body[0]._id 28 | done() 29 | }) 30 | }) 31 | 32 | it('retrieves an object', (done) => { 33 | superagent.get(`http://localhost:${port}/collections/test/${id}`) 34 | .end((e, res) => { 35 | // console.log(res.body) 36 | expect(e).to.eql(null) 37 | expect(typeof res.body).to.eql('object') 38 | expect(res.body._id.length).to.eql(24) 39 | expect(res.body._id).to.eql(id) 40 | done() 41 | }) 42 | }) 43 | 44 | it('retrieves a collection', (done) => { 45 | superagent.get(`http://localhost:${port}/collections/test`) 46 | .end((e, res) => { 47 | // console.log(res.body) 48 | expect(e).to.eql(null) 49 | expect(res.body.length).to.be.above(0) 50 | expect(res.body.map(function (item) { return item._id })).to.contain(id) 51 | done() 52 | }) 53 | }) 54 | 55 | it('updates an object', (done) => { 56 | superagent.put(`http://localhost:${port}/collections/test/${id}`) 57 | .send({name: 'Peter', 58 | email: 'peter@yahoo.com'}) 59 | .end((e, res) => { 60 | // console.log(res.body) 61 | expect(e).to.eql(null) 62 | expect(typeof res.body).to.eql('object') 63 | expect(res.body.msg).to.eql('success') 64 | done() 65 | }) 66 | }) 67 | 68 | it('checks an updated object', (done) => { 69 | superagent.get(`http://localhost:${port}/collections/test/${id}`) 70 | .end((e, res) => { 71 | // console.log(res.body) 72 | expect(e).to.eql(null) 73 | expect(typeof res.body).to.eql('object') 74 | expect(res.body._id.length).to.eql(24) 75 | expect(res.body._id).to.eql(id) 76 | expect(res.body.name).to.eql('Peter') 77 | done() 78 | }) 79 | }) 80 | it('removes an object', (done) => { 81 | superagent.del(`http://localhost:${port}/collections/test/${id}`) 82 | .end((e, res) => { 83 | // console.log(res.body) 84 | expect(e).to.eql(null) 85 | expect(typeof res.body).to.eql('object') 86 | expect(res.body.msg).to.eql('success') 87 | done() 88 | }) 89 | }) 90 | }) 91 | 92 | after(() => { 93 | shutdown() 94 | }) 95 | -------------------------------------------------------------------------------- /code/ch8/rest-hapi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rest-hapi", 3 | "version": "0.0.1", 4 | "description": "REST API application with Express, Mongoskin, MongoDB, Mocha and Superagent", 5 | "main": "index.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "scripts": { 10 | "start": "node index.js", 11 | "test": "mocha test -R spec" 12 | }, 13 | "author": "Azat Mardan (http://azat.co/)", 14 | "license": "MIT", 15 | "dependencies": { 16 | "good": "7.3.0", 17 | "hapi": "16.6.2", 18 | "mongodb": "2.2.33", 19 | "mongoskin": "2.1.0" 20 | }, 21 | "devDependencies": { 22 | "mocha": "4.0.1", 23 | "superagent": "3.8.0", 24 | "expect.js": "0.3.1" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /code/ch8/rest-hapi/test/index.js: -------------------------------------------------------------------------------- 1 | const boot = require('../index.js').boot 2 | const shutdown = require('../index.js').shutdown 3 | const port = require('../index.js').port 4 | 5 | var superagent = require('superagent') 6 | var expect = require('expect.js') 7 | 8 | // const port = process.env.PORT || 3000 9 | 10 | before(() => { 11 | boot() 12 | }) 13 | 14 | describe('express rest api server', () => { 15 | var id 16 | 17 | it('post object', (done) => { 18 | superagent.post(`http://localhost:${port}/collections/test`) 19 | .send({ name: 'John', 20 | email: 'john@rpjs.co' 21 | }) 22 | .end((e, res) => { 23 | // console.log(res.body) 24 | expect(e).to.eql(null) 25 | expect(res.body.length).to.eql(1) 26 | expect(res.body[0]._id.length).to.eql(24) 27 | id = res.body[0]._id 28 | done() 29 | }) 30 | }) 31 | 32 | it('retrieves an object', (done) => { 33 | superagent.get(`http://localhost:${port}/collections/test/${id}`) 34 | .end((e, res) => { 35 | // console.log(res.body) 36 | expect(e).to.eql(null) 37 | expect(typeof res.body).to.eql('object') 38 | expect(res.body._id.length).to.eql(24) 39 | expect(res.body._id).to.eql(id) 40 | done() 41 | }) 42 | }) 43 | 44 | it('retrieves a collection', (done) => { 45 | superagent.get(`http://localhost:${port}/collections/test`) 46 | .end((e, res) => { 47 | // console.log(res.body) 48 | expect(e).to.eql(null) 49 | expect(res.body.length).to.be.above(0) 50 | expect(res.body.map(function (item) { return item._id })).to.contain(id) 51 | done() 52 | }) 53 | }) 54 | 55 | it('updates an object', (done) => { 56 | superagent.put(`http://localhost:${port}/collections/test/${id}`) 57 | .send({name: 'Peter', 58 | email: 'peter@yahoo.com'}) 59 | .end((e, res) => { 60 | // console.log(res.body) 61 | expect(e).to.eql(null) 62 | expect(typeof res.body).to.eql('object') 63 | expect(res.body.msg).to.eql('success') 64 | done() 65 | }) 66 | }) 67 | 68 | it('checks an updated object', (done) => { 69 | superagent.get(`http://localhost:${port}/collections/test/${id}`) 70 | .end((e, res) => { 71 | // console.log(res.body) 72 | expect(e).to.eql(null) 73 | expect(typeof res.body).to.eql('object') 74 | expect(res.body._id.length).to.eql(24) 75 | expect(res.body._id).to.eql(id) 76 | expect(res.body.name).to.eql('Peter') 77 | done() 78 | }) 79 | }) 80 | it('removes an object', (done) => { 81 | superagent.del(`http://localhost:${port}/collections/test/${id}`) 82 | .end((e, res) => { 83 | // console.log(res.body) 84 | expect(e).to.eql(null) 85 | expect(typeof res.body).to.eql('object') 86 | expect(res.body.msg).to.eql('success') 87 | done() 88 | }) 89 | }) 90 | }) 91 | after(() => { 92 | shutdown() 93 | }) 94 | -------------------------------------------------------------------------------- /code/ch9/socket-express/app.js: -------------------------------------------------------------------------------- 1 | const http = require('http') 2 | const express = require('express') 3 | const path = require('path') 4 | const logger = require('morgan') 5 | const bodyParser = require('body-parser') 6 | 7 | const routes = require('./routes/index') 8 | const app = express() 9 | 10 | // view engine setup 11 | app.set('views', path.join(__dirname, 'views')) 12 | app.set('view engine', 'pug') 13 | 14 | app.use(logger('dev')) 15 | app.use(bodyParser.json()) 16 | app.use(bodyParser.urlencoded({extended: true})) 17 | app.use(express.static(path.join(__dirname, 'public'))) 18 | 19 | app.use('/', routes) 20 | 21 | const server = http.createServer(app) 22 | const io = require('socket.io').listen(server) 23 | 24 | io.sockets.on('connection', (socket) => { 25 | socket.on('messageChange', (data) => { 26 | console.log(data) 27 | socket.emit('receive', data.message.split('').reverse().join('')) 28 | }) 29 | }) 30 | 31 | app.set('port', process.env.PORT || 3000) 32 | server.listen(app.get('port'), () => { 33 | console.log(`Express server listening on port ${app.get('port')}`) 34 | }) 35 | -------------------------------------------------------------------------------- /code/ch9/socket-express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket-express", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "body-parser": "1.18.2", 10 | "cookie-parser": "1.4.3", 11 | "debug": "3.1.0", 12 | "express": "4.16.2", 13 | "morgan": "1.9.1", 14 | "pug": "2.0.0-rc.4", 15 | "socket.io": "2.0.4" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /code/ch9/socket-express/public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /code/ch9/socket-express/routes/index.js: -------------------------------------------------------------------------------- 1 | var express = require('express') 2 | var router = express.Router() 3 | 4 | /* GET home page. */ 5 | router.get('/', (req, res) => { 6 | res.render('index', { title: 'Socket.io + Express = <3' }) 7 | }) 8 | 9 | module.exports = router 10 | -------------------------------------------------------------------------------- /code/ch9/socket-express/views/error.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /code/ch9/socket-express/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= title 5 | p Welcome to 6 | span.received-message #{title} 7 | input(type='text', class='message', placeholder='what is on your mind?', onkeyup='send(this)') 8 | script(src="/socket.io/socket.io.js") 9 | script. 10 | var socket = io.connect('http://localhost:3000'); 11 | socket.on('receive', function (message) { 12 | console.log('received %s', message); 13 | document.querySelector('.received-message').innerText = message; 14 | }); 15 | var send = function(input) { 16 | console.log(input.value) 17 | var value = input.value; 18 | console.log('sending %s to server', value); 19 | socket.emit('messageChange', {message: value}); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /code/ch9/socket-express/views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/stylesheets/style.css') 6 | body 7 | block content -------------------------------------------------------------------------------- /code/ch9/ws-basic/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 20 | 21 | -------------------------------------------------------------------------------- /code/ch9/ws-basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ws-basic", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "server.js", 6 | "dependencies": { 7 | "ws": "3.3.0" 8 | }, 9 | "devDependencies": { 10 | "concurrently": "3.5.0", 11 | "http-server": "0.10.0", 12 | "standard": "10.0.3" 13 | }, 14 | "scripts": { 15 | "test": "echo \"Error: no test specified\" && exit 1", 16 | "st": "./node_modules/.bin/standard server.js", 17 | "start": "./node_modules/.bin/concurrently \"node server.js\" \"./node_modules/.bin/http-server .\"" 18 | }, 19 | "keywords": [], 20 | "author": "Azat Mardan (http://azat.co/)", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /code/ch9/ws-basic/server.js: -------------------------------------------------------------------------------- 1 | const WebSocketServer = require('ws').Server 2 | const wss = new WebSocketServer({port: 3000}) 3 | 4 | wss.on('connection', (ws) => { 5 | ws.send('XYZ') 6 | setInterval(()=>{ 7 | ws.send((new Date).toLocaleTimeString()) 8 | }, 1000) 9 | ws.on('message', (message) => { 10 | console.log('received: %s', message) 11 | }) 12 | }) 13 | --------------------------------------------------------------------------------