require('http').request


const http = require('http');

const request = http.request({ // http.ClientRequest
    hostname: 'localhost',
    port: 8080
});
    

require('http').request


request.on('response', response => {
    let body = '';

    response.on('data', chunk => {
    	body += chunk;
    });

    response.on('end', () => {
    	// Only now we can make awesome things
    });
});
    

request.end();
    

require('request')


const request = require('request');

request.get('http://localhost:8080/',
    (err, res, body) => {
        console.log(body)
    }
);
    

require('got')


const request = require('got');

request
    .get('http://localhost:8080/')
    .then(res => {
        console.log(res.body);
    });
    

require('http').Server


const http = require('http');
const server = new http.Server();
    

server.on('request', (req, res) => {
    res.setHeader('content-type', 'text/html');

    res.write('Hello, User!');

    res.end();
});
    

server.listen(8080);
    

require('http').Server


server.on('request', (req, res) => {
    if (req.url === '/') {
        res.setHeader('Content-Type', 'text/html');
        res.write('Hello, User!');
    } else {
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        res.write('Not Found');
    }

    res.end();
});
    

require('http').Server

/name/Sergey

server.on('request', (req, res) => {
    const matches = req.url.match(/^\/name\/([a-z]+)/);
    const name = matches && matches[1];

    if (name) {
        res.setHeader('Content-Type', 'text/html');
        res.write(`Hello, ${name}`);
    } else {
        res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        res.write('Not Found');
    }

    res.end();
});
    

Express.js

Мокеев Евгений, Гоголев Сергей

Server
Routing
MVC
Templates
Middlewares
Error handling

Server

$ npm install --save express
    
app/
└── app.js
    

require('express')


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

app.get('/name/:name', (req, res) => {
  res.send(`Hello, ${req.params.name}`);
});

app.get('*', (req, res) => {
  res.sendStatus(404);
});

app.listen(8080);
    

Проще

Самое большое коммьюнити

Поддержка strongloop

loopback

Сервис «Заметки»

Храним заметки пользователя в виде файлов

 GET /               главная
    
 GET /notes          список заметок
    
POST /notes          добавление заметки
    
 GET /notes/films    просмотр заметки
    

Routing

Паттерн Front controller

Единая точка входа в приложение

GET / HTTP/1.1
GET /notes HTTP/1.1
POST /notes HTTP/1.1

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

app.get('/', () => {});
    

app.get('/notes', () => {});
    

app.post('/notes', () => {});
    
GET /notes HTTP/1.1
POST /notes HTTP/1.1

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

app
    .route('/notes')
    .get(() => {})
    .post(() => {})
    
GET /notes/films HTTP/1.1
GET /notes/books HTTP/1.1

app.get('/notes/:name', () => {});
    

app.get('/notes/:name', (req, res) => {});
    

app.get('/notes/:name', (req, res) => {
    const name = req.params.name;

    res.send(`Note with "${name}" name`);
});
    
?

app.get('/notes?/:name', () => {});
    
GET /notes/films HTTP/1.1
GET /note/films HTTP/1.1
+

app.get('/notes+/:name', () => {});
    
GET /notes/films HTTP/1.1
GET /notesss/films HTTP/1.1
[]

app.get('/notes/[a-z]+', () => {});
    
GET /notes/films HTTP/1.1
GET /notes/books HTTP/1.1
()

app.get('/notes+/:name([a-z]+)', req => {
    console.log(req.params.name)
});
    

404


app.get('*', res => res.sendStatus(404));
app.put('*', res => res.sendStatus(404));
    

app.all('*', res => res.sendStatus(404));
    

HTTP/1.1 404 Not Found
Connection: keep-alive
Content-Length: 9
Content-Type: text/plain; charset=utf-8
Date: Sun, 28 Feb 2016 14:04:41 GMT
ETag: W/"9-nR6tc+Z4+i9RpwqTOwvwFw"
X-Powered-By: Express

Not Found
    
app/
└── app.js
└── routes.js
    

module.exports = (app) => {
    app.get('/', (req, res) => {});
};
    

app/
└── app.js
└── routes.js
    

require('./routes')(app);
    
express-route-tester

MVC

Паттерн MVC

Отделение модели от представления

app/
└── app.js
└── routes.js
└── models
    └── note.js

class Note {
    constructor(data) {
        this._data = data;
    }

    save() {}

    static findByName(name) {}

    static findAll {}
}

module.exports = Note;
    
app/
└── app.js
└── routes.js
└── models
└── controllers
    └── notes.js

const Note = require('../models/note');

exports.list = (req, res) => {
    const notes = Note.findAll();

    res.send(notes);
}

exports.item = (req, res) => {
    const note = Note.find(req.params.name);

    res.send(note);
}
    
app/
└── app.js
└── routes.js
└── models
└── controllers
    └── notes.js
    └── pages.js

exports.index = (req, res) => {
    res.send('Hello, User!');
}

exports.error404 = (req, res) => {
    res.sendStatus(404);
}
    
app/
└── app.js
└── routes.js
└── models
└── controllers

const pages = require('./controllers/pages');
const notes = require('./controllers/notes');

module.exports = function(app) {
    app.get('/', pages.index);
    app.get('/notes', notes.list);
    app.get('/notes/:name', notes.item);
    app.all('*', pages.error404)
};
    

Можем менять логику обработки запроса без изменения роутера

Можем менять способ хранения данных без изменения контроллера

Templates

HTML = template + data

HTML


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>My notes</title>
</head>
<body>
    Hello, User!
</body>
</html>
    

data


{
    title: 'My notes',
    message: 'Hello, User!'
}
    

template


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ title }}</title>
</head>
<body>
    {{ message }}
</body>
</html>
    
  1. Компиляция
    
    function index(data) {
        return `<!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>${data.title}</title>
    </head>
    <body>${data.message}</body>
    </html>`;
    }
                
  2. Исполнение

    doctype html
    html(lang="en")
      head
        title=pageTitle
        

Over 9000

Handlebars

$ npm install --save hbs
    
app/
└── app.js
└── routes.js
└── models
└── controllers
    

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

app.set('view engine', 'hbs');
require('./routes')(app);

app.listen(8080);
    
app/
└── app.js
└── routes.js
└── models
└── controllers
└── views
    └── index.hbs
    

{{ message }}


exports.index = (req, res) => {
    res.send('Hello, User!');
    res.render('index', {
        message: 'Hello, User!'
    });
}
    
app/
└── app.js
└── routes.js
└── models
└── controllers
└── views
    └── index.hbs
    └── layout.hbs
    

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ title }}</title>
</head>
<body>
    {{{ body }}}
</body>
</html>
    

Выражения

{{ }}

{{ body }}
&lt;h1&gt;Hello, User!&lt;/h1&gt;
    
<script>alert('Muahahaha!');</script>
    
{{{ }}}

{{{ body }}}
<h1>Hello, User!</h1>
    
{{ meta.description }}

res.render('index', {
    title: 'My notes',
    meta: {
        description: 'My awesome notes',
        charset: 'utf-8'
    }
});
    

<head>
    <meta charset="{{ meta.charset }}">
    <meta
        name="description"
        content="{{ meta.description }}">
    <title>{{ title }}</title>
</head>
    
{{#each }}

res.render('notes', {
    notes: [
        { name: 'Films' },
        { name: 'Books' },
        { name: 'Todo' }
    ]
});
    

    {{#each notes}}
  • {{name}}
  • {{/each}}
{{#if }}

{{#if notes.length}}
    {{#each notes}}
  • {{name}}
  • {{/each}}
{{^}}

Notes not found

{{/if}}
app/
└── app.js
└── routes.js
└── models
└── controllers
└── views
    └── index.hbs
    └── layout.hbs
    └── partials
        └── notes.hbs
    
    

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

app.set('view engine', 'hbs');
hbs.registerPartials(__dirname + '/views/partials');
    
{{> }}

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>{{ title }}</title>
</head>
<body>
    {{> notes}}
</body>
</html>
    
handlebarsjs.com

Middlewares

Паттерн Chain of responsibility

Декомпозиция и конвееризация обработки запросов

Паттерн Chain of responsibility

  • Запрос проходит через все обработчики без исключения
  • Запрос обрабатывается строго последовательно
  • Обработчик сам принимает решение, обрабатывать запрос или пропустить дальше

Middlewares


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

// Logs all requests
app.use(function (req, res, next) {
    console.log(`→ ${req.method} ${req.originalUrl}`);

    next();
});
    
$ node app.js

→ GET /
→ GET /notes
→ GET /notes/films
    

Middlewares


app.use((req, res, next) => {
    console.log(`→ ${req.method} ${req.originalUrl}`);
    next();
});
    

app.use((req, res, next) => {
    setTimeout(() => {
        req.commonData = { title: 'My Notes' };

        next();
    }, 1000);
});
    

Выполняются в порядке регистрирования!

require('morgan')


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

app.use(morgan('dev'));
    
GET /notes 200 18.079 ms - 301
|       |    |      |      |
method  url  code   time   bytes
    

app.use(morgan(`:method :url :status
    :response-time ms - :res[content-length]`);
    

require('body-parser')

POST /notes HTTP/1.1
Accept: application/json
Host: localhost

{
    "name": "films",
    "text": "Films to watch"
}
    

req.on('data', function(chunk) {
    body += chunk;
});
    

const json = JSON.parse(body);
    

require('body-parser')

{
    name: 'films',
    text: 'Films to watch'
}
    

const express = require('express');
const bodyParser = require('body-parser');
const app = express();

app.use(bodyParser.json());

app.use(function () {
    console.log(req.body);
})
    

express.static

app/
└── app.js
└── routes.js
└── models
└── controllers
└── views
└── public
    └── styles.css
    
GET /styles.css
    

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

app.use(express.static(__dirname + '/public')));
    

Error handling


app.use(function (err, req, res, next) {
    console.log(err);
    res.sendStatus(500);
});
    
  • Обработчиков может быть несколько
  • Если в middleware произошла ошибка, все последующие игнорируются вплоть до первого обработчика
  • Обработчик ошибок может не прерывать процесс, а передать управление следующей middleware

app.use(bodyParser.json());
    

app.use(function (err, req, res, next) {
    console.log(err);
    next();
});
    

app.use(morgan('dev'));
    

Express generator


$ npm install --save --global express-generator
    
$ express --hbs myappr
    
expressjs.com/starter/generator

Книги

Node.js Design Patterns

Node.js the Right Way

Статьи

How does NodeJS work?

nodeweekly.com

Обучение

nodeschool.io

Скринкаст Node.JS

Пример сервиса «Заметки»