koajs as an alternative to express - odessajs'16
TRANSCRIPT
I am Mykola Kozhuharenkofront-end dev in Krusche & CompanyI’m on github
Hello!
Nodejs/Express downsides:
◉Code readabilityCallback hell
◉Error handlingDuplicate callbacks / cb call might be lost in
third party libraryIf you miss an error handler - you are in
troublesCan't throw an error or use try/catch
app.post('/',function (req, res, next) { var body = req.body; if (!body.name || !body.email || !body.password) { return res.status(400).send("Missing username or email or password") }; // case #1- if user was registered (socials platform) before -> just add a password; User.findOneAndUpdate({ email: body.email }, { $set: {password: body.password} }, (err, user) => { if (!user) { // case #2 - user has never been registering before -> create new one; User.create(body, (err, user) => { if (err && err.name === 'ValidationError') { return res.status(400).send(err) } else if (err) { return res.status(500).send(err) } return res.send({ user: user }) }); } else { return res.send({ user: body }) } });});
Callbacks
User.findOneAndUpdate({ email: body.email }, { $set: {password: body.password} }, (err, user) => { if (err) { return res.status(500).send(err) } if (!user) { // case #2 - user has never been registering before -> create new one; User.create(body, (err, user) => { if (err && err.name === 'ValidationError') { return res.status(400).send(err) } else if (err) { return res.status(500).send(err) } return res.send({ user: user }) }); } else { return res.send({ user: body }) } });
Callbacks
◉ If you miss an error handler - you are in troubles
◉ Duplicate callbacks / cb call might be lost in third party library
// foo is a function that returns a generatorfunction *foo() { console.log('start'); yield 1; console.log('second'); yield 2; yield 3;}
var gen = foo(); // get a generator objectgen.next() // 'start', { value: 1, done:false }gen.next() // 'second', { value: 2, done:false }gen.next() // { value: 3, done:false }gen.next() // { value: undefined, done:true }
Generators #1
const Message = require('./models/message');
function *foo() { yield new Promise((resolve, reject) => { resolve("Hello") }); yield Message.create({text: 'some text');}
var gen = foo();var genVal = gen.next().value // { value: Promise, done:false }genVal.then(res => console.log(res)) // Hellogen.next() // { value: Promise, done:false }
Generators #2 - Promises
const Message = require('./models/message');
function *foo() { var greeting = yield new Promise.resolve('Hello'); var result = yield Message.create({text: greeting}); console.log(result) // saved! {id: 'ID', text: 'Hello'}}
var gen = foo();gen.next().value // Promise .then(greeting => { console.log(greeting); // 'Hello' gen.next(greeting).value // Promise .then(createRes = gen.next(createRes)) })
Generators #3 - Two ways
var fetch = require('isomorphic-fetch');
function *foo() { try { var greeting = yield fetch('/api/get-greetings')[0]; } catch (e) { console.log(e); }}var gen = foo();gen.next().value .then(greeting => gen.next(greeting)) .catch(e => gen.throw(e))
Generators #4 - throw
function runner (gen) { var gen = gen(); next(null, null); function next(err, res) { const res = err ? gen.throw(err) : gen.next(res); if (res.done) return; res.value .then((res) => next(null, res)) .catch((error) => next(error)); }};
runner(function *foo() { var greeting = yield Promise.resolve("Hello") var name = yield Promise.resolve("John") console.log(greeting, name); // 'Hello John'})
Generators #5 - Runner
var co = require('co')
app.post('/',function (req, res, next) { var body = req.body; co(function *() { try { var user = yield User.findOneAndUpdate({ email: body.email }, { $set: {password: body.password} });
if (!user) { user = yield User.create(body); } res.send({ user: user }) } catch (e) { if (err && err.name === 'ValidationError') { return res.status(400).send(err) } else if (err) { return res.status(500).send(err) } } });});
Co
Using Co you can yield:
Thunks (will be deprecated)
Arrays
co(function* () { // parallel
var res = yield [ Promise.resolve(1),
Promise.resolve(2),
Promise.resolve(3), ];
console.log(res); // => [1, 2, 3]}).catch(onerror);
Generatorsfunction* greet() { return yield new Promise((resolve, rej) => { resolve("Hello, Mary!") });}
co(function *(){ var greeting = yield greet console.log(greeting)}).catch((err) => { console.log(err)});
Promises
Along with co will be useful:◉mz - promisify NodeJS core modules
◉Thenify promisify any callback-based function
var fs = require('mz/fs')
fs.exists(__filename).then(function (exists) { if (exists) // do something})
var thenify = require('thenify');
var somethingAsync = thenify(function somethingAsync(a, b, c, callback) { callback(null, a, b, c);});
Adopted by community
Callbacks Async Promise
sGenerato
rs(along with
co)
Koa Hello world
var koa = require('koa');var app = koa();
app.use(function *() { this.body = 'Hello World';});
app.listen(3000);
Koa API
koa Request
CONTEXTthis.requestthis.response
koa Response
this.querythis.urlthis.headersthis.get - get a header
this.bodythis.type - get content-typethis.redirect this.statusthis.set -set a header
app.use(function *(next) { var start = new Date; // <<<------ 1
yield next; // waits for downstream middlewares
var delta = Math.ceil(Date.now() - start); // <<<------ 3 console.info(`${this.method} ${this.url} - ${delta}ms`); this.set('X-Response-Time', delta + 'ms'); // <<<------ 4});
app.use(function *() {this.body = yield new Promise.resolve([10000]); // <<<------ 2
});
Response time - Koa middleware
3
4
2
1
Custom error
app.use(function *(next) { try { yield next; } catch (e) { if (e instanceof AuthError) { this.statusCode = e.status; this.redirect('/not-authorized'); this.body = this.render('not-authorized'); } else if (e.status) { this.statusCode = e.status; this.body = this.render('error', e); } else { this.statusCode = 500; this.body = this.render('error', e); logger.error('Unexpected error: ', e); // winston } }});
app.use(function *(next) { throw new AuthError()});
app.post('/',function (req, res, next) { var body = req.body; if (!body.name || !body.email || !body.password) { return res.status(400).send("Missing username or email or password") }; // case #1- if user was registered (socials platform) before -> just add a password; User.findOneAndUpdate({ email: body.email }, { $set: {password: body.password} }, (err, user) => { if (err) return res.status(500).send(err) if (!user) { // case #2 - user has never been registering before -> create new one; User.create(body, (err, user) => { if (err && err.name === 'ValidationError') { return res.status(400).send(err) } else if (err) { return res.status(500).send(err) } return res.send({ user: user }) }); } else { return res.send({ user: body }) } });});
Do you remember this example with callbacks?
app.use(function *(next) { try { yield next; } catch (e) { if (e.status) { // app error this.body = e.message; this.statusCode = e.status; } if else (e && e.name === 'ValidationError') { this.body = e; this.statusCode = 400; } else { // Internal server error this.body = "Oops!"; this.status = 500; } }});
Koa way
app.use(function *(next) { var body = this.request.body;
if (!body.name || !body.email || !body.password) { throw new Error("Missing username||email||password"); }
var user = yield User.findOneAndUpdate({email: body.email}, { $set: {password: body.password} });
if (!user) user = yield User.create(body); this.body = { user: this.body };});
2nd middleware
1st middleware
app.get('/', function (req, res, next) { var readStream = fs.createReadStream('text.txt'); readStream.pipe(res); readStream.on('error', () => { res.statusCode = 500; res.end('Server error has occurred!'); }); res.on('close', () => readStream.destroy());});
Send a file
app.use(function* (next) { this.body = fs.createReadStream('text.txt')});
Express/node
Koa
“
“Koa and express uses the same modules. We’ve refactored express
into jshttp and pillarjs so that anyone else can make their own framework as
well”
Express & Koa similarities
Same modulesAccepts, content-type, cookie, fresh, content-disposition, on-finished, parseurl, type-is, vary, escape-html
ModularEach feature is moved to a separate module.
MinimalisticContains only essential (core) modules (e.g. headers parsing).
Same developersKoa was build by core developers of Express: TJ Holowaychuk and others
Koa middlewares:
◉koa-router◉koa-bodyparse◉koa-static◉koa-session◉koa-compress◉koa-cors◉Koa-logger
more here
Koa
◉ router◉ bigger community◉ needs domains to catch
async errors◉ close to node style◉ a lot of outdated
examples and tutorials
Expressvs
◉ better error handling with try catch
◉ nice async flow control (no cb hell)
◉ api is more stable;◉ no build-in router◉ smaller community◉ support stream out of the
box◉ easy to understand source
code; no monkey patching