Express, 2

Лекция 09

Debugging

Отладка

Debugging


// *nix
DEBUG=express:* node index.js

// Windows
set DEBUG=express:* & node index.js

// JS
process.env.DEBUG = 'express:*';
    

Debugging


const logger = (req, res, next) => {
  console.log(req.url);
  next();
};

const hello = (req, res) => {
  res.send('hello');
};

app.use(express.static('.'), { index: false });
app.use(logger);
app.use(hello);
    

Debugging


express:router use / query +1ms
express:router use / expressInit +1ms
express:router use / serveStatic +1ms
express:router use / logger +0ms
express:router use / hello +0ms
    

Debugging


GET http://127.0.0.1:3000/

express:router dispatching GET / +1s
express:router query  : / +1ms
express:router expressInit  : / +0ms
express:router serveStatic  : / +1ms
express:router logger  : / +3ms
express:router hello  : / +0ms
    

API

Express

Express - Router


const options = {
  caseSensitive: false,
  strict: false,
  mergeParams: false
};
    

Express - Router


// caseSensitive: false

'/news' === '/News'
    

Express - Router


// strict: false

'/news' === '/news/'
    

Express - Router


// mergeParams: false

const news = express.Router();
const comments = express.Router();

comments.get('/:commentId', (req, res) => {
  console.log(req.params);
  res.send(`c#${req.params.commentId}
    / n#${req.params.newsId}`);
});

news.use('/:newsId/comments', comments);

app.use('/news', news);
    

Express - Router


// mergeParams: false

GET http://127.0.0.1:3000/news/1/comments/2

{ commentId: '2' }

c#2 / n#undefined
    

Express - Router


// mergeParams: true

const news = express.Router();
const comments
  = express.Router({ mergeParams: true });

comments.get('/:commentId', (req, res) => {
  console.log(req.params);
  res.send(`c#${req.params.commentId}
    / n#${req.params.newsId}`);
});

news.use('/:newsId/comments', comments);

app.use('/news', news);
    

Express - Router


// mergeParams: true

GET http://127.0.0.1:3000/news/1/comments/2

{ newsId: '1', commentId: '2' }

c#2 / n#1
    

Express - Router


// mergeParams: true

const news = express.Router();
const comments
  = express.Router({ mergeParams: true });

comments.get('/:id', (req, res) => {
  console.log(req.params);
  res.send(`c#${req.params.id}`);
});

news.use('/:id/comments', comments);

app.use('/news', news);
    

Express - Router


// mergeParams: true

GET http://127.0.0.1:3000/news/1/comments/2

{ id: '2' }

c#2
    

API

Application

App


const app = express();
    

App - Locals


app.locals.title = 'My App';
app.locals.email = 'me@myapp.com';

...

const logger = (req, res, next) => {
  console.log(req.app.locals.title);
  next();
};
    

App - Mountpath


const app = express(); // the main app
const admin = express(); // the sub app

admin.get('/', (req, res) => {
  console.log(admin.mountpath); // /admin
  res.send('Admin Homepage');
});

app.use('/admin', admin); // mount the sub app
    

App - Path


const app = express(),
  blog = express(),
  blogAdmin = express();

app.use('/blog', blog);
blog.use('/admin', blogAdmin);

console.log(app.path()); // ''
console.log(blog.path()); // '/blog'
console.log(blogAdmin.path()); // '/blog/admin'
    

App - Param


app.param('userId', (req, res, next, id) => {
  User.find(id, (err, user) => {
    if (err) next(err);

    req.user = user;
    next();
  });
});

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

app.get('/user/:userId/comment', (req, res) => {
  ...
});
    

App - Param


app.param('id', (req, res, next, id) => {
  console.log('CALLED ONLY ONCE');
  next();
});

app.get('/user/:id', (req, res, next) => {
  console.log('although this matches');
  next();
});

app.get('/user/:id', (req, res) => {
  console.log('and this matches too');
  res.end();
});
    

App - Param


GET http://127.0.0.1:3000/user/42

CALLED ONLY ONCE
although this matches
and this matches too
    

App - Param


app.param(['id', 'page'], function (req, res, next, value) {
  console.log('CALLED ONLY ONCE with', value);
  next();
});

app.get('/user/:id/:page', function (req, res, next) {
  console.log('although this matches');
  next();
});

app.get('/user/:id/:page', function (req, res) {
  console.log('and this matches too');
  res.end();
});
    

App - Param


CALLED ONLY ONCE with 42
CALLED ONLY ONCE with 3
although this matches
and this matches too
    

App - Settings


app.get(property);
app.set(property, value);
    

app.disable(property);
app.enable(property);
    

app.disabled(property);
app.enabled(property);
    

App - Settings


case sensitive routing // '/Foo', '/foo'
etag
strict routing // '/foo', '/foo/'
    

App - Settings


query parser | Varied

false
'simple' - querystring
'extended' - qs
function
    

App - Settings


// https://www.npmjs.com/package/qs

'foo[bar][baz]=foobarbaz'

=>

{
  foo: {
    bar: {
      baz: 'foobarbaz'
    }
  }
}
    

App - Settings


// https://www.npmjs.com/package/qs

'a[]=b&a[]=c'

=>

{
  a: ['b', 'c']
}
    

App - Settings


// https://www.npmjs.com/package/qs

'a[0]=b&a[2]=c&a[1]=d'

=>

{
  a: ['b', 'd', 'c']
}
    

App - Settings


env | String

'production'
'development'
    

App - Settings


x-powered-by | Boolean

"X-Powered-By: Express" HTTP header
    

App - Settings


app.set('title', 'Brand New App');
app.get('title');

> 'Brand New App'
    

API

Request

Request - Url


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

GET http://127.0.0.1:3000/news/42/comments?sort=desc

req.baseUrl
> '/news' // маршрут сработавшего middleware

req.path
> '/42/comments'

req.originalUrl
> '/news/42/comments'
    

Request - Url


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

GET http://example.com/news/42/comments?sort=desc

req.hostname
> 'example.com'

req.ip
> '127.0.0.1'

req.protocol
> 'http'
    

Request - Fresh


req.fresh
req.stale

// cache-control, etag
// if-modified-since, if-none-match
    

Request - Method


app.use((req, res, next) => {
  if (req.method === 'PUT') {
    res.status(405)
      .send('Invalid method');
  }
  else {
    next();
  }
});
    

Request - Params


app.get('/news/:id', (req, res, next) => { ... });

GET 127.0.0.1:3000/news/42

req.params
> { id: 42 }
    

Request - Params


app.get('/news/*/*', (req, res, next) => { ... });

GET 127.0.0.1:3000/news/42/comment

req.params
> { '0': '42', '1': 'comment' }

req.params[0]
> '42'
    

Request - Query


GET /search?q=tobi+ferret

req.query.q
> 'tobi ferret'

GET /shoes?order=desc&shoe[color]=blue
  &shoe[type]=converse

req.query.order
> 'desc'

req.query.shoe.color
> 'blue'

req.query.shoe.type
> 'converse'
    

Request - Secure


GET https://example.com/news

req.secure
> true
    

req.protocol == 'https'
    

Request - Subdomains


GET tobi.ferrets.example.com

req.subdomains
> ["ferrets", "tobi"]
    

Request - XHR (AJAX)


POST example.com

req.xhr
> true

// XMLHttpRequest
    

Request - Accepts


// Accept: text/*, application/json
req.accepts('html');
> 'html'

req.accepts('text/html');
> 'text/html'

req.accepts(['json', 'text']);
> 'json'

req.accepts(['text', 'json']);
> 'json'

req.accepts('image/png');
req.accepts('png');
> undefined
    

Request - Accepts


req.acceptsCharsets(charset [, ...]);

req.acceptsEncodings(encoding [, ...]);

req.acceptsLanguages(lang [, ...]);
    

Request - Header


req.header('Content-Type');
> "text/plain"

req.header('content-type');
> "text/plain"

req.get('Content-Type');
> "text/plain"
    

Request - Is


// With Content-Type: text/html; charset=utf-8
req.is('html');
req.is('text/html');
req.is('text/*');
> true

// When Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
> true

req.is('html');
> false
    

API

Response

Response - Append


res.append('Set-Cookie', 'foo=bar;');
res.append('Warning', '199 warnings');
    

Response - Cookie


res.cookie('name', 'tobi',
  {
    domain: '.example.com',
    path: '/admin',
    secure: true,
    maxAge: 900000
  });

res.cookie('rememberme', '1',
  {
    expires: new Date(Date.now() + 900000),
    httpOnly: true
  });

res.clearCookie('__auth_id');
    

Response - Format


res.format({
  'text/plain': () => {
  res.send('hey');
  },

  'text/html': () => {
  res.send('

hey

'); }, 'application/json': () => { res.send({ message: 'hey' }); }, 'default': () => { res.status(406).send('Not Acceptable'); } });

Response - Format


res.format({
  text: function(){
  res.send('hey');
  },

  html: function(){
  res.send('

hey

'); }, json: function(){ res.send({ message: 'hey' }); } });

Response - JSON


res.json(null);

res.json({ user: 'tobi' });

res.status(500).json({ error: 'message' });
    

Response - Redirect


res.redirect('/foo/bar');

res.redirect('http://example.com');

res.redirect(301, 'http://example.com');

res.redirect('../login');
    

Response - Send


res.send(new Buffer('whoop'));

res.send({ some: 'json' });

res.send('

some html

'); res.status(404) .send('Sorry, we cannot find that!'); res.status(500) .send({ error: 'something blew up' });

Response - Send file


const options = {
  root: __dirname + '/public/',
  dotfiles: 'deny',
  headers: {
    'x-timestamp': Date.now(),
    'x-sent': true
  }
};

const errorHandler = (err) => { ... };

res.sendFile(fileName, options, errorHandler);
    

Response - Send status


// res.status(200).send('OK')
res.sendStatus(200);

// res.status(403).send('Forbidden')
res.sendStatus(403);

// res.status(404).send('Not Found')
res.sendStatus(404);

// res.status(500).send('Internal Server Error')
res.sendStatus(500);
    

Response - Headers


res.set('Content-Type', 'text/plain');

res.get('Content-Type');

res.set({
  'Content-Type': 'text/plain',
  'Content-Length': '123',
  'ETag': '12345'
});
    

Response - End


res.end();

res.status(404).end();
    

cookie-parser

cookie-parser


const express = require('express');
const cookieParser = require('cookie-parser');

const app = express();
app.use(cookieParser());
    

cookie-parser


cookieParser(secret, options)
    

cookie-parser


app.use(cookieParser('my private key'));

res.cookie('user', 'tobi', { signed: true });

Cookie: user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3

req.cookies.user
> user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3

req.signedCookies.user
> "tobi"
    

body-parser

body-parser


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

const app = express();
app.use(bodyParser.json());
    

body-parser


bodyParser.json(options)

inflate // прием сжатых данных, true
limit // макс. размер тела, '100kb'
reviver // ф-я трансформации выходного объекта
strict // только объекты и массивы, true
    

body-parser


bodyParser.raw(options)

inflate
limit
    

body-parser


bodyParser.text(options)

defaultCharset // кодировка, 'utf-8'
inflate
limit
    

body-parser


bodyParser.urlencoded(options)

extended // querystring vs qs
inflate
limit
parameterLimit // макс. число параметров, 1000